refactor: app.js (#1291)

* refactor: frontend

* Fix avatar gallery sort

* Update .NET dependencies

* Update npm dependencies

electron v37.1.0

* bulkRefreshFriends

* fix dark theme

* Remove crowdin

* Fix config.json dialog not updating

* VRCX log file fixes & add Cef log

* Remove SharedVariable, fix startup

* Revert init theme change

* Logging date not working? Fix WinformThemer designer error

* Add Cef request hander, no more escaping main page

* clean

* fix

* fix

* clean

* uh

* Apply thememode at startup, fixes random user colours

* Split database into files

* Instance info remove empty lines

* Open external VRC links with VRCX

* Electron fixes

* fix userdialog style

* ohhhh

* fix store

* fix store

* fix: load all group members after kicking a user

* fix: world dialog favorite button style

* fix: Clear VRCX Cache Timer input value

* clean

* Fix VR overlay

* Fix VR overlay 2

* Fix Discord discord rich presence for RPC worlds

* Clean up age verified user tags

* Fix playerList being occupied after program reload

* no `this`

* Fix login stuck loading

* writable: false

* Hide dialogs on logout

* add flush sync option

* rm LOGIN event

* rm LOGOUT event

* remove duplicate event listeners

* remove duplicate event listeners

* clean

* remove duplicate event listeners

* clean

* fix theme style

* fix t

* clearable

* clean

* fix ipcEvent

* Small changes

* Popcorn Palace support

* Remove checkActiveFriends

* Clean up

* Fix dragEnterCef

* Block API requests when not logged in

* Clear state on login & logout

* Fix worldDialog instances not updating

* use <script setup>

* Fix avatar change event, CheckGameRunning at startup

* Fix image dragging

* fix

* Remove PWI

* fix updateLoop

* add webpack-dev-server to dev environment

* rm unnecessary chunks

* use <script setup>

* webpack-dev-server changes

* use <script setup>

* use <script setup>

* Fix UGC text size

* Split login event

* t

* use <script setup>

* fix

* Update .gitignore and enable checkJs in jsconfig

* fix i18n t

* use <script setup>

* use <script setup>

* clean

* global types

* fix

* use checkJs for debugging

* Add watchState for login watchers

* fix .vue template

* type fixes

* rm Vue.filter

* Cef v138.0.170, VC++ 2022

* Settings fixes

* Remove 'USER:CURRENT'

* clean up 2FA callbacks

* remove userApply

* rm i18n import

* notification handling to use notification store methods

* refactor favorite handling to use favorite store methods and clean up event emissions

* refactor moderation handling to use dedicated functions for player moderation events

* refactor friend handling to use dedicated functions for friend events

* Fix program startup, move lang init

* Fix friend state

* Fix status change error

* Fix user notes diff

* fix

* rm group event

* rm auth event

* rm avatar event

* clean

* clean

* getUser

* getFriends

* getFavoriteWorlds, getFavoriteAvatars

* AvatarGalleryUpload btn style & package.json update

* Fix friend requests

* Apply user

* Apply world

* Fix note diff

* Fix VR overlay

* Fixes

* Update build scripts

* Apply avatar

* Apply instance

* Apply group

* update hidden VRC+ badge

* Fix sameInstance "private"

* fix 502/504 API errors

* fix 502/504 API errors

* clean

* Fix friend in same instance on orange showing twice in friends list

* Add back in broken friend state repair methods

* add types

---------

Co-authored-by: Natsumi <cmcooper123@hotmail.com>
This commit is contained in:
pa
2025-07-14 12:00:08 +09:00
committed by GitHub
parent 952fd77ed5
commit f4f78bb5ec
323 changed files with 47745 additions and 43326 deletions

109
src/App.vue Normal file
View File

@@ -0,0 +1,109 @@
<script>
import Vue, { onMounted } from 'vue';
import template from './app.pug';
import { createGlobalStores } from './stores';
import { watchState } from './service/watchState';
import Login from './views/Login/Login.vue';
import NavMenu from './components/NavMenu.vue';
import Sidebar from './views/Sidebar/Sidebar.vue';
import Feed from './views/Feed/Feed.vue';
import GameLog from './views/GameLog/GameLog.vue';
import PlayerList from './views/PlayerList/PlayerList.vue';
import Search from './views/Search/Search.vue';
import Favorites from './views/Favorites/Favorites.vue';
import FriendLog from './views/FriendLog/FriendLog.vue';
import Moderation from './views/Moderation/Moderation.vue';
import Notification from './views/Notifications/Notification.vue';
import FriendList from './views/FriendList/FriendList.vue';
import Charts from './views/Charts/Charts.vue';
import Profile from './views/Profile/Profile.vue';
import Settings from './views/Settings/Settings.vue';
import UserDialog from './components/dialogs/UserDialog/UserDialog.vue';
import WorldDialog from './components/dialogs/WorldDialog/WorldDialog.vue';
import AvatarDialog from './components/dialogs/AvatarDialog/AvatarDialog.vue';
import GroupDialog from './components/dialogs/GroupDialog/GroupDialog.vue';
import GalleryDialog from './components/dialogs/GalleryDialog.vue';
import FullscreenImageDialog from './components/dialogs/FullscreenImageDialog.vue';
import PreviousInstancesInfoDialog from './components/dialogs/PreviousInstancesDialog/PreviousInstancesInfoDialog.vue';
import LaunchDialog from './components/dialogs/LaunchDialog.vue';
import LaunchOptionsDialog from './views/Settings/dialogs/LaunchOptionsDialog.vue';
import FriendImportDialog from './views/Favorites/dialogs/FriendImportDialog.vue';
import WorldImportDialog from './views/Favorites/dialogs/WorldImportDialog.vue';
import AvatarImportDialog from './views/Favorites/dialogs/AvatarImportDialog.vue';
import ChooseFavoriteGroupDialog from './components/dialogs/ChooseFavoriteGroupDialog.vue';
import EditInviteMessageDialog from './views/Profile/dialogs/EditInviteMessageDialog.vue';
import VRCXUpdateDialog from './components/dialogs/VRCXUpdateDialog.vue';
import VRChatConfigDialog from './views/Settings/dialogs/VRChatConfigDialog.vue';
import PrimaryPasswordDialog from './views/Settings/dialogs/PrimaryPasswordDialog.vue';
import { utils } from './shared/utils/_utils';
export default {
template,
components: {
Login,
NavMenu,
Sidebar,
Feed,
GameLog,
PlayerList,
Search,
Favorites,
FriendLog,
Moderation,
Notification,
FriendList,
Charts,
Profile,
Settings,
UserDialog,
WorldDialog,
AvatarDialog,
GroupDialog,
GalleryDialog,
FullscreenImageDialog,
PreviousInstancesInfoDialog,
LaunchDialog,
LaunchOptionsDialog,
FriendImportDialog,
WorldImportDialog,
AvatarImportDialog,
ChooseFavoriteGroupDialog,
EditInviteMessageDialog,
VRCXUpdateDialog,
VRChatConfigDialog,
PrimaryPasswordDialog
},
setup() {
const store = createGlobalStores();
Vue.prototype.store = store;
Vue.prototype.utils = utils;
store.updateLoop.updateLoop();
onMounted(async () => {
store.gameLog.getGameLogTable();
await store.auth.migrateStoredUsers();
store.auth.autoLoginAfterMounted();
store.vrcx.checkAutoBackupRestoreVrcRegistry();
store.game.checkVRChatDebugLogging();
try {
if (await AppApi.IsRunningUnderWine()) {
store.vrcx.isRunningUnderWine = true;
store.vrcx.applyWineEmojis();
}
} catch (err) {
console.error(err, 'Failed to check if running under Wine');
}
});
return {
store,
watchState
};
}
};
</script>

69
src/api/auth.js Normal file
View File

@@ -0,0 +1,69 @@
import { request } from '../service/request';
import { useUserStore } from '../stores';
const loginReq = {
/**
* @param {{ code: string }} params One-time password
* @returns {Promise<{json: any, params}>}
*/
verifyOTP(params) {
return request('auth/twofactorauth/otp/verify', {
method: 'POST',
params
}).then((json) => {
const args = {
json,
params
};
return args;
});
},
/**
* @param {{ code: string }} params One-time token
* @returns {Promise<{json: any, params}>}
*/
verifyTOTP(params) {
return request('auth/twofactorauth/totp/verify', {
method: 'POST',
params
}).then((json) => {
const args = {
json,
params
};
return args;
});
},
/**
* @param {{ code: string }} params One-time token
* @returns {Promise<{json: any, params}>}
*/
verifyEmailOTP(params) {
return request('auth/twofactorauth/emailotp/verify', {
method: 'POST',
params
}).then((json) => {
const args = {
json,
params
};
return args;
});
},
getConfig() {
return request('config', {
method: 'GET'
}).then((json) => {
const args = {
json
};
useUserStore().handleConfig(args);
return args;
});
}
};
export default loginReq;

View File

@@ -1,19 +1,18 @@
// #region | API: Avatar
import { request } from '../service/request';
import { useUserStore } from '../stores';
const avatarReq = {
/**
* @param {{ avatarId: string }} params
* @returns {Promise<{json: any, params}>}
* @type {import('../types/avatar').getAvatar}
*/
getAvatar(params) {
return window.API.call(`avatars/${params.avatarId}`, {
return request(`avatars/${params.avatarId}`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('AVATAR', args);
return args;
});
},
@@ -37,7 +36,7 @@ const avatarReq = {
* @returns {Promise<{json: any, params}>}
*/
getAvatars(params) {
return window.API.call('avatars', {
return request('avatars', {
method: 'GET',
params
}).then((json) => {
@@ -45,16 +44,16 @@ const avatarReq = {
json,
params
};
window.API.$emit('AVATAR:LIST', args);
return args;
});
},
/**
* @param {{ id: string, releaseStatus: 'public' | 'private' }} params
* @param {{ id: string, releaseStatus?: 'public' | 'private', name?: string, description?: string }} params
* @returns {Promise<{json: any, params}>}
*/
saveAvatar(params) {
return window.API.call(`avatars/${params.id}`, {
return request(`avatars/${params.id}`, {
method: 'PUT',
params
}).then((json) => {
@@ -62,7 +61,6 @@ const avatarReq = {
json,
params
};
window.API.$emit('AVATAR:SAVE', args);
return args;
});
},
@@ -72,7 +70,8 @@ const avatarReq = {
* @returns {Promise<{json: any, params}>}
*/
selectAvatar(params) {
return window.API.call(`avatars/${params.avatarId}/select`, {
const userStore = useUserStore();
return request(`avatars/${params.avatarId}/select`, {
method: 'PUT',
params
}).then((json) => {
@@ -80,7 +79,7 @@ const avatarReq = {
json,
params
};
window.API.$emit('AVATAR:SELECT', args);
userStore.applyCurrentUser(json);
return args;
});
},
@@ -90,7 +89,8 @@ const avatarReq = {
* @return { Promise<{json: any, params}> }
*/
selectFallbackAvatar(params) {
return window.API.call(`avatars/${params.avatarId}/selectfallback`, {
const userStore = useUserStore();
return request(`avatars/${params.avatarId}/selectfallback`, {
method: 'PUT',
params
}).then((json) => {
@@ -98,7 +98,7 @@ const avatarReq = {
json,
params
};
window.API.$emit('AVATAR:SELECT', args);
userStore.applyCurrentUser(json);
return args;
});
},
@@ -108,14 +108,13 @@ const avatarReq = {
* @return { Promise<{json: any, params}> }
*/
deleteAvatar(params) {
return window.API.call(`avatars/${params.avatarId}`, {
return request(`avatars/${params.avatarId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('AVATAR:DELETE', args);
return args;
});
},
@@ -125,14 +124,13 @@ const avatarReq = {
* @returns {Promise<{json: any, params}>}
*/
createImposter(params) {
return window.API.call(`avatars/${params.avatarId}/impostor/enqueue`, {
return request(`avatars/${params.avatarId}/impostor/enqueue`, {
method: 'POST'
}).then((json) => {
const args = {
json,
params
};
// window.API.$emit('AVATAR:IMPOSTER:CREATE', args);
return args;
});
},
@@ -142,35 +140,33 @@ const avatarReq = {
* @returns {Promise<{json: T, params}>}
*/
deleteImposter(params) {
return window.API.call(`avatars/${params.avatarId}/impostor`, {
return request(`avatars/${params.avatarId}/impostor`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
// window.API.$emit('AVATAR:IMPOSTER:DELETE', args);
return args;
});
},
/**
* @returns {Promise<{json: any, params}>}
* @returns {Promise<{json: any}>}
*/
getAvailableAvatarStyles() {
return window.API.call('avatarStyles', {
return request('avatarStyles', {
method: 'GET'
}).then((json) => {
const args = {
json
};
// window.API.$emit('AVATAR:STYLES', args);
return args;
});
},
/**
* @param {{ avatarId: string }} params
* @param {{ avatarId: string }} avatarId
* @returns {Promise<{json: any, params}>}
*/
getAvatarGallery(avatarId) {
@@ -180,7 +176,7 @@ const avatarReq = {
n: 100,
offset: 0
};
return window.API.call(`files`, {
return request(`files`, {
params,
method: 'GET'
}).then((json) => {
@@ -188,7 +184,6 @@ const avatarReq = {
json,
params
};
// window.API.$emit('AVATAR:GALLERY', args);
return args;
});
},
@@ -202,7 +197,7 @@ const avatarReq = {
tag: 'avatargallery',
galleryId: avatarId
};
return window.API.call('file/image', {
return request('file/image', {
uploadImage: true,
matchingDimensions: false,
postData: JSON.stringify(params),
@@ -212,7 +207,6 @@ const avatarReq = {
json,
params
};
// window.API.$emit('AVATARGALLERYIMAGE:ADD', args);
return args;
});
},
@@ -225,7 +219,7 @@ const avatarReq = {
const params = {
ids: order
};
return window.API.call('files/order', {
return request('files/order', {
method: 'PUT',
params
}).then((json) => {
@@ -233,11 +227,9 @@ const avatarReq = {
json,
params
};
// window.API.$emit('AVATARGALLERYIMAGE:ORDER', args);
return args;
});
}
};
// #endregion
export default avatarReq;

View File

@@ -1,14 +1,13 @@
// #region | API: AvatarModeration
import { request } from '../service/request';
const avatarModerationReq = {
getAvatarModerations() {
return window.API.call('auth/user/avatarmoderations', {
return request('auth/user/avatarmoderations', {
method: 'GET'
}).then((json) => {
const args = {
json
};
// window.API.$emit('AVATAR-MODERATION:LIST', args);
return args;
});
},
@@ -18,7 +17,7 @@ const avatarModerationReq = {
* @return { Promise<{json: any, params}> }
*/
sendAvatarModeration(params) {
return window.API.call('auth/user/avatarmoderations', {
return request('auth/user/avatarmoderations', {
method: 'POST',
params
}).then((json) => {
@@ -26,7 +25,6 @@ const avatarModerationReq = {
json,
params
};
// window.API.$emit('AVATAR-MODERATION', args);
return args;
});
},
@@ -36,7 +34,7 @@ const avatarModerationReq = {
* @return { Promise<{json: any, params}> }
*/
deleteAvatarModeration(params) {
return window.API.call(
return request(
`auth/user/avatarmoderations?targetAvatarId=${encodeURIComponent(
params.targetAvatarId
)}&avatarModerationType=${encodeURIComponent(
@@ -50,11 +48,9 @@ const avatarModerationReq = {
json,
params
};
window.API.$emit('AVATAR-MODERATION:DELETE', args);
return args;
});
}
};
// #endregion
export default avatarModerationReq;

View File

@@ -1,29 +1,27 @@
// #region | API: Favorite
import { request } from '../service/request';
import { useFavoriteStore, useUserStore } from '../stores';
function getCurrentUserId() {
return useUserStore().currentUser.id;
}
const favoriteReq = {
getFavoriteLimits() {
return window.API.call('auth/user/favoritelimits', {
return request('auth/user/favoritelimits', {
method: 'GET'
}).then((json) => {
const args = {
json
};
window.API.$emit('FAVORITE:LIMITS', args);
return args;
});
},
/**
* @param {{
* n: number,
* offset: number,
* type: string,
* tag: string
* }} params
* @return { Promise<{json: any, params}> }
* @type {import('../types/favorite').getFavorites}
*/
getFavorites(params) {
return window.API.call('favorites', {
return request('favorites', {
method: 'GET',
params
}).then((json) => {
@@ -31,7 +29,6 @@ const favoriteReq = {
json,
params
};
window.API.$emit('FAVORITE:LIST', args);
return args;
});
},
@@ -45,7 +42,7 @@ const favoriteReq = {
* @return { Promise<{json: any, params}> }
*/
addFavorite(params) {
return window.API.call('favorites', {
return request('favorites', {
method: 'POST',
params
}).then((json) => {
@@ -53,7 +50,7 @@ const favoriteReq = {
json,
params
};
window.API.$emit('FAVORITE:ADD', args);
useFavoriteStore().handleFavoriteAdd(args);
return args;
});
},
@@ -63,14 +60,14 @@ const favoriteReq = {
* @return { Promise<{json: any, params}> }
*/
deleteFavorite(params) {
return window.API.call(`favorites/${params.objectId}`, {
return request(`favorites/${params.objectId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('FAVORITE:DELETE', args);
useFavoriteStore().handleFavoriteDelete(args);
return args;
});
},
@@ -80,7 +77,7 @@ const favoriteReq = {
* @return { Promise<{json: any, params}> }
*/
getFavoriteGroups(params) {
return window.API.call('favorite/groups', {
return request('favorite/groups', {
method: 'GET',
params
}).then((json) => {
@@ -88,7 +85,6 @@ const favoriteReq = {
json,
params
};
window.API.$emit('FAVORITE:GROUP:LIST', args);
return args;
});
},
@@ -99,8 +95,8 @@ const favoriteReq = {
* @return { Promise<{json: any, params}> }
*/
saveFavoriteGroup(params) {
return window.API.call(
`favorite/group/${params.type}/${params.group}/${window.API.currentUser.id}`,
return request(
`favorite/group/${params.type}/${params.group}/${getCurrentUserId()}`,
{
method: 'PUT',
params
@@ -110,7 +106,6 @@ const favoriteReq = {
json,
params
};
window.API.$emit('FAVORITE:GROUP:SAVE', args);
return args;
});
},
@@ -123,8 +118,8 @@ const favoriteReq = {
* @return { Promise<{json: any, params}> }
*/
clearFavoriteGroup(params) {
return window.API.call(
`favorite/group/${params.type}/${params.group}/${window.API.currentUser.id}`,
return request(
`favorite/group/${params.type}/${params.group}/${getCurrentUserId()}`,
{
method: 'DELETE',
params
@@ -134,20 +129,16 @@ const favoriteReq = {
json,
params
};
window.API.$emit('FAVORITE:GROUP:CLEAR', args);
useFavoriteStore().handleFavoriteGroupClear(args);
return args;
});
},
/**
* @param {{
* n: number,
* offset: number
* }} params
* @return { Promise<{json: any, params}> }
* @type {import('../types/favorite').getFavoriteWorlds}
*/
getFavoriteWorlds(params) {
return window.API.call('worlds/favorites', {
return request('worlds/favorites', {
method: 'GET',
params
}).then((json) => {
@@ -155,20 +146,15 @@ const favoriteReq = {
json,
params
};
window.API.$emit('FAVORITE:WORLD:LIST', args);
return args;
});
},
/**
* @param {{
* n: number,
* offset: number
* }} params
* @return { Promise<{json: any, params}> }
* @type {import('../types/favorite').getFavoriteAvatars}
*/
getFavoriteAvatars(params) {
return window.API.call('avatars/favorites', {
return request('avatars/favorites', {
method: 'GET',
params
}).then((json) => {
@@ -176,12 +162,9 @@ const favoriteReq = {
json,
params
};
window.API.$emit('FAVORITE:AVATAR:LIST', args);
return args;
});
}
};
// #endregion
export default favoriteReq;

View File

@@ -1,13 +1,14 @@
// #region | API: Friend
import { request } from '../service/request';
import { useUserStore } from '../stores/user';
const friendReq = {
/**
* Fetch friends of current user.
* @param {{ n: number, offset: number, offline: boolean }} params
* @returns {Promise<{json: any, params}>}
* @type {import('../types/friend').getFriends}
*/
getFriends(params) {
return window.API.call('auth/user/friends', {
const userStore = useUserStore();
return request('auth/user/friends', {
method: 'GET',
params
}).then((json) => {
@@ -15,7 +16,13 @@ const friendReq = {
json,
params
};
window.API.$emit('FRIEND:LIST', args);
for (const user of args.json) {
if (!user.displayName) {
console.error('/friends gave us garbage', user);
continue;
}
userStore.applyUser(user);
}
return args;
});
},
@@ -25,14 +32,13 @@ const friendReq = {
* @returns {Promise<{json: T, params}>}
*/
sendFriendRequest(params) {
return window.API.call(`user/${params.userId}/friendRequest`, {
return request(`user/${params.userId}/friendRequest`, {
method: 'POST'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('FRIEND:REQUEST', args);
return args;
});
},
@@ -42,14 +48,13 @@ const friendReq = {
* @returns {Promise<{json: any, params}>}
*/
cancelFriendRequest(params) {
return window.API.call(`user/${params.userId}/friendRequest`, {
return request(`user/${params.userId}/friendRequest`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
// window.API.$emit('FRIEND:REQUEST:CANCEL', args);
return args;
});
},
@@ -59,14 +64,13 @@ const friendReq = {
* @returns {Promise<{json: any, params}>}
*/
deleteFriend(params) {
return window.API.call(`auth/user/friends/${params.userId}`, {
return request(`auth/user/friends/${params.userId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('FRIEND:DELETE', args);
return args;
});
},
@@ -76,7 +80,7 @@ const friendReq = {
* @returns {Promise<{json: any, params}>}
*/
getFriendStatus(params) {
return window.API.call(`user/${params.userId}/friendStatus`, {
return request(`user/${params.userId}/friendStatus`, {
method: 'GET'
}).then((json) => {
console.log('getFriendStatus', json);
@@ -84,15 +88,12 @@ const friendReq = {
json,
params
};
window.API.$emit('FRIEND:STATUS', args);
return args;
});
},
// ------------------- need to test -------------------
deleteHiddenFriendRequest(params, userId) {
return window.API.call(`user/${userId}/friendRequest`, {
return request(`user/${userId}/friendRequest`, {
method: 'DELETE',
params
}).then((json) => {
@@ -101,11 +102,9 @@ const friendReq = {
params,
userId
};
window.API.$emit('NOTIFICATION:HIDE', args);
return args;
});
}
};
// #endregion
export default friendReq;

View File

@@ -1,3 +1,9 @@
import { request } from '../service/request';
import { useUserStore, useGroupStore } from '../stores';
function getCurrentUserId() {
return useUserStore().currentUser.id;
}
const groupReq = {
/**
* @param {string} groupId
@@ -5,7 +11,7 @@ const groupReq = {
* @returns
*/
setGroupRepresentation(groupId, params) {
return window.API.call(`groups/${groupId}/representation`, {
return request(`groups/${groupId}/representation`, {
method: 'PUT',
params
}).then((json) => {
@@ -23,7 +29,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
cancelGroupRequest(params) {
return window.API.call(`groups/${params.groupId}/requests`, {
return request(`groups/${params.groupId}/requests`, {
method: 'DELETE'
}).then((json) => {
const args = {
@@ -39,12 +45,9 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
deleteGroupPost(params) {
return window.API.call(
`groups/${params.groupId}/posts/${params.postId}`,
{
method: 'DELETE'
}
).then((json) => {
return request(`groups/${params.groupId}/posts/${params.postId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
@@ -53,10 +56,10 @@ const groupReq = {
});
},
/**
* @param {{ groupId: string }} params
* @type {import('../types/group').getGroup}
*/
getGroup(params) {
return window.API.call(`groups/${params.groupId}`, {
return request(`groups/${params.groupId}`, {
method: 'GET',
params: {
includeRoles: params.includeRoles || false
@@ -66,23 +69,48 @@ const groupReq = {
json,
params
};
window.API.$emit('GROUP', args);
return args;
});
},
/**
*
* @param {{ groupId: string }} params
* @return { Promise<{json: any, params}> }
*/
getCachedGroup(params) {
const groupStore = useGroupStore();
return new Promise((resolve, reject) => {
const ref = groupStore.cachedGroups.get(params.groupId);
if (typeof ref === 'undefined') {
groupReq
.getGroup(params)
.catch(reject)
.then((args) => {
args.ref = groupStore.applyGroup(args.json);
resolve(args);
});
} else {
resolve({
cache: true,
json: ref,
params,
ref
});
}
});
},
/**
* @param {{ userId: string }} params
* @return { Promise<{json: any, params}> }
*/
getRepresentedGroup(params) {
return window.API.call(`users/${params.userId}/groups/represented`, {
return request(`users/${params.userId}/groups/represented`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('GROUP:REPRESENTED', args);
return args;
});
},
@@ -91,14 +119,13 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroups(params) {
return window.API.call(`users/${params.userId}/groups`, {
return request(`users/${params.userId}/groups`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('GROUP:LIST', args);
return args;
});
},
@@ -107,14 +134,13 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
joinGroup(params) {
return window.API.call(`groups/${params.groupId}/join`, {
return request(`groups/${params.groupId}/join`, {
method: 'POST'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('GROUP:JOIN', args);
return args;
});
},
@@ -123,7 +149,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
leaveGroup(params) {
return window.API.call(`groups/${params.groupId}/leave`, {
return request(`groups/${params.groupId}/leave`, {
method: 'POST'
}).then((json) => {
const args = {
@@ -138,7 +164,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
groupStrictsearch(params) {
return window.API.call(`groups/strictsearch`, {
return request(`groups/strictsearch`, {
method: 'GET',
params
}).then((json) => {
@@ -159,7 +185,7 @@ const groupReq = {
}
*/
setGroupMemberProps(userId, groupId, params) {
return window.API.call(`groups/${groupId}/members/${userId}`, {
return request(`groups/${groupId}/members/${userId}`, {
method: 'PUT',
params
}).then((json) => {
@@ -169,7 +195,6 @@ const groupReq = {
groupId,
params
};
window.API.$emit('GROUP:MEMBER:PROPS', args);
return args;
});
},
@@ -182,7 +207,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
addGroupMemberRole(params) {
return window.API.call(
return request(
`groups/${params.groupId}/members/${params.userId}/roles/${params.roleId}`,
{
method: 'PUT'
@@ -192,7 +217,6 @@ const groupReq = {
json,
params
};
// window.API.$emit('GROUP:MEMBER:ROLE:CHANGE', args);
return args;
});
},
@@ -205,7 +229,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
removeGroupMemberRole(params) {
return window.API.call(
return request(
`groups/${params.groupId}/members/${params.userId}/roles/${params.roleId}`,
{
method: 'DELETE'
@@ -215,19 +239,17 @@ const groupReq = {
json,
params
};
// window.API.$emit('GROUP:MEMBER:ROLE:CHANGE', args);
return args;
});
},
getGroupPermissions(params) {
return window.API.call(`users/${params.userId}/groups/permissions`, {
return request(`users/${params.userId}/groups/permissions`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('GROUP:PERMISSIONS', args);
return args;
});
},
@@ -240,7 +262,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupPosts(params) {
return window.API.call(`groups/${params.groupId}/posts`, {
return request(`groups/${params.groupId}/posts`, {
method: 'GET',
params
}).then((json) => {
@@ -248,28 +270,23 @@ const groupReq = {
json,
params
};
window.API.$emit('GROUP:POSTS', args);
return args;
});
},
editGroupPost(params) {
return window.API.call(
`groups/${params.groupId}/posts/${params.postId}`,
{
method: 'PUT',
params
}
).then((json) => {
return request(`groups/${params.groupId}/posts/${params.postId}`, {
method: 'PUT',
params
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('GROUP:POST', args);
return args;
});
},
createGroupPost(params) {
return window.API.call(`groups/${params.groupId}/posts`, {
return request(`groups/${params.groupId}/posts`, {
method: 'POST',
params
}).then((json) => {
@@ -277,7 +294,6 @@ const groupReq = {
json,
params
};
window.API.$emit('GROUP:POST', args);
return args;
});
},
@@ -289,17 +305,13 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupMember(params) {
return window.API.call(
`groups/${params.groupId}/members/${params.userId}`,
{
method: 'GET'
}
).then((json) => {
return request(`groups/${params.groupId}/members/${params.userId}`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
// window.API.$emit('GROUP:MEMBER', args);
return args;
});
},
@@ -312,7 +324,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupMembers(params) {
return window.API.call(`groups/${params.groupId}/members`, {
return request(`groups/${params.groupId}/members`, {
method: 'GET',
params
}).then((json) => {
@@ -320,7 +332,6 @@ const groupReq = {
json,
params
};
window.API.$emit('GROUP:MEMBERS', args);
return args;
});
},
@@ -334,7 +345,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupMembersSearch(params) {
return window.API.call(`groups/${params.groupId}/members/search`, {
return request(`groups/${params.groupId}/members/search`, {
method: 'GET',
params
}).then((json) => {
@@ -352,7 +363,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
blockGroup(params) {
return window.API.call(`groups/${params.groupId}/block`, {
return request(`groups/${params.groupId}/block`, {
method: 'POST'
}).then((json) => {
const args = {
@@ -370,12 +381,9 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
unblockGroup(params) {
return window.API.call(
`groups/${params.groupId}/members/${params.userId}`,
{
method: 'DELETE'
}
).then((json) => {
return request(`groups/${params.groupId}/members/${params.userId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
@@ -391,7 +399,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
sendGroupInvite(params) {
return window.API.call(`groups/${params.groupId}/invites`, {
return request(`groups/${params.groupId}/invites`, {
method: 'POST',
params: {
userId: params.userId
@@ -401,7 +409,6 @@ const groupReq = {
json,
params
};
window.API.$emit('GROUP:INVITE', args);
return args;
});
},
@@ -413,18 +420,13 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
kickGroupMember(params) {
return window.API.call(
`groups/${params.groupId}/members/${params.userId}`,
{
method: 'DELETE'
}
).then((json) => {
return request(`groups/${params.groupId}/members/${params.userId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
// useless code
// window.API.$emit('GROUP:MEMBER:KICK', args);
return args;
});
},
@@ -433,7 +435,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
banGroupMember(params) {
return window.API.call(`groups/${params.groupId}/bans`, {
return request(`groups/${params.groupId}/bans`, {
method: 'POST',
params: {
userId: params.userId
@@ -443,24 +445,17 @@ const groupReq = {
json,
params
};
// useless code
// window.API.$emit('GROUP:MEMBER:BAN', args);
return args;
});
},
unbanGroupMember(params) {
return window.API.call(
`groups/${params.groupId}/bans/${params.userId}`,
{
method: 'DELETE'
}
).then((json) => {
return request(`groups/${params.groupId}/bans/${params.userId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
// useless code
// window.API.$emit('GROUP:MEMBER:UNBAN', args);
return args;
});
},
@@ -469,97 +464,73 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
deleteSentGroupInvite(params) {
return window.API.call(
`groups/${params.groupId}/invites/${params.userId}`,
{
method: 'DELETE'
}
).then((json) => {
return request(`groups/${params.groupId}/invites/${params.userId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
// useless code
// window.API.$emit('GROUP:INVITE:DELETE', args);
return args;
});
},
deleteBlockedGroupRequest(params) {
return window.API.call(
`groups/${params.groupId}/members/${params.userId}`,
{
method: 'DELETE'
}
).then((json) => {
return request(`groups/${params.groupId}/members/${params.userId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
// useless code
// window.API.$emit('GROUP:BLOCKED:DELETE', args);
return args;
});
},
acceptGroupInviteRequest(params) {
return window.API.call(
`groups/${params.groupId}/requests/${params.userId}`,
{
method: 'PUT',
params: {
action: 'accept'
}
return request(`groups/${params.groupId}/requests/${params.userId}`, {
method: 'PUT',
params: {
action: 'accept'
}
).then((json) => {
}).then((json) => {
const args = {
json,
params
};
// useless code
// window.API.$emit('GROUP:INVITE:ACCEPT', args);
return args;
});
},
rejectGroupInviteRequest(params) {
return window.API.call(
`groups/${params.groupId}/requests/${params.userId}`,
{
method: 'PUT',
params: {
action: 'reject'
}
return request(`groups/${params.groupId}/requests/${params.userId}`, {
method: 'PUT',
params: {
action: 'reject'
}
).then((json) => {
}).then((json) => {
const args = {
json,
params
};
// useless code
// window.API.$emit('GROUP:INVITE:REJECT', args);
return args;
});
},
blockGroupInviteRequest(params) {
return window.API.call(
`groups/${params.groupId}/requests/${params.userId}`,
{
method: 'PUT',
params: {
action: 'reject',
block: true
}
return request(`groups/${params.groupId}/requests/${params.userId}`, {
method: 'PUT',
params: {
action: 'reject',
block: true
}
).then((json) => {
}).then((json) => {
const args = {
json,
params
};
// useless code
// window.API.$emit('GROUP:INVITE:BLOCK', args);
return args;
});
},
getGroupBans(params) {
return window.API.call(`groups/${params.groupId}/bans`, {
return request(`groups/${params.groupId}/bans`, {
method: 'GET',
params
}).then((json) => {
@@ -575,7 +546,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupAuditLogTypes(params) {
return window.API.call(`groups/${params.groupId}/auditLogTypes`, {
return request(`groups/${params.groupId}/auditLogTypes`, {
method: 'GET'
}).then((json) => {
const args = {
@@ -590,7 +561,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupLogs(params) {
return window.API.call(`groups/${params.groupId}/auditLogs`, {
return request(`groups/${params.groupId}/auditLogs`, {
method: 'GET',
params
}).then((json) => {
@@ -606,7 +577,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupInvites(params) {
return window.API.call(`groups/${params.groupId}/invites`, {
return request(`groups/${params.groupId}/invites`, {
method: 'GET',
params
}).then((json) => {
@@ -622,7 +593,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupJoinRequests(params) {
return window.API.call(`groups/${params.groupId}/requests`, {
return request(`groups/${params.groupId}/requests`, {
method: 'GET',
params
}).then((json) => {
@@ -630,7 +601,6 @@ const groupReq = {
json,
params
};
// window.API.$emit('GROUP:JOINREQUESTS', args);
return args;
});
},
@@ -639,8 +609,8 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupInstances(params) {
return window.API.call(
`users/${window.API.currentUser.id}/instances/groups/${params.groupId}`,
return request(
`users/${getCurrentUserId()}/instances/groups/${params.groupId}`,
{
method: 'GET'
}
@@ -657,7 +627,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupRoles(params) {
return window.API.call(`groups/${params.groupId}/roles`, {
return request(`groups/${params.groupId}/roles`, {
method: 'GET',
params
}).then((json) => {
@@ -665,22 +635,16 @@ const groupReq = {
json,
params
};
// useless code
// this.$emit('GROUP:ROLES', args);
return args;
});
},
getUsersGroupInstances() {
return window.API.call(
`users/${window.API.currentUser.id}/instances/groups`,
{
method: 'GET'
}
).then((json) => {
return request(`users/${getCurrentUserId()}/instances/groups`, {
method: 'GET'
}).then((json) => {
const args = {
json
};
window.API.$emit('GROUP:USER:INSTANCES', args);
return args;
});
},
@@ -696,7 +660,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
groupSearch(params) {
return window.API.call(`groups`, {
return request(`groups`, {
method: 'GET',
params
}).then((json) => {
@@ -717,7 +681,7 @@ const groupReq = {
* @return { Promise<{json: any, params}> }
*/
getGroupGallery(params) {
return window.API.call(
return request(
`groups/${params.groupId}/galleries/${params.galleryId}`,
{
method: 'GET',
@@ -735,10 +699,9 @@ const groupReq = {
});
}
// no place to use this
// getRequestedGroups() {
// return window.API.call(
// `users/${window.API.currentUser.id}/groups/requested`,
// return request(
// `users/${API.currentUser.id}/groups/requested`,
// {
// method: 'GET'
// }
@@ -746,18 +709,17 @@ const groupReq = {
// const args = {
// json
// };
// window.API.$emit('GROUP:REQUESTED', args);
// API.$emit('GROUP:REQUESTED', args);
// return args;
// });
// }
// ----------------- left over code -----------------
// /**
// * @param {{ groupId: string }} params
// * @return { Promise<{json: any, params}> }
// */
// API.getGroupAnnouncement = function (params) {
// return this.call(`groups/${params.groupId}/announcement`, {
// return request(`groups/${params.groupId}/announcement`, {
// method: 'GET'
// }).then((json) => {
// var args = {

View File

@@ -1,25 +1,26 @@
import { request } from '../service/request';
import { useAvatarStore, useWorldStore } from '../stores';
const imageReq = {
// use in uploadAvatarImage
// need to test
async uploadAvatarFailCleanup(id) {
const json = await window.API.call(`file/${id}`, {
const avatarStore = useAvatarStore();
const json = await request(`file/${id}`, {
method: 'GET'
});
const fileId = json.id;
const fileVersion = json.versions[json.versions.length - 1].version;
window.API.call(`file/${fileId}/${fileVersion}/signature/finish`, {
request(`file/${fileId}/${fileVersion}/signature/finish`, {
method: 'PUT'
});
window.API.call(`file/${fileId}/${fileVersion}/file/finish`, {
request(`file/${fileId}/${fileVersion}/file/finish`, {
method: 'PUT'
});
window.$app.avatarDialog.loading = false;
// window.$app.changeAvatarImageDialogLoading = false;
avatarStore.avatarDialog.loading = false;
},
async uploadAvatarImage(params, fileId) {
try {
return await window.API.call(`file/${fileId}`, {
return await request(`file/${fileId}`, {
method: 'POST',
params
}).then((json) => {
@@ -28,7 +29,6 @@ const imageReq = {
params,
fileId
};
// window.API.$emit('AVATARIMAGE:INIT', args);
return args;
});
} catch (err) {
@@ -36,12 +36,11 @@ const imageReq = {
imageReq.uploadAvatarFailCleanup(fileId);
throw err;
}
// return void 0;
},
async uploadAvatarImageFileStart(params) {
try {
return await window.API.call(
return await request(
`file/${params.fileId}/${params.fileVersion}/file/start`,
{
method: 'PUT'
@@ -51,18 +50,16 @@ const imageReq = {
json,
params
};
// window.API.$emit('AVATARIMAGE:FILESTART', args);
return args;
});
} catch (err) {
console.error(err);
imageReq.uploadAvatarFailCleanup(params.fileId);
}
return void 0;
},
uploadAvatarImageFileFinish(params) {
return window.API.call(
return request(
`file/${params.fileId}/${params.fileVersion}/file/finish`,
{
method: 'PUT',
@@ -76,14 +73,13 @@ const imageReq = {
json,
params
};
// window.API.$emit('AVATARIMAGE:FILEFINISH', args);
return args;
});
},
async uploadAvatarImageSigStart(params) {
try {
return await window.API.call(
return await request(
`file/${params.fileId}/${params.fileVersion}/signature/start`,
{
method: 'PUT'
@@ -93,18 +89,16 @@ const imageReq = {
json,
params
};
// window.API.$emit('AVATARIMAGE:SIGSTART', args);
return args;
});
} catch (err) {
console.error(err);
imageReq.uploadAvatarFailCleanup(params.fileId);
}
return void 0;
},
uploadAvatarImageSigFinish(params) {
return window.API.call(
return request(
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
{
method: 'PUT',
@@ -118,13 +112,12 @@ const imageReq = {
json,
params
};
// window.API.$emit('AVATARIMAGE:SIGFINISH', args);
return args;
});
},
setAvatarImage(params) {
return window.API.call(`avatars/${params.id}`, {
return request(`avatars/${params.id}`, {
method: 'PUT',
params
}).then((json) => {
@@ -132,33 +125,29 @@ const imageReq = {
json,
params
};
// window.API.$emit('AVATARIMAGE:SET', args);
window.API.$emit('AVATAR', args);
return args;
});
},
// use in uploadWorldImage
// need to test
async uploadWorldFailCleanup(id) {
const json = await window.API.call(`file/${id}`, {
const worldStore = useWorldStore();
const json = await request(`file/${id}`, {
method: 'GET'
});
const fileId = json.id;
const fileVersion = json.versions[json.versions.length - 1].version;
window.API.call(`file/${fileId}/${fileVersion}/signature/finish`, {
request(`file/${fileId}/${fileVersion}/signature/finish`, {
method: 'PUT'
});
window.API.call(`file/${fileId}/${fileVersion}/file/finish`, {
request(`file/${fileId}/${fileVersion}/file/finish`, {
method: 'PUT'
});
window.$app.worldDialog.loading = false;
// window.$app.changeWorldImageDialogLoading = false;
worldStore.worldDialog.loading = false;
},
async uploadWorldImage(params, fileId) {
try {
return await window.API.call(`file/${fileId}`, {
return await request(`file/${fileId}`, {
method: 'POST',
params
}).then((json) => {
@@ -167,7 +156,6 @@ const imageReq = {
params,
fileId
};
// window.API.$emit('WORLDIMAGE:INIT', args);
return args;
});
} catch (err) {
@@ -179,7 +167,7 @@ const imageReq = {
async uploadWorldImageFileStart(params) {
try {
return await window.API.call(
return await request(
`file/${params.fileId}/${params.fileVersion}/file/start`,
{
method: 'PUT'
@@ -189,7 +177,6 @@ const imageReq = {
json,
params
};
// window.API.$emit('WORLDIMAGE:FILESTART', args);
return args;
});
} catch (err) {
@@ -200,7 +187,7 @@ const imageReq = {
},
uploadWorldImageFileFinish(params) {
return window.API.call(
return request(
`file/${params.fileId}/${params.fileVersion}/file/finish`,
{
method: 'PUT',
@@ -214,14 +201,13 @@ const imageReq = {
json,
params
};
// window.API.$emit('WORLDIMAGE:FILEFINISH', args);
return args;
});
},
async uploadWorldImageSigStart(params) {
try {
return await window.API.call(
return await request(
`file/${params.fileId}/${params.fileVersion}/signature/start`,
{
method: 'PUT'
@@ -231,7 +217,6 @@ const imageReq = {
json,
params
};
// window.API.$emit('WORLDIMAGE:SIGSTART', args);
return args;
});
} catch (err) {
@@ -242,7 +227,7 @@ const imageReq = {
},
uploadWorldImageSigFinish(params) {
return window.API.call(
return request(
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
{
method: 'PUT',
@@ -256,13 +241,13 @@ const imageReq = {
json,
params
};
// window.API.$emit('WORLDIMAGE:SIGFINISH', args);
return args;
});
},
setWorldImage(params) {
return window.API.call(`worlds/${params.id}`, {
const worldStore = useWorldStore();
return request(`worlds/${params.id}`, {
method: 'PUT',
params
}).then((json) => {
@@ -270,27 +255,25 @@ const imageReq = {
json,
params
};
// window.API.$emit('WORLDIMAGE:SET', args);
window.API.$emit('WORLD', args);
args.ref = worldStore.applyWorld(json);
return args;
});
},
getAvatarImages(params) {
return window.API.call(`file/${params.fileId}`, {
return request(`file/${params.fileId}`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
// window.API.$emit('AVATARIMAGE:GET', args);
return args;
});
},
getWorldImages(params) {
return window.API.call(`file/${params.fileId}`, {
return request(`file/${params.fileId}`, {
method: 'GET',
params
}).then((json) => {
@@ -298,7 +281,6 @@ const imageReq = {
json,
params
};
// window.API.$emit('WORLDIMAGE:GET', args);
return args;
});
}

View File

@@ -1,9 +1,6 @@
/**
* API requests
* Export all API requests from here
*
* "window.API" is used as app.js is a large IIFE, preventing direct API export. No current issues
* Refactoring may be required
*/
import userRequest from './user';
@@ -21,6 +18,7 @@ import inviteMessagesRequest from './inviteMessages';
import imageRequest from './image';
import miscRequest from './misc';
import groupRequest from './group';
import authRequest from './auth';
import inventoryRequest from './inventory';
import propRequest from './prop';
@@ -39,6 +37,7 @@ window.request = {
inviteMessagesRequest,
imageRequest,
miscRequest,
authRequest,
groupRequest,
inventoryRequest,
propRequest
@@ -59,6 +58,7 @@ export {
inviteMessagesRequest,
imageRequest,
miscRequest,
authRequest,
groupRequest,
inventoryRequest,
propRequest

View File

@@ -1,22 +1,22 @@
// #region | API: Instance
import { $app } from '../app';
import { t } from '../plugin';
import { request } from '../service/request';
import { useInstanceStore } from '../stores';
const instanceReq = {
/**
* @param {{worldId: string, instanceId: string}} params
* @returns {Promise<{json: any, params}>}
* @type {import('../types/instance').getInstance}
*/
getInstance(params) {
return window.API.call(
`instances/${params.worldId}:${params.instanceId}`,
{
method: 'GET'
}
).then((json) => {
const instanceStore = useInstanceStore();
return request(`instances/${params.worldId}:${params.instanceId}`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('INSTANCE', args);
args.ref = instanceStore.applyInstance(json);
return args;
});
},
@@ -37,7 +37,8 @@ const instanceReq = {
* @returns {Promise<{json: any, params}>}
*/
createInstance(params) {
return window.API.call('instances', {
const instanceStore = useInstanceStore();
return request('instances', {
method: 'POST',
params
}).then((json) => {
@@ -45,7 +46,7 @@ const instanceReq = {
json,
params
};
window.API.$emit('INSTANCE', args);
args.ref = instanceStore.applyInstance(json);
return args;
});
},
@@ -59,7 +60,7 @@ const instanceReq = {
if (instance.shortName) {
params.shortName = instance.shortName;
}
return window.API.call(
return request(
`instances/${instance.worldId}:${instance.instanceId}/shortName`,
{
method: 'GET',
@@ -80,14 +81,15 @@ const instanceReq = {
* @returns {Promise<{json: any, params}>}
*/
getInstanceFromShortName(params) {
return window.API.call(`instances/s/${params.shortName}`, {
const instanceStore = useInstanceStore();
return request(`instances/s/${params.shortName}`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('INSTANCE', args);
args.ref = instanceStore.applyInstance(json);
return args;
});
},
@@ -105,7 +107,7 @@ const instanceReq = {
if (instance.shortName) {
params.shortName = instance.shortName;
}
return window.API.call(
return request(
`invite/myself/to/${instance.worldId}:${instance.instanceId}`,
{
method: 'POST',
@@ -121,20 +123,19 @@ const instanceReq = {
})
.catch((err) => {
if (err?.error?.message) {
window.$app.$message({
$app.$message({
message: err.error.message,
type: 'error'
});
throw err;
}
window.$app.$message({
message: window.$t('message.instance.not_allowed'),
$app.$message({
message: t('message.instance.not_allowed'),
type: 'error'
});
throw err;
});
}
};
// #endregion
export default instanceReq;

View File

@@ -1,10 +1,12 @@
import { request } from '../service/request';
const inventoryReq = {
/**
* @param {{ inventoryId: string, userId: string }} params
* @returns {Promise<{json: any, params}>}
*/
getUserInventoryItem(params) {
return window.API.call(
return request(
`user/${params.userId}/inventory/${params.inventoryId}`,
{
method: 'GET'
@@ -23,7 +25,7 @@ const inventoryReq = {
* @returns {Promise<{json: any, params}>}
*/
getInventoryItem(params) {
return window.API.call(`inventory/${params.inventoryId}`, {
return request(`inventory/${params.inventoryId}`, {
method: 'GET',
params
}).then((json) => {
@@ -40,7 +42,7 @@ const inventoryReq = {
* @returns {Promise<{json: any, params}>}
*/
getInventoryItems(params) {
return window.API.call('inventory', {
return request('inventory', {
method: 'GET',
params
}).then((json) => {
@@ -57,7 +59,7 @@ const inventoryReq = {
* @returns {Promise<{json: any, params}>}
*/
consumeInventoryBundle(params) {
return window.API.call(`inventory/${params.inventoryId}/consume`, {
return request(`inventory/${params.inventoryId}/consume`, {
method: 'PUT',
params
}).then((json) => {
@@ -74,13 +76,10 @@ const inventoryReq = {
* @returns {Promise<{json: any, params}>}
*/
getInventoryTemplate(params) {
return window.API.call(
`inventory/template/${params.inventoryTemplateId}`,
{
method: 'GET',
params
}
).then((json) => {
return request(`inventory/template/${params.inventoryTemplateId}`, {
method: 'GET',
params
}).then((json) => {
const args = {
json,
params

View File

@@ -1,30 +1,28 @@
// #region | App: Invite Messages
import { request } from '../service/request';
import { useUserStore } from '../stores';
function getCurrentUserId() {
return useUserStore().currentUser.id;
}
const inviteMessagesReq = {
refreshInviteMessageTableData(messageType) {
return window.API.call(
`message/${window.API.currentUser.id}/${messageType}`,
{
method: 'GET'
}
).then((json) => {
return request(`message/${getCurrentUserId()}/${messageType}`, {
method: 'GET'
}).then((json) => {
const args = {
json,
messageType
};
window.API.$emit(`INVITE:${messageType.toUpperCase()}`, args);
return args;
});
},
editInviteMessage(params, messageType, slot) {
return window.API.call(
`message/${window.API.currentUser.id}/${messageType}/${slot}`,
{
method: 'PUT',
params
}
).then((json) => {
return request(`message/${getCurrentUserId()}/${messageType}/${slot}`, {
method: 'PUT',
params
}).then((json) => {
const args = {
json,
params,
@@ -36,6 +34,4 @@ const inviteMessagesReq = {
}
};
// #endregion
export default inviteMessagesReq;

View File

@@ -1,6 +1,13 @@
import { request } from '../service/request';
import { useUserStore } from '../stores';
function getCurrentUserId() {
return useUserStore().currentUser.id;
}
const miscReq = {
getBundles(fileId) {
return window.API.call(`file/${fileId}`, {
return request(`file/${fileId}`, {
method: 'GET'
}).then((json) => {
const args = {
@@ -11,7 +18,7 @@ const miscReq = {
},
saveNote(params) {
return window.API.call('userNotes', {
return request('userNotes', {
method: 'POST',
params
}).then((json) => {
@@ -19,7 +26,6 @@ const miscReq = {
json,
params
};
// window.API.$emit('NOTE', args);
return args;
});
},
@@ -34,7 +40,7 @@ const miscReq = {
* @return { Promise<{json: any, params}> }
*/
reportUser(params) {
return window.API.call(`feedback/${params.userId}/user`, {
return request(`feedback/${params.userId}/user`, {
method: 'POST',
params: {
contentType: params.contentType,
@@ -46,7 +52,6 @@ const miscReq = {
json,
params
};
// window.API.$emit('FEEDBACK:REPORT:USER', args);
return args;
});
},
@@ -59,7 +64,7 @@ const miscReq = {
* @return { Promise<{json: any, params}> }
*/
getFileAnalysis(params) {
return window.API.call(
return request(
`analysis/${params.fileId}/${params.version}/${params.variant}`,
{
method: 'GET'
@@ -69,19 +74,17 @@ const miscReq = {
json,
params
};
// window.API.$emit('FILE:ANALYSIS', args);
return args;
});
},
getVRChatCredits() {
return window.API.call(`user/${window.API.currentUser.id}/balance`, {
return request(`user/${getCurrentUserId()}/balance`, {
method: 'GET'
}).then((json) => {
const args = {
json
};
// window.API.$emit('VRCCREDITS', args);
return args;
});
},
@@ -94,7 +97,7 @@ const miscReq = {
* @returns {Promise<{json: any, params}>}
*/
closeInstance(params) {
return window.API.call(`instances/${params.location}`, {
return request(`instances/${params.location}`, {
method: 'DELETE',
params: {
hardClose: params.hardClose ?? false
@@ -104,7 +107,6 @@ const miscReq = {
json,
params
};
window.API.$emit('INSTANCE:CLOSE', args);
return args;
});
},
@@ -116,8 +118,8 @@ const miscReq = {
* @returns {Promise<{json: any, params}>}
*/
deleteWorldPersistData(params) {
return window.API.call(
`users/${window.API.currentUser.id}/${params.worldId}/persist`,
return request(
`users/${getCurrentUserId()}/${params.worldId}/persist`,
{
method: 'DELETE'
}
@@ -126,7 +128,6 @@ const miscReq = {
json,
params
};
window.API.$emit('WORLD:PERSIST:DELETE', args);
return args;
});
},
@@ -138,8 +139,8 @@ const miscReq = {
* @returns {Promise<{json: any, params}>}
*/
hasWorldPersistData(params) {
return window.API.call(
`users/${window.API.currentUser.id}/${params.worldId}/persist/exists`,
return request(
`users/${getCurrentUserId()}/${params.worldId}/persist/exists`,
{
method: 'GET'
}
@@ -148,47 +149,41 @@ const miscReq = {
json,
params
};
window.API.$emit('WORLD:PERSIST:HAS', args);
return args;
});
},
updateBadge(params) {
return window.API.call(
`users/${window.API.currentUser.id}/badges/${params.badgeId}`,
{
method: 'PUT',
params: {
userId: window.API.currentUser.id,
badgeId: params.badgeId,
hidden: params.hidden,
showcased: params.showcased
}
return request(`users/${getCurrentUserId()}/badges/${params.badgeId}`, {
method: 'PUT',
params: {
userId: getCurrentUserId(),
badgeId: params.badgeId,
hidden: params.hidden,
showcased: params.showcased
}
).then((json) => {
}).then((json) => {
const args = {
json,
params
};
// window.API.$emit('BADGE:UPDATE', args);
return args;
});
},
getVisits() {
return window.API.call('visits', {
return request('visits', {
method: 'GET'
}).then((json) => {
const args = {
json
};
// window.API.$emit('VISITS', args);
return args;
});
},
deleteFile(fileId) {
return window.API.call(`file/${fileId}`, {
return request(`file/${fileId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
@@ -207,7 +202,7 @@ const miscReq = {
// * @returns {Promise<{json: any, params}>}
// */
// sendBoop(params) {
// return window.API.call(`users/${params.userId}/boop`, {
// return request(`users/${params.userId}/boop`, {
// method: 'POST',
// params
// }).then((json) => {

View File

@@ -1,4 +1,9 @@
// #region | API: Notification
import { request } from '../service/request';
import { useGroupStore, useNotificationStore } from '../stores';
function getGalleryStore() {
return useGroupStore();
}
const notificationReq = {
/** @typedef {{
@@ -17,7 +22,7 @@ const notificationReq = {
* @returns {Promise<{json: any, params}>}
*/
getNotifications(params) {
return window.API.call('auth/user/notifications', {
return request('auth/user/notifications', {
method: 'GET',
params
}).then((json) => {
@@ -25,13 +30,13 @@ const notificationReq = {
json,
params
};
window.API.$emit('NOTIFICATION:LIST', args);
return args;
});
},
getHiddenFriendRequests(params) {
return window.API.call('auth/user/notifications', {
return request('auth/user/notifications', {
method: 'GET',
params: {
type: 'friendRequest',
@@ -43,13 +48,12 @@ const notificationReq = {
json,
params
};
window.API.$emit('NOTIFICATION:LIST:HIDDEN', args);
return args;
});
},
getNotificationsV2(params) {
return window.API.call('notifications', {
return request('notifications', {
method: 'GET',
params
}).then((json) => {
@@ -57,7 +61,6 @@ const notificationReq = {
json,
params
};
window.API.$emit('NOTIFICATION:V2:LIST', args);
return args;
});
},
@@ -80,7 +83,7 @@ const notificationReq = {
* @return { Promise<{json: any, params}> }
*/
sendInvite(params, receiverUserId) {
return window.API.call(`invite/${receiverUserId}`, {
return request(`invite/${receiverUserId}`, {
method: 'POST',
params
}).then((json) => {
@@ -89,28 +92,26 @@ const notificationReq = {
params,
receiverUserId
};
window.API.$emit('NOTIFICATION:INVITE:SEND', args);
return args;
});
},
sendInvitePhoto(params, receiverUserId) {
return window.API.call(`invite/${receiverUserId}/photo`, {
return request(`invite/${receiverUserId}/photo`, {
uploadImageLegacy: true,
postData: JSON.stringify(params),
imageData: window.$app.uploadImage
imageData: getGalleryStore().uploadImage
}).then((json) => {
const args = {
json,
params,
receiverUserId
};
window.API.$emit('NOTIFICATION:INVITE:PHOTO:SEND', args);
return args;
});
},
sendRequestInvite(params, receiverUserId) {
return window.API.call(`requestInvite/${receiverUserId}`, {
return request(`requestInvite/${receiverUserId}`, {
method: 'POST',
params
}).then((json) => {
@@ -119,29 +120,27 @@ const notificationReq = {
params,
receiverUserId
};
window.API.$emit('NOTIFICATION:REQUESTINVITE:SEND', args);
return args;
});
},
sendRequestInvitePhoto(params, receiverUserId) {
return window.API.call(`requestInvite/${receiverUserId}/photo`, {
return request(`requestInvite/${receiverUserId}/photo`, {
uploadImageLegacy: true,
postData: JSON.stringify(params),
imageData: window.$app.uploadImage
imageData: getGalleryStore().uploadImage
}).then((json) => {
const args = {
json,
params,
receiverUserId
};
window.API.$emit('NOTIFICATION:REQUESTINVITE:PHOTO:SEND', args);
return args;
});
},
sendInviteResponse(params, inviteId) {
return window.API.call(`invite/${inviteId}/response`, {
return request(`invite/${inviteId}/response`, {
method: 'POST',
params,
inviteId
@@ -151,16 +150,15 @@ const notificationReq = {
params,
inviteId
};
window.API.$emit('INVITE:RESPONSE:SEND', args);
return args;
});
},
sendInviteResponsePhoto(params, inviteId) {
return window.API.call(`invite/${inviteId}/response/photo`, {
return request(`invite/${inviteId}/response/photo`, {
uploadImageLegacy: true,
postData: JSON.stringify(params),
imageData: window.$app.uploadImage,
imageData: getGalleryStore().uploadImage,
inviteId
}).then((json) => {
const args = {
@@ -168,7 +166,6 @@ const notificationReq = {
params,
inviteId
};
window.API.$emit('INVITE:RESPONSE:PHOTO:SEND', args);
return args;
});
},
@@ -178,7 +175,7 @@ const notificationReq = {
* @return { Promise<{json: any, params}> }
*/
acceptFriendRequestNotification(params) {
return window.API.call(
return request(
`auth/user/notifications/${params.notificationId}/accept`,
{
method: 'PUT'
@@ -189,13 +186,13 @@ const notificationReq = {
json,
params
};
window.API.$emit('NOTIFICATION:ACCEPT', args);
useNotificationStore().handleNotificationAccept(args);
return args;
})
.catch((err) => {
// if friend request could not be found, delete it
if (err && err.message && err.message.includes('404')) {
window.API.$emit('NOTIFICATION:HIDE', { params });
useNotificationStore().handleNotificationHide({ params });
}
});
},
@@ -205,7 +202,7 @@ const notificationReq = {
* @return { Promise<{json: any, params}> }
*/
hideNotification(params) {
return window.API.call(
return request(
`auth/user/notifications/${params.notificationId}/hide`,
{
method: 'PUT'
@@ -215,13 +212,11 @@ const notificationReq = {
json,
params
};
window.API.$emit('NOTIFICATION:HIDE', args);
useNotificationStore().handleNotificationHide(args);
return args;
});
},
// ------------------- need to test -------------------
/**
* @param {{
* notificationId: string,
@@ -231,32 +226,14 @@ const notificationReq = {
* @return { Promise<{json: any, params}> }
*/
sendNotificationResponse(params) {
return window.API.call(
`notifications/${params.notificationId}/respond`,
{
method: 'POST',
params
}
)
.then((json) => {
const args = {
json,
params
};
window.API.$emit('NOTIFICATION:RESPONSE', args);
return args;
})
.catch((err) => {
// TODO: need to test
// something went wrong, lets assume it's already expired
window.API.$emit('NOTIFICATION:HIDE', { params });
notificationReq.hideNotificationV2(params.notificationId);
throw err;
});
return request(`notifications/${params.notificationId}/respond`, {
method: 'POST',
params
});
},
// use in sendNotificationResponse
hideNotificationV2(notificationId) {
return window.API.call(`notifications/${notificationId}`, {
return request(`notifications/${notificationId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
@@ -265,16 +242,12 @@ const notificationReq = {
notificationId
}
};
// useless
// window.API.$emit('NOTIFICATION:V2:HIDE', args);
return args;
});
}
// ------------------ look like no place use these requests ------------------
// sendInviteGalleryPhoto(params, receiverUserId) {
// return window.API.call(`invite/${receiverUserId}/photo`, {
// return request(`invite/${receiverUserId}/photo`, {
// method: 'POST',
// params
// }).then((json) => {
@@ -283,13 +256,13 @@ const notificationReq = {
// params,
// receiverUserId
// };
// window.API.$emit('NOTIFICATION:INVITE:GALLERYPHOTO:SEND', args);
// API.$emit('NOTIFICATION:INVITE:GALLERYPHOTO:SEND', args);
// return args;
// });
// },
// API.clearNotifications = function () {
// return this.call('auth/user/notifications/clear', {
// return request('auth/user/notifications/clear', {
// method: 'PUT'
// }).then((json) => {
// var args = {

View File

@@ -1,14 +1,13 @@
// #region | API: PlayerModeration
import { request } from '../service/request';
const playerModerationReq = {
getPlayerModerations() {
return window.API.call('auth/user/playermoderations', {
return request('auth/user/playermoderations', {
method: 'GET'
}).then((json) => {
const args = {
json
};
window.API.$emit('PLAYER-MODERATION:LIST', args);
return args;
});
},
@@ -19,7 +18,7 @@ const playerModerationReq = {
*/
// old-way: POST auth/user/blocks {blocked:userId}
sendPlayerModeration(params) {
return window.API.call('auth/user/playermoderations', {
return request('auth/user/playermoderations', {
method: 'POST',
params
}).then((json) => {
@@ -27,7 +26,6 @@ const playerModerationReq = {
json,
params
};
// window.API.$emit('PLAYER-MODERATION:SEND', args);
return args;
});
},
@@ -38,7 +36,7 @@ const playerModerationReq = {
*/
// old-way: PUT auth/user/unblocks {blocked:userId}
deletePlayerModeration(params) {
return window.API.call('auth/user/unplayermoderate', {
return request('auth/user/unplayermoderate', {
method: 'PUT',
params
}).then((json) => {
@@ -46,11 +44,9 @@ const playerModerationReq = {
json,
params
};
window.API.$emit('PLAYER-MODERATION:DELETE', args);
return args;
});
}
};
// #endregion
export default playerModerationReq;

View File

@@ -1,10 +1,12 @@
import { request } from '../service/request';
const propReq = {
/**
* @param {{ propId: string }} params
* @returns {Promise<{json: any, params}>}
*/
getProp(params) {
return window.API.call(`props/${params.propId}`, {
return request(`props/${params.propId}`, {
method: 'GET',
params
}).then((json) => {

View File

@@ -1,13 +1,19 @@
// #region | API: User
import { request } from '../service/request';
import { useUserStore } from '../stores';
function getCurrentUserId() {
return useUserStore().currentUser.id;
}
const userReq = {
/**
* Fetch user from API.
* @param {{ userId: string }} params identifier of registered user
* @returns {Promise<{json: any, params}>}
* identifier of registered user
* @type {import('../types/user').getUser}
*/
getUser(params) {
return window.API.call(`users/${params.userId}`, {
const userStore = useUserStore();
return request(`users/${params.userId}`, {
method: 'GET'
}).then((json) => {
if (!json) {
@@ -19,7 +25,7 @@ const userReq = {
json,
params
};
window.API.$emit('USER', args);
args.ref = userStore.applyUser(json);
return args;
});
},
@@ -30,10 +36,17 @@ const userReq = {
* @returns {Promise<{json: any, params}>}
*/
getCachedUser(params) {
const userStore = useUserStore();
return new Promise((resolve, reject) => {
const ref = window.API.cachedUsers.get(params.userId);
const ref = userStore.cachedUsers.get(params.userId);
if (typeof ref === 'undefined') {
userReq.getUser(params).catch(reject).then(resolve);
userReq
.getUser(params)
.catch(reject)
.then((args) => {
args.ref = userStore.applyUser(args.json);
resolve(args);
});
} else {
resolve({
cache: true,
@@ -59,7 +72,7 @@ const userReq = {
* @returns {Promise<{json: any, params}>}
*/
getUsers(params) {
return window.API.call('users', {
return request('users', {
method: 'GET',
params
}).then((json) => {
@@ -67,7 +80,6 @@ const userReq = {
json,
params
};
window.API.$emit('USER:LIST', args);
return args;
});
},
@@ -77,7 +89,8 @@ const userReq = {
* @returns {Promise<{json: any, params}>}
*/
addUserTags(params) {
return window.API.call(`users/${window.API.currentUser.id}/addTags`, {
const userStore = useUserStore();
return request(`users/${getCurrentUserId()}/addTags`, {
method: 'POST',
params
}).then((json) => {
@@ -85,7 +98,7 @@ const userReq = {
json,
params
};
window.API.$emit('USER:CURRENT:SAVE', args);
userStore.applyCurrentUser(json);
return args;
});
},
@@ -95,18 +108,16 @@ const userReq = {
* @returns {Promise<{json: any, params}>}
*/
removeUserTags(params) {
return window.API.call(
`users/${window.API.currentUser.id}/removeTags`,
{
method: 'POST',
params
}
).then((json) => {
const userStore = useUserStore();
return request(`users/${getCurrentUserId()}/removeTags`, {
method: 'POST',
params
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('USER:CURRENT:SAVE', args);
userStore.applyCurrentUser(json);
return args;
});
},
@@ -116,7 +127,7 @@ const userReq = {
* @returns {Promise<{json: any, params}>}
*/
getUserFeedback(params) {
return window.API.call(`users/${params.userId}/feedback`, {
return request(`users/${params.userId}/feedback`, {
method: 'GET',
params: {
n: 100
@@ -126,7 +137,6 @@ const userReq = {
json,
params
};
// window.API.$emit('USER:FEEDBACK', args);
return args;
});
},
@@ -144,15 +154,16 @@ const userReq = {
* @returns {Promise<{json: any, params}>}
*/
saveCurrentUser(params) {
return window.API.call(`users/${window.API.currentUser.id}`, {
const userStore = useUserStore();
return request(`users/${getCurrentUserId()}`, {
method: 'PUT',
params
}).then((json) => {
var args = {
const args = {
json,
params
};
window.API.$emit('USER:CURRENT:SAVE', args);
userStore.applyCurrentUser(json);
return args;
});
},
@@ -162,7 +173,7 @@ const userReq = {
* @returns {Promise<{json: any, params}>}
*/
getUserNotes(params) {
return window.API.call(`userNotes`, {
return request(`userNotes`, {
method: 'GET',
params
}).then((json) => {
@@ -170,11 +181,9 @@ const userReq = {
json,
params
};
// window.API.$emit('USER:NOTES', args);
return args;
});
}
};
// #endregion
export default userReq;

View File

@@ -1,8 +1,8 @@
// #region | App: VRCPlus Icons
import { request } from '../service/request';
const VRCPlusIconsReq = {
getFileList(params) {
return window.API.call('files', {
return request('files', {
method: 'GET',
params
}).then((json) => {
@@ -10,7 +10,18 @@ const VRCPlusIconsReq = {
json,
params
};
window.API.$emit('FILES:LIST', args);
return args;
});
},
deleteFile(fileId) {
return request(`file/${fileId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
fileId
};
return args;
});
},
@@ -19,7 +30,7 @@ const VRCPlusIconsReq = {
const params = {
tag: 'icon'
};
return window.API.call('file/image', {
return request('file/image', {
uploadImage: true,
matchingDimensions: true,
postData: JSON.stringify(params),
@@ -29,15 +40,12 @@ const VRCPlusIconsReq = {
json,
params
};
window.API.$emit('VRCPLUSICON:ADD', args);
return args;
});
}
// unused
// images.pug line 63
// deleteFileVersion(params) {
// return window.API.call(`file/${params.fileId}/${params.version}`, {
// return request(`file/${params.fileId}/${params.version}`, {
// method: 'DELETE'
// }).then((json) => {
// const args = {
@@ -49,6 +57,4 @@ const VRCPlusIconsReq = {
// }
};
// #endregion
export default VRCPlusIconsReq;

View File

@@ -1,9 +1,15 @@
import { request } from '../service/request';
import { useUserStore } from '../stores';
function getCurrentUserId() {
return useUserStore().currentUser.id;
}
const vrcPlusImageReq = {
uploadGalleryImage(imageData) {
const params = {
tag: 'gallery'
};
return window.API.call('file/image', {
return request('file/image', {
uploadImage: true,
matchingDimensions: false,
postData: JSON.stringify(params),
@@ -13,13 +19,12 @@ const vrcPlusImageReq = {
json,
params
};
window.API.$emit('GALLERYIMAGE:ADD', args);
return args;
});
},
uploadSticker(imageData, params) {
return window.API.call('file/image', {
return request('file/image', {
uploadImage: true,
matchingDimensions: true,
postData: JSON.stringify(params),
@@ -29,13 +34,12 @@ const vrcPlusImageReq = {
json,
params
};
window.API.$emit('STICKER:ADD', args);
return args;
});
},
getPrints(params) {
return window.API.call(`prints/user/${window.API.currentUser.id}`, {
return request(`prints/user/${getCurrentUserId()}`, {
method: 'GET',
params
}).then((json) => {
@@ -48,20 +52,19 @@ const vrcPlusImageReq = {
},
deletePrint(printId) {
return window.API.call(`prints/${printId}`, {
return request(`prints/${printId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
printId
};
// window.API.$emit('PRINT:DELETE', args);
return args;
});
},
uploadPrint(imageData, cropWhiteBorder, params) {
return window.API.call('prints', {
return request('prints', {
uploadImagePrint: true,
cropWhiteBorder,
postData: JSON.stringify(params),
@@ -71,26 +74,24 @@ const vrcPlusImageReq = {
json,
params
};
window.API.$emit('PRINT:ADD', args);
return args;
});
},
getPrint(params) {
return window.API.call(`prints/${params.printId}`, {
return request(`prints/${params.printId}`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('PRINT', args);
return args;
});
},
uploadEmoji(imageData, params) {
return window.API.call('file/image', {
return request('file/image', {
uploadImage: true,
matchingDimensions: true,
postData: JSON.stringify(params),
@@ -100,15 +101,12 @@ const vrcPlusImageReq = {
json,
params
};
window.API.$emit('EMOJI:ADD', args);
return args;
});
}
// ----------- no place uses this function ------------
// editPrint(params) {
// return window.API.call(`prints/${params.printId}`, {
// return request(`prints/${params.printId}`, {
// method: 'POST',
// params
// }).then((json) => {
@@ -116,7 +114,7 @@ const vrcPlusImageReq = {
// json,
// params
// };
// window.API.$emit('PRINT:EDIT', args);
// API.$emit('PRINT:EDIT', args);
// return args;
// });
// },

View File

@@ -1,4 +1,5 @@
// #region | API: World
import { request } from '../service/request';
import { useWorldStore } from '../stores';
const worldReq = {
/**
@@ -6,14 +7,15 @@ const worldReq = {
* @returns {Promise<{json: any, params}>}
*/
getWorld(params) {
return window.API.call(`worlds/${params.worldId}`, {
const worldStore = useWorldStore();
return request(`worlds/${params.worldId}`, {
method: 'GET'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('WORLD', args);
args.ref = worldStore.applyWorld(json);
return args;
});
},
@@ -23,10 +25,17 @@ const worldReq = {
* @returns {Promise<{json: any, params}>}
*/
getCachedWorld(params) {
const worldStore = useWorldStore();
return new Promise((resolve, reject) => {
const ref = window.API.cachedWorlds.get(params.worldId);
const ref = worldStore.cachedWorlds.get(params.worldId);
if (typeof ref === 'undefined') {
worldReq.getWorld(params).catch(reject).then(resolve);
worldReq
.getWorld(params)
.catch(reject)
.then((args) => {
args.ref = worldStore.applyWorld(args.json);
resolve(args);
});
} else {
resolve({
cache: true,
@@ -57,11 +66,12 @@ const worldReq = {
* @returns {Promise<{json: any, params, option}>}
*/
getWorlds(params, option) {
const worldStore = useWorldStore();
let endpoint = 'worlds';
if (typeof option !== 'undefined') {
endpoint = `worlds/${option}`;
}
return window.API.call(endpoint, {
return request(endpoint, {
method: 'GET',
params
}).then((json) => {
@@ -70,7 +80,9 @@ const worldReq = {
params,
option
};
window.API.$emit('WORLD:LIST', args);
for (const json of args.json) {
worldStore.applyWorld(json);
}
return args;
});
},
@@ -79,14 +91,13 @@ const worldReq = {
* @returns {Promise<{json: any, params}>}
*/
deleteWorld(params) {
return window.API.call(`worlds/${params.worldId}`, {
return request(`worlds/${params.worldId}`, {
method: 'DELETE'
}).then((json) => {
const args = {
json,
params
};
window.API.$emit('WORLD:DELETE', args);
return args;
});
},
@@ -96,7 +107,8 @@ const worldReq = {
* @returns {Promise<{json: any, params}>}
*/
saveWorld(params) {
return window.API.call(`worlds/${params.id}`, {
const worldStore = useWorldStore();
return request(`worlds/${params.id}`, {
method: 'PUT',
params
}).then((json) => {
@@ -104,7 +116,7 @@ const worldReq = {
json,
params
};
window.API.$emit('WORLD:SAVE', args);
args.ref = worldStore.applyWorld(json);
return args;
});
},
@@ -114,7 +126,8 @@ const worldReq = {
* @returns {Promise<{json: any, params}>}
*/
publishWorld(params) {
return window.API.call(`worlds/${params.worldId}/publish`, {
const worldStore = useWorldStore();
return request(`worlds/${params.worldId}/publish`, {
method: 'PUT',
params
}).then((json) => {
@@ -122,7 +135,7 @@ const worldReq = {
json,
params
};
window.API.$emit('WORLD:SAVE', args);
args.ref = worldStore.applyWorld(json);
return args;
});
},
@@ -132,7 +145,8 @@ const worldReq = {
* @returns {Promise<{json: any, params}>}
*/
unpublishWorld(params) {
return window.API.call(`worlds/${params.worldId}/publish`, {
const worldStore = useWorldStore();
return request(`worlds/${params.worldId}/publish`, {
method: 'DELETE',
params
}).then((json) => {
@@ -140,12 +154,10 @@ const worldReq = {
json,
params
};
window.API.$emit('WORLD:SAVE', args);
args.ref = worldStore.applyWorld(json);
return args;
});
}
};
// #endregion
export default worldReq;

14159
src/app.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +1,71 @@
doctype html
#x-app.x-app(@dragenter.prevent @dragover.prevent @drop.prevent)
LoginPage(v-if="!API.isLoggedIn" v-bind="loginPageBind" v-on="loginPageEvent")
//- ### Login ###
Login(v-if='!watchState.isLoggedIn')
VRCXUpdateDialog(v-bind="vrcxUpdateDialogBind" v-on="vrcxUpdateDialogEvent")
VRCXUpdateDialog
//- menu
.x-menu-container
//- download progress, update pending
.pending-update(v-if='updateInProgress' @click='showVRCXUpdateDialog')
el-progress(
type='circle'
width='50'
stroke-width='3'
:percentage='updateProgress'
:format='updateProgressText')
.pending-update(v-else-if='pendingVRCXUpdate || pendingVRCXInstall')
el-button(
type='default'
@click='showVRCXUpdateDialog'
size='mini'
icon='el-icon-download'
circle
style='font-size: 14px; height: 50px; width: 50px')
template(v-if='watchState.isLoggedIn')
//- ### Menu ###
NavMenu
NavMenu(ref='menu' @select='selectMenu' :menu-active-index='menuActiveIndex')
//- ### Sidebar ###
Sidebar
//- ### Tabs ###
template(v-if='API.isLoggedIn')
FeedTab(v-bind='feedTabBind' v-on='feedTabEvent')
//- ### Tabs ###
Feed
GameLogTab(v-bind='gameLogTabBind' v-on='gameLogTabEvent')
GameLog
PlayerListTab(v-bind='playerListTabBind' v-on='playerListTabEvent')
PlayerList
SearchTab(v-bind='searchTabBind' v-on='searchTabEvent')
Search
FavoritesTab(v-bind='favoritesTabBind' v-on='favoritesTabEvent')
Favorites
FriendLogTab(v-bind='friendLogTabBind')
FriendLog
ModerationTab(v-bind='moderationTabBind')
Moderation
NotificationTab(v-bind='notificationTabBind' v-on='notificationTabEvent')
Notification
ProfileTab(v-bind='profileTabBind' v-on='profileTabEvent')
FriendList
FriendListTab(v-bind='friendsListTabBind' v-on='friendsListTabEvent')
Charts
KeepAlive
ChartsTab(v-if='menuActiveIndex === "charts"' v-bind='chartsTabBind' v-on='chartsTabEvent')
Profile
//- settings
include ./mixins/tabs/settings.pug
+settingsTab
SideBar(v-bind='sideBarTabBind' v-on='sideBarTabEvent')
Settings
//- ## Dialogs ## -\\
include ./mixins/dialogs/dialogs.pug
+dialogs
UserDialog
//- 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")
WorldDialog
AvatarDialog
GroupDialog
GalleryDialog
FullscreenImageDialog
PreviousInstancesInfoDialog
LaunchDialog
LaunchOptionsDialog
FriendImportDialog
WorldImportDialog
AvatarImportDialog
ChooseFavoriteGroupDialog
EditInviteMessageDialog
VRChatConfigDialog
PrimaryPasswordDialog

View File

@@ -8,6 +8,9 @@
// For a copy, see <https://opensource.org/licenses/MIT>.
//
@use "./assets/scss/flags.scss";
@use "./assets/scss/animated-emoji.scss";
@import '~animate.css/animate.min.css';
@import '~noty/lib/noty.css';
@import '~element-ui/lib/theme-chalk/index.css';
@@ -299,6 +302,7 @@ hr.x-vertical-divider {
flex-direction: column;
background: #f8f8f8;
padding: 5px;
order: 99;
}
.el-popper.x-quick-search {

View File

@@ -2,7 +2,7 @@
* VRCX Dark-Vanilla theme by MintLily
* https://github.com/MintLily/Dark-Vanilla
*/
@import 'theme.dark';
@use 'theme.dark';
:root {
--ThemeName: 'Dark Vanilla';
--ThemeVersion: 'v1.7';

View File

@@ -2,7 +2,7 @@
* VRCX Material-You-like theme by Kamiya
* https://github.com/kamiya10/VRCX-theme
*/
@import 'theme.dark';
@use 'theme.dark';
@import url('https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;600&family=Noto+Sans+TC:wght@300;400;500&family=Noto+Sans+SC:wght@300;400;500&family=Noto+Sans+JP:wght@300;400;500&family=Roboto&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');
@@ -653,6 +653,8 @@ input[type='number'],
border-left: 1px solid rgb(var(--md-sys-color-surface-variant));
*/
padding: 8px 10px;
display: flex;
align-items: center;
}
/* ---------- Switch ---------- */
@@ -1083,8 +1085,8 @@ input[type='number'],
border-radius: 8px;
height: 28px;
padding: 0 12px;
margin-top: 8px !important;
margin-right: 8px !important;
// margin-top: 8px !important;
// margin-right: 8px !important;
color: rgb(var(--md-sys-color-on-surface-variant));
font-family: var(--md-sys-typescale-label-large-font);
line-height: 28px;
@@ -1380,7 +1382,7 @@ img.x-link.el-popover__reference {
*:not(.x-user-dialog, .x-world-dialog, .x-avatar-dialog, .x-group-dialog)
> .el-dialog:not([aria-label*='Notification Position']):not(
[aria-label*='Launch']
):not([aria-label*='VRCX Updater']) {
):not([aria-label*='VRCX Updater']):not([aria-label*='Social Status']) {
max-width: 1125px !important;
width: 1125px !important;
}
@@ -2008,6 +2010,7 @@ i.x-user-status {
.el-collapse-item .el-tag--mini {
background-color: transparent;
border: transparent;
padding-top: 6px;
}
.simple-switch {
justify-content: space-between;
@@ -2071,4 +2074,12 @@ i.x-user-status {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.el-dialog[aria-label='Launch'] .el-form .el-form-item__content {
display: flex;
align-items: center;
}
.el-dialog[aria-label='Launch'] .el-form > .el-form-item:nth-child(2) .el-form-item__label {
display: flex;
align-items: center;
}

View File

@@ -2,7 +2,7 @@
* VRCX Pink theme by Kamiya
* https://github.com/kamiya10/VRCX-theme
*/
@import 'theme.dark';
@use 'theme.dark';
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
:root {
--theme: #dfa2a2;
@@ -70,7 +70,7 @@ textarea {
.el-textarea .el-input__count,
.el-textarea__inner,
.x-menu-container {
background-color: var(--lighter-bg);
background-color: var(--lighter-bg) !important;
}
.el-color-picker__panel {
border-color: var(--lighter-bg);

76
src/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,76 @@
import '@fontsource/noto-sans-kr';
import '@fontsource/noto-sans-jp';
import '@fontsource/noto-sans-sc';
import '@fontsource/noto-sans-tc';
import '@infolektuell/noto-color-emoji';
import Vue from 'vue';
import { PiniaVuePlugin } from 'pinia';
import { DataTables } from 'vue-data-tables';
import VueLazyload from 'vue-lazyload';
import configRepository from './service/config';
import vrcxJsonStorage from './service/jsonStorage';
import {
changeAppDarkStyle,
changeAppThemeStyle,
refreshCustomCss,
refreshCustomScript,
systemIsDarkMode
} from './shared/utils';
import { i18n } from './plugin';
configRepository.init();
i18n.locale = await configRepository.getString('VRCX_appLanguage', 'en');
AppApi.SetUserAgent();
const initThemeMode = await configRepository.getString(
'VRCX_ThemeMode',
'system'
);
let isDarkMode;
if (initThemeMode === 'light') {
isDarkMode = false;
} else if (initThemeMode === 'system') {
isDarkMode = systemIsDarkMode();
} else {
isDarkMode = true;
}
changeAppDarkStyle(isDarkMode);
changeAppThemeStyle(initThemeMode);
refreshCustomCss();
refreshCustomScript();
Vue.use(PiniaVuePlugin);
Vue.use(DataTables);
Vue.use(VueLazyload, {
preLoad: 1,
observer: true,
observerOptions: {
rootMargin: '0px',
threshold: 0
},
attempt: 3
});
new vrcxJsonStorage(VRCXStorage);
// some workaround for failing to get voice list first run
speechSynthesis.getVoices();
if (process.env.NODE_ENV !== 'production') {
Vue.config.errorHandler = function (err, vm, info) {
console.error('Vue Error', err);
console.error('Component', vm);
console.error('Error Info', info);
};
Vue.config.warnHandler = function (msg, vm, trace) {
console.warn('Vue Warning', msg);
console.warn('Component', vm);
console.warn('Trace', trace);
};
}

View File

@@ -1,46 +0,0 @@
import { baseClass, $app, API, $t, $utils } from '../baseClass.js';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {
API.getConfig = function () {
return this.call('config', {
method: 'GET'
}).then((json) => {
var args = {
json
};
this.$emit('CONFIG', args);
return args;
});
};
API.$on('CONFIG', function (args) {
args.ref = this.applyConfig(args.json);
});
API.$on('CONFIG', function (args) {
if (typeof args.ref?.whiteListedAssetUrls !== 'object') {
console.error('Invalid config whiteListedAssetUrls');
}
AppApi.PopulateImageHosts(
JSON.stringify(args.ref.whiteListedAssetUrls)
);
});
API.applyConfig = function (json) {
var ref = {
...json
};
this.cachedConfig = ref;
return ref;
};
}
_data = {};
_methods = {};
}

View File

@@ -1,16 +0,0 @@
import * as workerTimers from 'worker-timers';
import configRepository from '../service/config.js';
import database from '../service/database.js';
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {}
_data = {};
_methods = {};
}

View File

@@ -1,53 +0,0 @@
import { baseClass, $app, API, $t } from './baseClass.js';
export default class extends baseClass {
constructor(_app) {
super(_app);
}
eventHandlers = new Map();
$emit = function (name, ...args) {
if ($app.debug) {
console.log(name, ...args);
}
var handlers = this.eventHandlers.get(name);
if (typeof handlers === 'undefined') {
return;
}
try {
for (var handler of handlers) {
handler.apply(this, args);
}
} catch (err) {
console.error(err);
}
};
$on = function (name, handler) {
var handlers = this.eventHandlers.get(name);
if (typeof handlers === 'undefined') {
handlers = [];
this.eventHandlers.set(name, handlers);
}
handlers.push(handler);
};
$off = function (name, handler) {
var handlers = this.eventHandlers.get(name);
if (typeof handlers === 'undefined') {
return;
}
var { length } = handlers;
for (var i = 0; i < length; ++i) {
if (handlers[i] === handler) {
if (length > 1) {
handlers.splice(i, 1);
} else {
this.eventHandlers.delete(name);
}
break;
}
}
};
}

View File

@@ -1,421 +0,0 @@
import Noty from 'noty';
import security from '../service/security.js';
import configRepository from '../service/config.js';
import { baseClass, $app, API, $t } from './baseClass.js';
/* eslint-disable no-unused-vars */
let webApiService = {};
/* eslint-enable no-unused-vars */
export default class extends baseClass {
constructor(_app, _API, _t, _webApiService) {
super(_app, _API, _t);
webApiService = _webApiService;
}
async init() {
API.isLoggedIn = false;
API.attemptingAutoLogin = false;
API.autoLoginAttempts = new Set();
/**
* @param {{ username: string, password: string }} params credential to login
* @returns {Promise<{origin: boolean, json: any, params}>}
*/
API.login = function (params) {
var { username, password, saveCredentials, cipher } = params;
username = encodeURIComponent(username);
password = encodeURIComponent(password);
var auth = btoa(`${username}:${password}`);
if (saveCredentials) {
delete params.saveCredentials;
if (cipher) {
params.password = cipher;
delete params.cipher;
}
$app.saveCredentials = params;
}
return this.call('auth/user', {
method: 'GET',
headers: {
Authorization: `Basic ${auth}`
}
}).then((json) => {
var args = {
json,
params,
origin: true
};
if (
json.requiresTwoFactorAuth &&
json.requiresTwoFactorAuth.includes('emailOtp')
) {
this.$emit('USER:EMAILOTP', args);
} else if (json.requiresTwoFactorAuth) {
this.$emit('USER:2FA', args);
} else {
this.$emit('USER:CURRENT', args);
}
return args;
});
};
/**
* @param {{ code: string }} params One-time password
* @returns {Promise<{json: any, params}>}
*/
API.verifyOTP = function (params) {
return this.call('auth/twofactorauth/otp/verify', {
method: 'POST',
params
}).then((json) => {
var args = {
json,
params
};
this.$emit('OTP', args);
return args;
});
};
/**
* @param {{ code: string }} params One-time token
* @returns {Promise<{json: any, params}>}
*/
API.verifyTOTP = function (params) {
return this.call('auth/twofactorauth/totp/verify', {
method: 'POST',
params
}).then((json) => {
var args = {
json,
params
};
this.$emit('TOTP', args);
return args;
});
};
/**
* @param {{ code: string }} params One-time token
* @returns {Promise<{json: any, params}>}
*/
API.verifyEmailOTP = function (params) {
return this.call('auth/twofactorauth/emailotp/verify', {
method: 'POST',
params
}).then((json) => {
var args = {
json,
params
};
this.$emit('EMAILOTP', args);
return args;
});
};
API.$on('AUTOLOGIN', function () {
if (this.attemptingAutoLogin) {
return;
}
this.attemptingAutoLogin = true;
var user =
$app.loginForm.savedCredentials[
$app.loginForm.lastUserLoggedIn
];
if (typeof user === 'undefined') {
this.attemptingAutoLogin = false;
return;
}
if ($app.enablePrimaryPassword) {
console.error(
'Primary password is enabled, this disables auto login.'
);
this.attemptingAutoLogin = false;
this.logout();
return;
}
var attemptsInLastHour = Array.from(this.autoLoginAttempts).filter(
(timestamp) => timestamp > new Date().getTime() - 3600000
).length;
if (attemptsInLastHour >= 3) {
console.error(
'More than 3 auto login attempts within the past hour, logging out instead of attempting auto login.'
);
this.attemptingAutoLogin = false;
this.logout();
return;
}
this.autoLoginAttempts.add(new Date().getTime());
$app.relogin(user)
.then(() => {
if (this.errorNoty) {
this.errorNoty.close();
}
this.errorNoty = new Noty({
type: 'success',
text: 'Automatically logged in.'
}).show();
console.log('Automatically logged in.');
})
.catch((err) => {
if (this.errorNoty) {
this.errorNoty.close();
}
this.errorNoty = new Noty({
type: 'error',
text: 'Failed to login automatically.'
}).show();
console.error('Failed to login automatically.', err);
})
.finally(() => {
if (!navigator.onLine) {
this.errorNoty = new Noty({
type: 'error',
text: `You're offline.`
}).show();
console.error(`You're offline.`);
}
});
});
API.$on('USER:CURRENT', function () {
this.attemptingAutoLogin = false;
});
API.$on('LOGOUT', function () {
this.attemptingAutoLogin = false;
this.autoLoginAttempts.clear();
});
API.logout = function () {
this.$emit('LOGOUT');
// return this.call('logout', {
// method: 'PUT'
// }).finally(() => {
// this.$emit('LOGOUT');
// });
};
}
_data = {
loginForm: {
loading: true,
username: '',
password: '',
endpoint: '',
websocket: '',
saveCredentials: false,
savedCredentials: {},
lastUserLoggedIn: '',
rules: {
username: [
{
required: true,
trigger: 'blur'
}
],
password: [
{
required: true,
trigger: 'blur'
}
]
}
}
};
_methods = {
async relogin(user) {
var { loginParmas } = user;
if (user.cookies) {
await webApiService.setCookies(user.cookies);
}
this.loginForm.lastUserLoggedIn = user.user.id; // for resend email 2fa
if (loginParmas.endpoint) {
API.endpointDomain = loginParmas.endpoint;
API.websocketDomain = loginParmas.websocket;
} else {
API.endpointDomain = API.endpointDomainVrchat;
API.websocketDomain = API.websocketDomainVrchat;
}
return new Promise((resolve, reject) => {
this.loginForm.loading = true;
if (this.enablePrimaryPassword) {
this.checkPrimaryPassword(loginParmas)
.then((pwd) => {
return API.getConfig()
.catch((err) => {
reject(err);
})
.then(() => {
API.login({
username: loginParmas.username,
password: pwd,
cipher: loginParmas.password,
endpoint: loginParmas.endpoint,
websocket: loginParmas.websocket
})
.catch((err2) => {
// API.logout();
reject(err2);
})
.then(() => {
resolve();
});
});
})
.catch((_) => {
this.$message({
message: 'Incorrect primary password',
type: 'error'
});
reject(_);
});
} else {
API.getConfig()
.catch((err) => {
reject(err);
})
.then(() => {
API.login({
username: loginParmas.username,
password: loginParmas.password,
endpoint: loginParmas.endpoint,
websocket: loginParmas.websocket
})
.catch((err2) => {
API.logout();
reject(err2);
})
.then(() => {
resolve();
});
});
}
}).finally(() => (this.loginForm.loading = false));
},
async deleteSavedLogin(userId) {
var savedCredentials = JSON.parse(
await configRepository.getString('savedCredentials')
);
delete savedCredentials[userId];
// Disable primary password when no account is available.
if (Object.keys(savedCredentials).length === 0) {
this.enablePrimaryPassword = false;
await configRepository.setBool('enablePrimaryPassword', false);
}
this.loginForm.savedCredentials = savedCredentials;
var jsonCredentials = JSON.stringify(savedCredentials);
await configRepository.setString(
'savedCredentials',
jsonCredentials
);
new Noty({
type: 'success',
text: 'Account removed.'
}).show();
},
async login() {
await webApiService.clearCookies();
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
}).finally(() => {
this.loginForm.loading = false;
});
return args;
});
}
},
logout() {
this.$confirm('Continue? Logout', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
API.logout();
}
}
});
}
};
}

View File

@@ -1,402 +0,0 @@
import Noty from 'noty';
import { baseClass, $app, API, $t } from './baseClass.js';
/* eslint-disable no-unused-vars */
let webApiService = {};
/* eslint-enable no-unused-vars */
export default class extends baseClass {
constructor(_app, _API, _t, _webApiService) {
super(_app, _API, _t);
webApiService = _webApiService;
}
init() {
API.cachedConfig = {};
API.pendingGetRequests = new Map();
API.failedGetRequests = new Map();
API.endpointDomainVrchat = 'https://api.vrchat.cloud/api/1';
API.websocketDomainVrchat = 'wss://pipeline.vrchat.cloud';
API.endpointDomain = 'https://api.vrchat.cloud/api/1';
API.websocketDomain = 'wss://pipeline.vrchat.cloud';
API.call = function (endpoint, options) {
var init = {
url: `${API.endpointDomain}/${endpoint}`,
method: 'GET',
...options
};
var { params } = init;
if (init.method === 'GET') {
// don't retry recent 404/403
if (this.failedGetRequests.has(endpoint)) {
var lastRun = this.failedGetRequests.get(endpoint);
if (lastRun >= Date.now() - 900000) {
// 15mins
throw new Error(
`${$t('api.error.message.403_404_bailing_request')}, ${endpoint}`
);
}
this.failedGetRequests.delete(endpoint);
}
// transform body to url
if (params === Object(params)) {
var url = new URL(init.url);
var { searchParams } = url;
for (var key in params) {
searchParams.set(key, params[key]);
}
init.url = url.toString();
}
// merge requests
var req = this.pendingGetRequests.get(init.url);
if (typeof req !== 'undefined') {
if (req.time >= Date.now() - 10000) {
// 10s
return req.req;
}
this.pendingGetRequests.delete(init.url);
}
} else if (
init.uploadImage ||
init.uploadFilePUT ||
init.uploadImageLegacy
) {
// nothing
} else {
init.headers = {
'Content-Type': 'application/json;charset=utf-8',
...init.headers
};
init.body =
params === Object(params) ? JSON.stringify(params) : '{}';
}
var req = webApiService
.execute(init)
.catch((err) => {
this.$throw(0, err, endpoint);
})
.then((response) => {
if (!response.data) {
if ($app.debugWebRequests) {
console.log(init, response);
}
return response;
}
try {
response.data = JSON.parse(response.data);
if ($app.debugWebRequests) {
console.log(init, response.data);
}
return response;
} catch (e) {}
if (response.status === 200) {
this.$throw(
0,
$t('api.error.message.invalid_json_response'),
endpoint
);
}
if (
response.status === 429 &&
init.url.endsWith('/instances/groups')
) {
$app.nextGroupInstanceRefresh = 120; // 1min
throw new Error(
`${response.status}: rate limited ${endpoint}`
);
}
if (response.status === 504 || response.status === 502) {
// ignore expected API errors
throw new Error(
`${response.status}: ${response.data} ${endpoint}`
);
}
this.$throw(response.status, endpoint);
return {};
})
.then(({ data, status }) => {
if (status === 200) {
if (!data) {
return data;
}
var text = '';
if (data.success === Object(data.success)) {
text = data.success.message;
} else if (data.OK === String(data.OK)) {
text = data.OK;
}
if (text) {
new Noty({
type: 'success',
text: $app.escapeTag(text)
}).show();
}
return data;
}
if (
status === 401 &&
data.error.message === '"Missing Credentials"'
) {
this.$emit('AUTOLOGIN');
throw new Error(
`401 ${$t('api.error.message.missing_credentials')}`
);
}
if (
status === 401 &&
data.error.message === '"Unauthorized"' &&
endpoint !== 'auth/user'
) {
// trigger 2FA dialog
if (!$app.twoFactorAuthDialogVisible) {
$app.API.getCurrentUser();
}
throw new Error(`401 ${$t('api.status_code.401')}`);
}
if (status === 403 && endpoint === 'config') {
$app.$alert(
$t('api.error.message.vpn_in_use'),
`403 ${$t('api.error.message.login_error')}`
);
this.logout();
throw new Error(`403 ${endpoint}`);
}
if (
init.method === 'GET' &&
status === 404 &&
endpoint.startsWith('avatars/')
) {
$app.$message({
message: $t(
'message.api_handler.avatar_private_or_deleted'
),
type: 'error'
});
$app.avatarDialog.visible = false;
throw new Error(
`404: ${data.error.message} ${endpoint}`
);
}
if (
status === 404 &&
endpoint.endsWith('/persist/exists')
) {
return false;
}
if (
init.method === 'GET' &&
(status === 404 || status === 403) &&
!endpoint.startsWith('auth/user')
) {
this.failedGetRequests.set(endpoint, Date.now());
}
if (
init.method === 'GET' &&
status === 404 &&
endpoint.startsWith('users/') &&
endpoint.split('/').length - 1 === 1
) {
throw new Error(
`404: ${data.error.message} ${endpoint}`
);
}
if (
status === 404 &&
endpoint.startsWith('invite/') &&
init.inviteId
) {
this.expireNotification(init.inviteId);
}
if (
status === 403 &&
endpoint.startsWith('invite/myself/to/')
) {
throw new Error(
`403: ${data.error.message} ${endpoint}`
);
}
if (data && data.error === Object(data.error)) {
this.$throw(
data.error.status_code || status,
data.error.message,
endpoint
);
} else if (data && typeof data.error === 'string') {
this.$throw(
data.status_code || status,
data.error,
endpoint
);
}
this.$throw(status, data, endpoint);
return data;
});
if (init.method === 'GET') {
req.finally(() => {
this.pendingGetRequests.delete(init.url);
});
this.pendingGetRequests.set(init.url, {
req,
time: Date.now()
});
}
return req;
};
// FIXME : extra를 없애줘
API.$throw = function (code, error, endpoint) {
var text = [];
if (code > 0) {
const status = this.statusCodes[code];
if (typeof status === 'undefined') {
text.push(`${code}`);
} else {
const codeText = $t(`api.status_code.${code}`);
text.push(`${code} ${codeText}`);
}
}
if (typeof error !== 'undefined') {
text.push(
`${$t('api.error.message.error_message')}: ${typeof error === 'string' ? error : JSON.stringify(error)}`
);
}
if (typeof endpoint !== 'undefined') {
text.push(
`${$t('api.error.message.endpoint')}: "${typeof endpoint === 'string' ? endpoint : JSON.stringify(endpoint)}"`
);
}
text = text.map((s) => $app.escapeTag(s)).join('<br>');
if (text.length) {
if (this.errorNoty) {
this.errorNoty.close();
}
this.errorNoty = new Noty({
type: 'error',
text
}).show();
}
throw new Error(text);
};
API.$bulk = function (options, args) {
if ('handle' in options) {
options.handle.call(this, args, options);
}
if (
args.json.length > 0 &&
((options.params.offset += args.json.length),
// eslint-disable-next-line no-nested-ternary
options.N > 0
? options.N > options.params.offset
: options.N < 0
? args.json.length
: options.params.n === args.json.length)
) {
this.bulk(options);
} else if ('done' in options) {
options.done.call(this, true, options);
}
return args;
};
API.bulk = function (options) {
// it's stupid, but I won't waste time on the 'this' context
// works, that's enough.
if (typeof options.fn === 'function') {
options
.fn(options.params)
.catch((err) => {
if ('done' in options) {
options.done.call(this, false, options);
}
throw err;
})
.then((args) => this.$bulk(options, args));
} else {
this[options.fn](options.params)
.catch((err) => {
if ('done' in options) {
options.done.call(this, false, options);
}
throw err;
})
.then((args) => this.$bulk(options, args));
}
};
API.statusCodes = {
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing',
103: 'Early Hints',
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status',
208: 'Already Reported',
226: 'IM Used',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Found',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
306: 'Switch Proxy',
307: 'Temporary Redirect',
308: 'Permanent Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Payload Too Large',
414: 'URI Too Long',
415: 'Unsupported Media Type',
416: 'Range Not Satisfiable',
417: 'Expectation Failed',
418: "I'm a teapot",
421: 'Misdirected Request',
422: 'Unprocessable Entity',
423: 'Locked',
424: 'Failed Dependency',
425: 'Too Early',
426: 'Upgrade Required',
428: 'Precondition Required',
429: 'Too Many Requests',
431: 'Request Header Fields Too Large',
451: 'Unavailable For Legal Reasons',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates',
507: 'Insufficient Storage',
508: 'Loop Detected',
510: 'Not Extended',
511: 'Network Authentication Required',
// CloudFlare Error
520: 'Web server returns an unknown error',
521: 'Web server is down',
522: 'Connection timed out',
523: 'Origin is unreachable',
524: 'A timeout occurred',
525: 'SSL handshake failed',
526: 'Invalid SSL certificate',
527: 'Railgun Listener to origin error'
};
}
}

View File

@@ -1,28 +0,0 @@
import $utils from './utils';
/* eslint-disable no-unused-vars */
let $app = {};
let API = {};
let $t = {};
/* eslint-enable no-unused-vars */
class baseClass {
constructor(_app, _API, _t) {
$app = _app;
API = _API;
$t = _t;
this.init();
}
updateRef(_app) {
$app = _app;
}
init() {}
_data = {};
_methods = {};
}
export { baseClass, $app, API, $t, $utils };

View File

@@ -1,325 +0,0 @@
import { isRealInstance, parseLocation } from '../composables/instance/utils';
import { $app, API, baseClass } from './baseClass.js';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {
API.currentUser = {
$userColour: ''
};
API.getCurrentUser = function () {
return this.call('auth/user', {
method: 'GET'
}).then((json) => {
var args = {
json,
fromGetCurrentUser: true
};
if (
json.requiresTwoFactorAuth &&
json.requiresTwoFactorAuth.includes('emailOtp')
) {
this.$emit('USER:EMAILOTP', args);
} else if (json.requiresTwoFactorAuth) {
this.$emit('USER:2FA', args);
} else {
if ($app.debugCurrentUserDiff) {
var ref = args.json;
var $ref = this.currentUser;
var props = {};
for (var prop in $ref) {
if ($ref[prop] !== Object($ref[prop])) {
props[prop] = true;
}
}
for (var prop in ref) {
if (
Array.isArray(ref[prop]) &&
Array.isArray($ref[prop])
) {
if (!$app.arraysMatch(ref[prop], $ref[prop])) {
props[prop] = true;
}
} else if (ref[prop] !== Object(ref[prop])) {
props[prop] = true;
}
}
var has = false;
for (var prop in props) {
var asis = $ref[prop];
var tobe = ref[prop];
if (asis === tobe) {
delete props[prop];
} else {
if (
prop.startsWith('$') ||
prop === 'offlineFriends' ||
prop === 'onlineFriends' ||
prop === 'activeFriends'
) {
delete props[prop];
continue;
}
props[prop] = [tobe, asis];
has = true;
}
}
if (has) {
console.log('API.getCurrentUser diff', props);
}
}
$app.nextCurrentUserRefresh = 420; // 7mins
this.$emit('USER:CURRENT', args);
}
return args;
});
};
API.$on('USER:CURRENT', function (args) {
var { json } = args;
args.ref = this.applyCurrentUser(json);
// when isGameRunning use gameLog instead of API
var $location = parseLocation($app.lastLocation.location);
var $travelingLocation = parseLocation(
$app.lastLocationDestination
);
var location = $app.lastLocation.location;
var instanceId = $location.instanceId;
var worldId = $location.worldId;
var travelingToLocation = $app.lastLocationDestination;
var travelingToWorld = $travelingLocation.worldId;
var travelingToInstance = $travelingLocation.instanceId;
if (!$app.isGameRunning && json.presence) {
if (isRealInstance(json.presence.world)) {
location = `${json.presence.world}:${json.presence.instance}`;
} else {
location = json.presence.world;
}
if (isRealInstance(json.presence.travelingToWorld)) {
travelingToLocation = `${json.presence.travelingToWorld}:${json.presence.travelingToInstance}`;
} else {
travelingToLocation = json.presence.travelingToWorld;
}
instanceId = json.presence.instance;
worldId = json.presence.world;
travelingToInstance = json.presence.travelingToInstance;
travelingToWorld = json.presence.travelingToWorld;
}
this.applyUser({
allowAvatarCopying: json.allowAvatarCopying,
badges: json.badges,
bio: json.bio,
bioLinks: json.bioLinks,
currentAvatarImageUrl: json.currentAvatarImageUrl,
currentAvatarTags: json.currentAvatarTags,
currentAvatarThumbnailImageUrl:
json.currentAvatarThumbnailImageUrl,
date_joined: json.date_joined,
developerType: json.developerType,
displayName: json.displayName,
friendKey: json.friendKey,
// json.friendRequestStatus - missing from currentUser
id: json.id,
// instanceId - missing from currentUser
isFriend: json.isFriend,
last_activity: json.last_activity,
last_login: json.last_login,
last_mobile: json.last_mobile,
last_platform: json.last_platform,
// location - missing from currentUser
// platform - missing from currentUser
// note - missing from currentUser
profilePicOverride: json.profilePicOverride,
// profilePicOverrideThumbnail - missing from currentUser
pronouns: json.pronouns,
state: json.state,
status: json.status,
statusDescription: json.statusDescription,
tags: json.tags,
// travelingToInstance - missing from currentUser
// travelingToLocation - missing from currentUser
// travelingToWorld - missing from currentUser
userIcon: json.userIcon,
// worldId - missing from currentUser
fallbackAvatar: json.fallbackAvatar,
// Location from gameLog/presence
location,
instanceId,
worldId,
travelingToLocation,
travelingToInstance,
travelingToWorld,
// set VRCX online/offline timers
$online_for: this.currentUser.$online_for,
$offline_for: this.currentUser.$offline_for,
$location_at: this.currentUser.$location_at,
$travelingToTime: this.currentUser.$travelingToTime
});
});
API.applyCurrentUser = function (json) {
var ref = this.currentUser;
if (this.isLoggedIn) {
if (json.currentAvatar !== ref.currentAvatar) {
$app.addAvatarToHistory(json.currentAvatar);
if ($app.isGameRunning) {
$app.addAvatarWearTime(ref.currentAvatar);
ref.$previousAvatarSwapTime = Date.now();
}
}
Object.assign(ref, json);
if (ref.homeLocation !== ref.$homeLocation.tag) {
ref.$homeLocation = parseLocation(ref.homeLocation);
// apply home location name to user dialog
if (
$app.userDialog.visible &&
$app.userDialog.id === ref.id
) {
$app.getWorldName(API.currentUser.homeLocation).then(
(worldName) => {
$app.userDialog.$homeLocationName = worldName;
}
);
}
}
ref.$isVRCPlus = ref.tags.includes('system_supporter');
this.applyUserTrustLevel(ref);
this.applyUserLanguage(ref);
this.applyPresenceLocation(ref);
this.applyQueuedInstance(ref.queuedInstance);
this.applyPresenceGroups(ref);
} else {
ref = {
acceptedPrivacyVersion: 0,
acceptedTOSVersion: 0,
accountDeletionDate: null,
accountDeletionLog: null,
activeFriends: [],
ageVerificationStatus: '',
ageVerified: false,
allowAvatarCopying: false,
badges: [],
bio: '',
bioLinks: [],
currentAvatar: '',
currentAvatarImageUrl: '',
currentAvatarTags: [],
currentAvatarThumbnailImageUrl: '',
date_joined: '',
developerType: '',
displayName: '',
emailVerified: false,
fallbackAvatar: '',
friendGroupNames: [],
friendKey: '',
friends: [],
googleId: '',
hasBirthday: false,
hasEmail: false,
hasLoggedInFromClient: false,
hasPendingEmail: false,
hideContentFilterSettings: false,
homeLocation: '',
id: '',
isAdult: true,
isBoopingEnabled: false,
isFriend: false,
last_activity: '',
last_login: '',
last_mobile: null,
last_platform: '',
obfuscatedEmail: '',
obfuscatedPendingEmail: '',
oculusId: '',
offlineFriends: [],
onlineFriends: [],
pastDisplayNames: [],
picoId: '',
presence: {
avatarThumbnail: '',
currentAvatarTags: '',
displayName: '',
groups: [],
id: '',
instance: '',
instanceType: '',
platform: '',
profilePicOverride: '',
status: '',
travelingToInstance: '',
travelingToWorld: '',
userIcon: '',
world: '',
...json.presence
},
profilePicOverride: '',
pronouns: '',
queuedInstance: '',
state: '',
status: '',
statusDescription: '',
statusFirstTime: false,
statusHistory: [],
steamDetails: {},
steamId: '',
tags: [],
twoFactorAuthEnabled: false,
twoFactorAuthEnabledDate: null,
unsubscribe: false,
updated_at: '',
userIcon: '',
userLanguage: '',
userLanguageCode: '',
username: '',
viveId: '',
// VRCX
$online_for: Date.now(),
$offline_for: '',
$location_at: Date.now(),
$travelingToTime: Date.now(),
$previousAvatarSwapTime: '',
$homeLocation: {},
$isVRCPlus: false,
$isModerator: false,
$isTroll: false,
$isProbableTroll: false,
$trustLevel: 'Visitor',
$trustClass: 'x-tag-untrusted',
$userColour: '',
$trustSortNum: 1,
$languages: [],
$locationTag: '',
$travelingToLocation: '',
...json
};
if ($app.isGameRunning) {
ref.$previousAvatarSwapTime = Date.now();
}
ref.$homeLocation = parseLocation(ref.homeLocation);
ref.$isVRCPlus = ref.tags.includes('system_supporter');
this.applyUserTrustLevel(ref);
this.applyUserLanguage(ref);
this.applyPresenceLocation(ref);
this.applyPresenceGroups(ref);
this.currentUser = ref;
this.isLoggedIn = true;
this.$emit('LOGIN', {
json,
ref
});
}
return ref;
};
}
_data = {};
_methods = {};
}

View File

@@ -1,291 +0,0 @@
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) {
super(_app, _API, _t);
}
_data = {
isDiscordActive: false,
discordActive: false,
discordInstance: true,
discordJoinButton: false,
discordHideInvite: true,
discordHideImage: false
};
_methods = {
updateDiscord() {
var currentLocation = this.lastLocation.location;
var timeStamp = this.lastLocation.date;
if (this.lastLocation.location === 'traveling') {
currentLocation = this.lastLocationDestination;
timeStamp = this.lastLocationDestinationTime;
}
if (
!this.discordActive ||
(!this.isGameRunning && !this.gameLogDisabled) ||
(!currentLocation && !this.lastLocation$.tag)
) {
this.setDiscordActive(false);
return;
}
this.setDiscordActive(true);
var L = this.lastLocation$;
if (currentLocation !== this.lastLocation$.tag) {
Discord.SetTimestamps(timeStamp, 0);
L = parseLocation(currentLocation);
L.worldName = '';
L.thumbnailImageUrl = '';
L.worldCapacity = 0;
L.joinUrl = '';
L.accessName = '';
if (L.worldId) {
var ref = API.cachedWorlds.get(L.worldId);
if (ref) {
L.worldName = ref.name;
L.thumbnailImageUrl = ref.thumbnailImageUrl;
L.worldCapacity = ref.capacity;
} else {
worldRequest
.getWorld({
worldId: L.worldId
})
.then((args) => {
L.worldName = args.ref.name;
L.thumbnailImageUrl =
args.ref.thumbnailImageUrl;
L.worldCapacity = args.ref.capacity;
return args;
});
}
if (this.isGameNoVR) {
var platform = 'Desktop';
} else {
var platform = 'VR';
}
var groupAccessType = '';
if (L.groupAccessType) {
if (L.groupAccessType === 'public') {
groupAccessType = 'Public';
} else if (L.groupAccessType === 'plus') {
groupAccessType = 'Plus';
}
}
switch (L.accessType) {
case 'public':
L.joinUrl = getLaunchURL(L);
L.accessName = `Public #${L.instanceName} (${platform})`;
break;
case 'invite+':
L.accessName = `Invite+ #${L.instanceName} (${platform})`;
break;
case 'invite':
L.accessName = `Invite #${L.instanceName} (${platform})`;
break;
case 'friends':
L.accessName = `Friends #${L.instanceName} (${platform})`;
break;
case 'friends+':
L.accessName = `Friends+ #${L.instanceName} (${platform})`;
break;
case 'group':
L.accessName = `Group #${L.instanceName} (${platform})`;
this.getGroupName(L.groupId).then((groupName) => {
if (groupName) {
L.accessName = `Group${groupAccessType}(${groupName}) #${L.instanceName} (${platform})`;
}
});
break;
}
}
this.lastLocation$ = L;
}
var hidePrivate = false;
if (
this.discordHideInvite &&
(L.accessType === 'invite' ||
L.accessType === 'invite+' ||
L.groupAccessType === 'members')
) {
hidePrivate = true;
}
switch (API.currentUser.status) {
case 'active':
L.statusName = 'Online';
L.statusImage = 'active';
break;
case 'join me':
L.statusName = 'Join Me';
L.statusImage = 'joinme';
break;
case 'ask me':
L.statusName = 'Ask Me';
L.statusImage = 'askme';
if (this.discordHideInvite) {
hidePrivate = true;
}
break;
case 'busy':
L.statusName = 'Do Not Disturb';
L.statusImage = 'busy';
hidePrivate = true;
break;
}
var appId = '883308884863901717';
var bigIcon = 'vrchat';
var partyId = `${L.worldId}:${L.instanceName}`;
var partySize = this.lastLocation.playerList.size;
var partyMaxSize = L.worldCapacity;
if (partySize > partyMaxSize) {
partyMaxSize = partySize;
}
var buttonText = 'Join';
var buttonUrl = L.joinUrl;
if (!this.discordJoinButton) {
buttonText = '';
buttonUrl = '';
}
if (!this.discordInstance) {
partySize = 0;
partyMaxSize = 0;
}
if (hidePrivate) {
partyId = '';
partySize = 0;
partyMaxSize = 0;
buttonText = '';
buttonUrl = '';
} else if (this.isRpcWorld(L.tag)) {
// custom world rpc
if (
L.worldId === 'wrld_f20326da-f1ac-45fc-a062-609723b097b1' ||
L.worldId === 'wrld_10e5e467-fc65-42ed-8957-f02cace1398c' ||
L.worldId === 'wrld_04899f23-e182-4a8d-b2c7-2c74c7c15534'
) {
appId = '784094509008551956';
bigIcon = 'pypy';
} else if (
L.worldId === 'wrld_42377cf1-c54f-45ed-8996-5875b0573a83' ||
L.worldId === 'wrld_dd6d2888-dbdc-47c2-bc98-3d631b2acd7c'
) {
appId = '846232616054030376';
bigIcon = 'vr_dancing';
} else if (
L.worldId === 'wrld_52bdcdab-11cd-4325-9655-0fb120846945' ||
L.worldId === 'wrld_2d40da63-8f1f-4011-8a9e-414eb8530acd'
) {
appId = '939473404808007731';
bigIcon = 'zuwa_zuwa_dance';
} else if (
L.worldId === 'wrld_74970324-58e8-4239-a17b-2c59dfdf00db' ||
L.worldId === 'wrld_db9d878f-6e76-4776-8bf2-15bcdd7fc445' ||
L.worldId === 'wrld_435bbf25-f34f-4b8b-82c6-cd809057eb8e' ||
L.worldId === 'wrld_f767d1c8-b249-4ecc-a56f-614e433682c8'
) {
appId = '968292722391785512';
bigIcon = 'ls_media';
} else if (
L.worldId === 'wrld_266523e8-9161-40da-acd0-6bd82e075833'
) {
appId = '1095440531821170820';
bigIcon = 'movie_and_chill';
}
if (this.nowPlaying.name) {
L.worldName = this.nowPlaying.name;
}
if (this.nowPlaying.playing) {
Discord.SetTimestamps(
Date.now(),
(this.nowPlaying.startTime -
this.nowPlaying.offset +
this.nowPlaying.length) *
1000
);
}
} else if (!this.discordHideImage && L.thumbnailImageUrl) {
bigIcon = L.thumbnailImageUrl;
}
Discord.SetAssets(
bigIcon, // big icon
'Powered by VRCX', // big icon hover text
L.statusImage, // small icon
L.statusName, // small icon hover text
partyId, // party id
partySize, // party size
partyMaxSize, // party max size
buttonText, // button text
buttonUrl, // button url
appId // app id
);
// NOTE
// 글자 수가 짧으면 업데이트가 안된다..
if (L.worldName.length < 2) {
L.worldName += '\uFFA0'.repeat(2 - L.worldName.length);
}
if (hidePrivate) {
Discord.SetText('Private', '');
Discord.SetTimestamps(0, 0);
} else if (this.discordInstance) {
Discord.SetText(L.worldName, L.accessName);
} else {
Discord.SetText(L.worldName, '');
}
},
async setDiscordActive(active) {
if (active !== this.isDiscordActive) {
this.isDiscordActive = await Discord.SetActive(active);
}
},
async saveDiscordOption(configLabel = '') {
if (configLabel === 'discordActive') {
this.discordActive = !this.discordActive;
await configRepository.setBool(
'discordActive',
this.discordActive
);
}
if (configLabel === 'discordInstance') {
this.discordInstance = !this.discordInstance;
await configRepository.setBool(
'discordInstance',
this.discordInstance
);
}
if (configLabel === 'discordJoinButton') {
this.discordJoinButton = !this.discordJoinButton;
await configRepository.setBool(
'discordJoinButton',
this.discordJoinButton
);
}
if (configLabel === 'discordHideInvite') {
this.discordHideInvite = !this.discordHideInvite;
await configRepository.setBool(
'discordHideInvite',
this.discordHideInvite
);
}
if (configLabel === 'discordHideImage') {
this.discordHideImage = !this.discordHideImage;
await configRepository.setBool(
'discordHideImage',
this.discordHideImage
);
}
this.lastLocation$.tag = '';
this.nextDiscordUpdate = 3;
this.updateDiscord();
}
};
}

View File

@@ -1,179 +0,0 @@
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
import configRepository from '../service/config.js';
import database from '../service/database.js';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
_data = {
feedTable: {
data: [],
search: '',
vip: false,
loading: false,
filter: [],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: {
prop: 'created_at',
order: 'descending'
}
},
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
},
feedSessionTable: []
};
_methods = {
feedSearch(row) {
var value = this.feedTable.search.toUpperCase();
if (!value) {
return true;
}
if (
(value.startsWith('wrld_') || value.startsWith('grp_')) &&
String(row.location).toUpperCase().includes(value)
) {
return true;
}
switch (row.type) {
case 'GPS':
if (String(row.displayName).toUpperCase().includes(value)) {
return true;
}
if (String(row.worldName).toUpperCase().includes(value)) {
return true;
}
return false;
case 'Online':
if (String(row.displayName).toUpperCase().includes(value)) {
return true;
}
if (String(row.worldName).toUpperCase().includes(value)) {
return true;
}
return false;
case 'Offline':
if (String(row.displayName).toUpperCase().includes(value)) {
return true;
}
if (String(row.worldName).toUpperCase().includes(value)) {
return true;
}
return false;
case 'Status':
if (String(row.displayName).toUpperCase().includes(value)) {
return true;
}
if (String(row.status).toUpperCase().includes(value)) {
return true;
}
if (
String(row.statusDescription)
.toUpperCase()
.includes(value)
) {
return true;
}
return false;
case 'Avatar':
if (String(row.displayName).toUpperCase().includes(value)) {
return true;
}
if (String(row.avatarName).toUpperCase().includes(value)) {
return true;
}
return false;
case 'Bio':
if (String(row.displayName).toUpperCase().includes(value)) {
return true;
}
if (String(row.bio).toUpperCase().includes(value)) {
return true;
}
if (String(row.previousBio).toUpperCase().includes(value)) {
return true;
}
return false;
}
return true;
},
async feedTableLookup() {
await configRepository.setString(
'VRCX_feedTableFilters',
JSON.stringify(this.feedTable.filter)
);
await configRepository.setBool(
'VRCX_feedTableVIPFilter',
this.feedTable.vip
);
this.feedTable.loading = true;
var vipList = [];
if (this.feedTable.vip) {
vipList = Array.from(this.localFavoriteFriends.values());
}
this.feedTable.data = await database.lookupFeedDatabase(
this.feedTable.search,
this.feedTable.filter,
vipList
);
this.feedTable.loading = false;
},
addFeed(feed) {
this.queueFeedNoty(feed);
this.feedSessionTable.push(feed);
this.updateSharedFeed(false);
if (
this.feedTable.filter.length > 0 &&
!this.feedTable.filter.includes(feed.type)
) {
return;
}
if (
this.feedTable.vip &&
!this.localFavoriteFriends.has(feed.userId)
) {
return;
}
if (!this.feedSearch(feed)) {
return;
}
this.feedTable.data.push(feed);
this.sweepFeed();
this.notifyMenu('feed');
},
sweepFeed() {
var { data } = this.feedTable;
var j = data.length;
if (j > this.maxTableSize) {
data.splice(0, j - this.maxTableSize);
}
var date = new Date();
date.setDate(date.getDate() - 1); // 24 hour limit
var limit = date.toJSON();
var i = 0;
var k = this.feedSessionTable.length;
while (i < k && this.feedSessionTable[i].created_at < limit) {
++i;
}
if (i === k) {
this.feedSessionTable = [];
} else if (i) {
this.feedSessionTable.splice(0, i);
}
}
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +0,0 @@
import * as workerTimers from 'worker-timers';
import configRepository from '../service/config.js';
import database from '../service/database.js';
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
import { inventoryRequest } from '../api';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {
API.currentUserInventory = new Map();
API.$on('LOGIN', function () {
API.currentUserInventory.clear();
});
}
_data = {
inventoryTable: []
};
_methods = {
async getInventory() {
this.inventoryTable = [];
API.currentUserInventory.clear();
var params = {
n: 100,
offset: 0,
order: 'newest'
};
this.galleryDialogInventoryLoading = true;
try {
for (let i = 0; i < 100; i++) {
params.offset = i * params.n;
const args =
await inventoryRequest.getInventoryItems(params);
for (const item of args.json.data) {
API.currentUserInventory.set(item.id, item);
if (!item.flags.includes('ugc')) {
this.inventoryTable.push(item);
}
}
if (args.json.data.length === 0) {
break;
}
}
} catch (error) {
console.error('Error fetching inventory items:', error);
} finally {
this.galleryDialogInventoryLoading = false;
}
}
};
}

View File

@@ -1,41 +0,0 @@
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
import { userRequest } from '../api';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {
API.$on('CONFIG', function (args) {
var languages =
args.ref?.constants?.LANGUAGE?.SPOKEN_LANGUAGE_OPTIONS;
if (!languages) {
return;
}
$app.subsetOfLanguages = languages;
var data = [];
for (var key in languages) {
var value = languages[key];
data.push({
key,
value
});
}
$app.languageDialog.languages = data;
});
}
_data = {
subsetOfLanguages: [],
languageDialog: {
visible: false,
loading: false,
languageChoice: false,
languages: []
}
};
_methods = {};
}

View File

@@ -1,106 +0,0 @@
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
import database from '../service/database.js';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {}
_data = {
hideUserMemos: false
};
_methods = {
async migrateMemos() {
var json = JSON.parse(await VRCXStorage.GetAll());
for (var line in json) {
if (line.substring(0, 8) === 'memo_usr') {
var userId = line.substring(5);
var memo = json[line];
if (memo) {
await this.saveUserMemo(userId, memo);
VRCXStorage.Remove(`memo_${userId}`);
}
}
}
},
async getUserMemo(userId) {
try {
return await database.getUserMemo(userId);
} catch (err) {
console.error(err);
return {
userId: '',
editedAt: '',
memo: ''
};
}
},
async saveUserMemo(id, memo) {
if (memo) {
await database.setUserMemo({
userId: id,
editedAt: new Date().toJSON(),
memo
});
} else {
await database.deleteUserMemo(id);
}
var ref = this.friends.get(id);
if (ref) {
ref.memo = String(memo || '');
if (memo) {
var array = memo.split('\n');
ref.$nickName = array[0];
} else {
ref.$nickName = '';
}
}
},
async getAllUserMemos() {
var memos = await database.getAllUserMemos();
memos.forEach((memo) => {
var ref = $app.friends.get(memo.userId);
if (typeof ref !== 'undefined') {
ref.memo = memo.memo;
ref.$nickName = '';
if (memo.memo) {
var array = memo.memo.split('\n');
ref.$nickName = array[0];
}
}
});
},
async getWorldMemo(worldId) {
try {
return await database.getWorldMemo(worldId);
} catch (err) {
console.error(err);
return {
worldId: '',
editedAt: '',
memo: ''
};
}
},
async getAvatarMemo(avatarId) {
try {
return await database.getAvatarMemoDB(avatarId);
} catch (err) {
console.error(err);
return {
avatarId: '',
editedAt: '',
memo: ''
};
}
}
};
}

View File

@@ -1,574 +0,0 @@
import * as workerTimers from 'worker-timers';
import configRepository from '../service/config.js';
import database from '../service/database.js';
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
import { avatarRequest, favoriteRequest, worldRequest } from '../api';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
_methods = {
promptTOTP() {
if (this.twoFactorAuthDialogVisible) {
return;
}
AppApi.FlashWindow();
this.twoFactorAuthDialogVisible = true;
this.$prompt(
$t('prompt.totp.description'),
$t('prompt.totp.header'),
{
distinguishCancelAndClose: true,
cancelButtonText: $t('prompt.totp.use_otp'),
confirmButtonText: $t('prompt.totp.verify'),
inputPlaceholder: $t('prompt.totp.input_placeholder'),
inputPattern: /^[0-9]{6}$/,
inputErrorMessage: $t('prompt.totp.input_error'),
callback: (action, instance) => {
if (action === 'confirm') {
API.verifyTOTP({
code: instance.inputValue.trim()
})
.catch((err) => {
$app.clearCookiesTryLogin();
throw err;
})
.then((args) => {
API.getCurrentUser();
return args;
});
} else if (action === 'cancel') {
this.promptOTP();
}
},
beforeClose: (action, instance, done) => {
this.twoFactorAuthDialogVisible = false;
done();
}
}
);
},
promptOTP() {
if (this.twoFactorAuthDialogVisible) {
return;
}
this.twoFactorAuthDialogVisible = true;
this.$prompt(
$t('prompt.otp.description'),
$t('prompt.otp.header'),
{
distinguishCancelAndClose: true,
cancelButtonText: $t('prompt.otp.use_totp'),
confirmButtonText: $t('prompt.otp.verify'),
inputPlaceholder: $t('prompt.otp.input_placeholder'),
inputPattern: /^[a-z0-9]{4}-[a-z0-9]{4}$/,
inputErrorMessage: $t('prompt.otp.input_error'),
callback: (action, instance) => {
if (action === 'confirm') {
API.verifyOTP({
code: instance.inputValue.trim()
})
.catch((err) => {
$app.clearCookiesTryLogin();
throw err;
})
.then((args) => {
API.getCurrentUser();
return args;
});
} else if (action === 'cancel') {
this.promptTOTP();
}
},
beforeClose: (action, instance, done) => {
this.twoFactorAuthDialogVisible = false;
done();
}
}
);
},
promptEmailOTP() {
if (this.twoFactorAuthDialogVisible) {
return;
}
AppApi.FlashWindow();
this.twoFactorAuthDialogVisible = true;
this.$prompt(
$t('prompt.email_otp.description'),
$t('prompt.email_otp.header'),
{
distinguishCancelAndClose: true,
cancelButtonText: $t('prompt.email_otp.resend'),
confirmButtonText: $t('prompt.email_otp.verify'),
inputPlaceholder: $t('prompt.email_otp.input_placeholder'),
inputPattern: /^[0-9]{6}$/,
inputErrorMessage: $t('prompt.email_otp.input_error'),
callback: (action, instance) => {
if (action === 'confirm') {
API.verifyEmailOTP({
code: instance.inputValue.trim()
})
.catch((err) => {
this.promptEmailOTP();
throw err;
})
.then((args) => {
API.getCurrentUser();
return args;
});
} else if (action === 'cancel') {
this.resendEmail2fa();
}
},
beforeClose: (action, instance, done) => {
this.twoFactorAuthDialogVisible = false;
done();
}
}
);
},
promptOmniDirectDialog() {
this.$prompt(
$t('prompt.direct_access_omni.description'),
$t('prompt.direct_access_omni.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.direct_access_omni.ok'),
cancelButtonText: $t('prompt.direct_access_omni.cancel'),
inputPattern: /\S+/,
inputErrorMessage: $t(
'prompt.direct_access_omni.input_error'
),
callback: (action, instance) => {
if (action === 'confirm' && instance.inputValue) {
var input = instance.inputValue.trim();
if (!this.directAccessParse(input)) {
this.$message({
message: $t(
'prompt.direct_access_omni.message.error'
),
type: 'error'
});
}
}
}
}
);
},
promptNotificationTimeout() {
this.$prompt(
$t('prompt.notification_timeout.description'),
$t('prompt.notification_timeout.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.notification_timeout.ok'),
cancelButtonText: $t('prompt.notification_timeout.cancel'),
inputValue: this.notificationTimeout / 1000,
inputPattern: /\d+$/,
inputErrorMessage: $t(
'prompt.notification_timeout.input_error'
),
callback: async (action, instance) => {
if (
action === 'confirm' &&
instance.inputValue &&
!isNaN(instance.inputValue)
) {
this.notificationTimeout = Math.trunc(
Number(instance.inputValue) * 1000
);
await configRepository.setString(
'VRCX_notificationTimeout',
this.notificationTimeout
);
this.updateVRConfigVars();
}
}
}
);
},
promptPhotonOverlayMessageTimeout() {
this.$prompt(
$t('prompt.overlay_message_timeout.description'),
$t('prompt.overlay_message_timeout.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.overlay_message_timeout.ok'),
cancelButtonText: $t(
'prompt.overlay_message_timeout.cancel'
),
inputValue: this.photonOverlayMessageTimeout / 1000,
inputPattern: /\d+$/,
inputErrorMessage: $t(
'prompt.overlay_message_timeout.input_error'
),
callback: async (action, instance) => {
if (
action === 'confirm' &&
instance.inputValue &&
!isNaN(instance.inputValue)
) {
this.photonOverlayMessageTimeout = Math.trunc(
Number(instance.inputValue) * 1000
);
await configRepository.setString(
'VRCX_photonOverlayMessageTimeout',
this.photonOverlayMessageTimeout
);
this.updateVRConfigVars();
}
}
}
);
},
promptRenameWorld(world) {
this.$prompt(
$t('prompt.rename_world.description'),
$t('prompt.rename_world.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.rename_world.ok'),
cancelButtonText: $t('prompt.rename_world.cancel'),
inputValue: world.ref.name,
inputErrorMessage: $t('prompt.rename_world.input_error'),
callback: (action, instance) => {
if (
action === 'confirm' &&
instance.inputValue !== world.ref.name
) {
worldRequest
.saveWorld({
id: world.id,
name: instance.inputValue
})
.then((args) => {
this.$message({
message: $t(
'prompt.rename_world.message.success'
),
type: 'success'
});
return args;
});
}
}
}
);
},
promptChangeWorldDescription(world) {
this.$prompt(
$t('prompt.change_world_description.description'),
$t('prompt.change_world_description.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.change_world_description.ok'),
cancelButtonText: $t(
'prompt.change_world_description.cancel'
),
inputValue: world.ref.description,
inputErrorMessage: $t(
'prompt.change_world_description.input_error'
),
callback: (action, instance) => {
if (
action === 'confirm' &&
instance.inputValue !== world.ref.description
) {
worldRequest
.saveWorld({
id: world.id,
description: instance.inputValue
})
.then((args) => {
this.$message({
message: $t(
'prompt.change_world_description.message.success'
),
type: 'success'
});
return args;
});
}
}
}
);
},
promptChangeWorldCapacity(world) {
this.$prompt(
$t('prompt.change_world_capacity.description'),
$t('prompt.change_world_capacity.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.change_world_capacity.ok'),
cancelButtonText: $t('prompt.change_world_capacity.cancel'),
inputValue: world.ref.capacity,
inputPattern: /\d+$/,
inputErrorMessage: $t(
'prompt.change_world_capacity.input_error'
),
callback: (action, instance) => {
if (
action === 'confirm' &&
instance.inputValue !== world.ref.capacity
) {
worldRequest
.saveWorld({
id: world.id,
capacity: instance.inputValue
})
.then((args) => {
this.$message({
message: $t(
'prompt.change_world_capacity.message.success'
),
type: 'success'
});
return args;
});
}
}
}
);
},
promptChangeWorldRecommendedCapacity(world) {
this.$prompt(
$t('prompt.change_world_recommended_capacity.description'),
$t('prompt.change_world_recommended_capacity.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.change_world_capacity.ok'),
cancelButtonText: $t('prompt.change_world_capacity.cancel'),
inputValue: world.ref.recommendedCapacity,
inputPattern: /\d+$/,
inputErrorMessage: $t(
'prompt.change_world_recommended_capacity.input_error'
),
callback: (action, instance) => {
if (
action === 'confirm' &&
instance.inputValue !==
world.ref.recommendedCapacity
) {
worldRequest
.saveWorld({
id: world.id,
recommendedCapacity: instance.inputValue
})
.then((args) => {
this.$message({
message: $t(
'prompt.change_world_recommended_capacity.message.success'
),
type: 'success'
});
return args;
});
}
}
}
);
},
promptChangeWorldYouTubePreview(world) {
this.$prompt(
$t('prompt.change_world_preview.description'),
$t('prompt.change_world_preview.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.change_world_preview.ok'),
cancelButtonText: $t('prompt.change_world_preview.cancel'),
inputValue: world.ref.previewYoutubeId,
inputErrorMessage: $t(
'prompt.change_world_preview.input_error'
),
callback: (action, instance) => {
if (
action === 'confirm' &&
instance.inputValue !== world.ref.previewYoutubeId
) {
if (instance.inputValue.length > 11) {
try {
var url = new URL(instance.inputValue);
var id1 = url.pathname;
var id2 = url.searchParams.get('v');
if (id1 && id1.length === 12) {
instance.inputValue = id1.substring(
1,
12
);
}
if (id2 && id2.length === 11) {
instance.inputValue = id2;
}
} catch {
this.$message({
message: $t(
'prompt.change_world_preview.message.error'
),
type: 'error'
});
return;
}
}
if (
instance.inputValue !==
world.ref.previewYoutubeId
) {
worldRequest
.saveWorld({
id: world.id,
previewYoutubeId: instance.inputValue
})
.then((args) => {
this.$message({
message: $t(
'prompt.change_world_preview.message.success'
),
type: 'success'
});
return args;
});
}
}
}
}
);
},
promptMaxTableSizeDialog() {
this.$prompt(
$t('prompt.change_table_size.description'),
$t('prompt.change_table_size.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.change_table_size.save'),
cancelButtonText: $t('prompt.change_table_size.cancel'),
inputValue: this.maxTableSize,
inputPattern: /\d+$/,
inputErrorMessage: $t(
'prompt.change_table_size.input_error'
),
callback: async (action, instance) => {
if (action === 'confirm' && instance.inputValue) {
if (instance.inputValue > 10000) {
instance.inputValue = 10000;
}
this.maxTableSize = instance.inputValue;
await configRepository.setString(
'VRCX_maxTableSize',
this.maxTableSize
);
database.setmaxTableSize(this.maxTableSize);
this.feedTableLookup();
this.gameLogTableLookup();
}
}
}
);
},
promptProxySettings() {
this.$prompt(
$t('prompt.proxy_settings.description'),
$t('prompt.proxy_settings.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.proxy_settings.restart'),
cancelButtonText: $t('prompt.proxy_settings.close'),
inputValue: this.proxyServer,
inputPlaceholder: $t('prompt.proxy_settings.placeholder'),
callback: async (action, instance) => {
this.proxyServer = instance.inputValue;
await VRCXStorage.Set(
'VRCX_ProxyServer',
this.proxyServer
);
await VRCXStorage.Flush();
await new Promise((resolve) => {
workerTimers.setTimeout(resolve, 100);
});
if (action === 'confirm') {
var isUpgrade = false;
this.restartVRCX(isUpgrade);
}
}
}
);
},
promptPhotonLobbyTimeoutThreshold() {
this.$prompt(
$t('prompt.photon_lobby_timeout.description'),
$t('prompt.photon_lobby_timeout.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.photon_lobby_timeout.ok'),
cancelButtonText: $t('prompt.photon_lobby_timeout.cancel'),
inputValue: this.photonLobbyTimeoutThreshold / 1000,
inputPattern: /\d+$/,
inputErrorMessage: $t(
'prompt.photon_lobby_timeout.input_error'
),
callback: async (action, instance) => {
if (
action === 'confirm' &&
instance.inputValue &&
!isNaN(instance.inputValue)
) {
this.photonLobbyTimeoutThreshold = Math.trunc(
Number(instance.inputValue) * 1000
);
await configRepository.setString(
'VRCX_photonLobbyTimeoutThreshold',
this.photonLobbyTimeoutThreshold
);
}
}
}
);
},
promptAutoClearVRCXCacheFrequency() {
this.$prompt(
$t('prompt.auto_clear_cache.description'),
$t('prompt.auto_clear_cache.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.auto_clear_cache.ok'),
cancelButtonText: $t('prompt.auto_clear_cache.cancel'),
inputValue: this.clearVRCXCacheFrequency / 3600 / 2,
inputPattern: /\d+$/,
inputErrorMessage: $t(
'prompt.auto_clear_cache.input_error'
),
callback: async (action, instance) => {
if (
action === 'confirm' &&
instance.inputValue &&
!isNaN(instance.inputValue)
) {
this.clearVRCXCacheFrequency = Math.trunc(
Number(instance.inputValue) * 3600 * 2
);
await configRepository.setString(
'VRCX_clearVRCXCacheFrequency',
this.clearVRCXCacheFrequency
);
}
}
}
);
}
};
}

View File

@@ -1,284 +0,0 @@
import * as workerTimers from 'worker-timers';
import configRepository from '../service/config.js';
import database from '../service/database.js';
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {}
_data = {};
_methods = {
async tryRestoreFriendNumber() {
var lastUpdate = await configRepository.getString(
`VRCX_lastStoreTime_${API.currentUser.id}`
);
if (lastUpdate == -4) {
// this means the backup was already applied
return;
}
var status = false;
this.friendNumber = 0;
for (var ref of this.friendLog.values()) {
ref.friendNumber = 0;
}
try {
if (lastUpdate) {
// backup ready to try apply
status = await this.restoreFriendNumber();
}
// needs to be in reverse because we don't know the starting number
this.applyFriendLogFriendOrderInReverse();
} catch (err) {
console.error(err);
}
// if (status) {
// this.$message({
// message: 'Friend order restored from backup',
// type: 'success',
// duration: 0,
// showClose: true
// });
// } else if (this.friendLogTable.data.length > 0) {
// this.$message({
// message:
// 'No backup found, friend order partially restored from friendLog',
// type: 'success',
// duration: 0,
// showClose: true
// });
// }
await configRepository.setString(
`VRCX_lastStoreTime_${API.currentUser.id}`,
-4
);
},
async restoreFriendNumber() {
var storedData = null;
try {
var data = await configRepository.getString(
`VRCX_friendOrder_${API.currentUser.id}`
);
if (data) {
var storedData = JSON.parse(data);
}
} catch (err) {
console.error(err);
}
if (!storedData || storedData.length === 0) {
var message = 'whomp whomp, no friend order backup found';
console.error(message);
return false;
}
var friendLogTable = this.getFriendLogFriendOrder();
// for storedData
var machList = [];
for (var i = 0; i < Object.keys(storedData).length; i++) {
var key = Object.keys(storedData)[i];
var value = storedData[key];
var item = this.parseFriendOrderBackup(
friendLogTable,
key,
value
);
machList.push(item);
}
machList.sort((a, b) => b.matches - a.matches);
console.log(
`friendLog: ${friendLogTable.length} friendOrderBackups:`,
machList
);
var bestBackup = machList[0];
if (!bestBackup?.isValid) {
var message = 'whomp whomp, no valid backup found';
console.error(message);
return false;
}
this.applyFriendOrderBackup(bestBackup.table);
this.applyFriendLogFriendOrder();
await configRepository.setInt(
`VRCX_friendNumber_${API.currentUser.id}`,
this.friendNumber
);
return true;
},
getFriendLogFriendOrder() {
var friendLogTable = [];
for (var i = 0; i < this.friendLogTable.data.length; i++) {
var ref = this.friendLogTable.data[i];
if (ref.type !== 'Friend') {
continue;
}
if (
friendLogTable.findIndex((x) => x.id === ref.userId) !== -1
) {
// console.log(
// 'ignoring duplicate friend',
// ref.displayName,
// ref.created_at
// );
continue;
}
friendLogTable.push({
id: ref.userId,
displayName: ref.displayName,
created_at: ref.created_at
});
}
var compareByCreatedAt = function (a, b) {
var A = a.created_at;
var B = b.created_at;
if (A < B) {
return -1;
}
if (A > B) {
return 1;
}
return 0;
};
friendLogTable.sort(compareByCreatedAt);
return friendLogTable;
},
applyFriendLogFriendOrder() {
var friendLogTable = this.getFriendLogFriendOrder();
if (this.friendNumber === 0) {
console.log(
'No backup applied, applying friend log in reverse'
);
// this means no FriendOrderBackup was applied
// will need to apply in reverse order instead
return;
}
for (var friendLog of friendLogTable) {
var ref = this.friendLog.get(friendLog.id);
if (!ref || ref.friendNumber) {
continue;
}
ref.friendNumber = ++this.friendNumber;
this.friendLog.set(ref.userId, ref);
database.setFriendLogCurrent(ref);
var friendRef = this.friends.get(friendLog.id);
if (friendRef?.ref) {
friendRef.ref.$friendNumber = ref.friendNumber;
}
}
},
applyFriendLogFriendOrderInReverse() {
this.friendNumber = this.friends.size + 1;
var friendLogTable = this.getFriendLogFriendOrder();
for (var i = friendLogTable.length - 1; i > -1; i--) {
var friendLog = friendLogTable[i];
var ref = this.friendLog.get(friendLog.id);
if (!ref) {
continue;
}
if (ref.friendNumber) {
break;
}
ref.friendNumber = --this.friendNumber;
this.friendLog.set(ref.userId, ref);
database.setFriendLogCurrent(ref);
var friendRef = this.friends.get(friendLog.id);
if (friendRef?.ref) {
friendRef.ref.$friendNumber = ref.friendNumber;
}
}
this.friendNumber = this.friends.size;
console.log('Applied friend order from friendLog');
},
parseFriendOrderBackup(friendLogTable, created_at, backupUserIds) {
var backupTable = [];
for (var i = 0; i < backupUserIds.length; i++) {
var userId = backupUserIds[i];
var ctx = this.friends.get(userId);
if (ctx) {
backupTable.push({
id: ctx.id,
displayName: ctx.name
});
}
}
// var compareTable = [];
// compare 2 tables, find max amount of id's in same order
var maxMatches = 0;
var currentMatches = 0;
var backupIndex = 0;
for (var i = 0; i < friendLogTable.length; i++) {
var isMatch = false;
var ref = friendLogTable[i];
if (backupIndex <= 0) {
backupIndex = backupTable.findIndex((x) => x.id === ref.id);
if (backupIndex !== -1) {
currentMatches = 1;
}
} else if (backupTable[backupIndex].id === ref.id) {
currentMatches++;
isMatch = true;
} else {
var backupIndex = backupTable.findIndex(
(x) => x.id === ref.id
);
if (backupIndex !== -1) {
currentMatches = 1;
}
}
if (backupIndex === backupTable.length - 1) {
backupIndex = 0;
} else {
backupIndex++;
}
if (currentMatches > maxMatches) {
maxMatches = currentMatches;
}
// compareTable.push({
// id: ref.id,
// displayName: ref.displayName,
// match: isMatch
// });
}
var lerp = (a, b, alpha) => {
return a + alpha * (b - a);
};
return {
matches: parseFloat(`${maxMatches}.${created_at}`),
table: backupUserIds,
isValid: maxMatches > lerp(4, 10, backupTable.length / 1000) // pls no collisions
};
},
applyFriendOrderBackup(userIdOrder) {
for (var i = 0; i < userIdOrder.length; i++) {
var userId = userIdOrder[i];
var ctx = this.friends.get(userId);
var ref = ctx?.ref;
if (!ref || ref.$friendNumber) {
continue;
}
var friendLogCurrent = {
userId,
displayName: ref.displayName,
trustLevel: ref.$trustLevel,
friendNumber: i + 1
};
this.friendLog.set(userId, friendLogCurrent);
database.setFriendLogCurrent(friendLogCurrent);
this.friendNumber = i + 1;
}
}
};
}

View File

@@ -1,577 +0,0 @@
import * as workerTimers from 'worker-timers';
import { baseClass, $app, API } from './baseClass.js';
import { worldRequest, groupRequest } from '../api';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
_data = {
sharedFeed: {
gameLog: {
wrist: [],
lastEntryDate: ''
},
feedTable: {
wrist: [],
lastEntryDate: ''
},
notificationTable: {
wrist: [],
lastEntryDate: ''
},
friendLogTable: {
wrist: [],
lastEntryDate: ''
},
moderationAgainstTable: {
wrist: [],
lastEntryDate: ''
},
pendingUpdate: false
},
updateSharedFeedTimer: null,
updateSharedFeedPending: false,
updateSharedFeedPendingForceUpdate: false
};
_methods = {
updateSharedFeed(forceUpdate) {
if (!this.friendLogInitStatus) {
return;
}
if (this.updateSharedFeedTimer) {
if (forceUpdate) {
this.updateSharedFeedPendingForceUpdate = true;
}
this.updateSharedFeedPending = true;
} else {
this.updateSharedExecute(forceUpdate);
this.updateSharedFeedTimer = setTimeout(() => {
if (this.updateSharedFeedPending) {
this.updateSharedExecute(
this.updateSharedFeedPendingForceUpdate
);
}
this.updateSharedFeedTimer = null;
}, 150);
}
},
updateSharedExecute(forceUpdate) {
try {
this.updateSharedFeedDebounce(forceUpdate);
} catch (err) {
console.error(err);
}
this.updateSharedFeedTimer = null;
this.updateSharedFeedPending = false;
this.updateSharedFeedPendingForceUpdate = false;
},
updateSharedFeedDebounce(forceUpdate) {
this.updateSharedFeedGameLog(forceUpdate);
this.updateSharedFeedFeedTable(forceUpdate);
this.updateSharedFeedNotificationTable(forceUpdate);
this.updateSharedFeedFriendLogTable(forceUpdate);
this.updateSharedFeedModerationAgainstTable(forceUpdate);
var feeds = this.sharedFeed;
if (!feeds.pendingUpdate) {
return;
}
var wristFeed = [];
wristFeed = wristFeed.concat(
feeds.gameLog.wrist,
feeds.feedTable.wrist,
feeds.notificationTable.wrist,
feeds.friendLogTable.wrist,
feeds.moderationAgainstTable.wrist
);
// OnPlayerJoining/Traveling
API.currentTravelers.forEach((ref) => {
var isFavorite = this.localFavoriteFriends.has(ref.id);
if (
(this.sharedFeedFilters.wrist.OnPlayerJoining ===
'Friends' ||
(this.sharedFeedFilters.wrist.OnPlayerJoining ===
'VIP' &&
isFavorite)) &&
!$app.lastLocation.playerList.has(ref.id)
) {
if (ref.$location.tag === $app.lastLocation.location) {
var feedEntry = {
...ref,
isFavorite,
isFriend: true,
type: 'OnPlayerJoining'
};
wristFeed.unshift(feedEntry);
} else {
var worldRef = API.cachedWorlds.get(
ref.$location.worldId
);
var groupName = '';
if (ref.$location.groupId) {
var groupRef = API.cachedGroups.get(
ref.$location.groupId
);
if (typeof groupRef !== 'undefined') {
groupName = groupRef.name;
} else {
// no group cache, fetch group and try again
groupRequest
.getGroup({
groupId: ref.$location.groupId
})
.then((args) => {
workerTimers.setTimeout(() => {
// delay to allow for group cache to update
$app.sharedFeed.pendingUpdate =
true;
$app.updateSharedFeed(false);
}, 100);
return args;
})
.catch((err) => {
console.error(err);
});
}
}
if (typeof worldRef !== 'undefined') {
var feedEntry = {
created_at: ref.created_at,
type: 'GPS',
userId: ref.id,
displayName: ref.displayName,
location: ref.$location.tag,
worldName: worldRef.name,
groupName,
previousLocation: '',
isFavorite,
time: 0,
isFriend: true,
isTraveling: true
};
wristFeed.unshift(feedEntry);
} else {
// no world cache, fetch world and try again
worldRequest
.getWorld({
worldId: ref.$location.worldId
})
.then((args) => {
workerTimers.setTimeout(() => {
// delay to allow for world cache to update
$app.sharedFeed.pendingUpdate = true;
$app.updateSharedFeed(false);
}, 100);
return args;
})
.catch((err) => {
console.error(err);
});
}
}
}
});
wristFeed.sort(function (a, b) {
if (a.created_at < b.created_at) {
return 1;
}
if (a.created_at > b.created_at) {
return -1;
}
return 0;
});
wristFeed.splice(16);
AppApi.ExecuteVrFeedFunction(
'wristFeedUpdate',
JSON.stringify(wristFeed)
);
this.applyUserDialogLocation();
this.applyWorldDialogInstances();
this.applyGroupDialogInstances();
feeds.pendingUpdate = false;
},
updateSharedFeedGameLog(forceUpdate) {
// Location, OnPlayerJoined, OnPlayerLeft
var sessionTable = this.gameLogSessionTable;
var i = sessionTable.length;
if (i > 0) {
if (
sessionTable[i - 1].created_at ===
this.sharedFeed.gameLog.lastEntryDate &&
forceUpdate === false
) {
return;
}
this.sharedFeed.gameLog.lastEntryDate =
sessionTable[i - 1].created_at;
} else {
return;
}
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
var wristArr = [];
var w = 0;
var wristFilter = this.sharedFeedFilters.wrist;
var currentUserLeaveTime = 0;
var locationJoinTime = 0;
for (var i = sessionTable.length - 1; i > -1; i--) {
var ctx = sessionTable[i];
if (ctx.created_at < bias) {
break;
}
if (ctx.type === 'Notification') {
continue;
}
// on Location change remove OnPlayerLeft
if (ctx.type === 'LocationDestination') {
currentUserLeaveTime = Date.parse(ctx.created_at);
var currentUserLeaveTimeOffset =
currentUserLeaveTime + 5 * 1000;
for (var k = w - 1; k > -1; k--) {
var feedItem = wristArr[k];
if (
(feedItem.type === 'OnPlayerLeft' ||
feedItem.type === 'BlockedOnPlayerLeft' ||
feedItem.type === 'MutedOnPlayerLeft') &&
Date.parse(feedItem.created_at) >=
currentUserLeaveTime &&
Date.parse(feedItem.created_at) <=
currentUserLeaveTimeOffset
) {
wristArr.splice(k, 1);
w--;
}
}
}
// on Location change remove OnPlayerJoined
if (ctx.type === 'Location') {
locationJoinTime = Date.parse(ctx.created_at);
var locationJoinTimeOffset = locationJoinTime + 20 * 1000;
for (var k = w - 1; k > -1; k--) {
var feedItem = wristArr[k];
if (
(feedItem.type === 'OnPlayerJoined' ||
feedItem.type === 'BlockedOnPlayerJoined' ||
feedItem.type === 'MutedOnPlayerJoined') &&
Date.parse(feedItem.created_at) >=
locationJoinTime &&
Date.parse(feedItem.created_at) <=
locationJoinTimeOffset
) {
wristArr.splice(k, 1);
w--;
}
}
}
// remove current user
if (
(ctx.type === 'OnPlayerJoined' ||
ctx.type === 'OnPlayerLeft' ||
ctx.type === 'PortalSpawn') &&
ctx.displayName === API.currentUser.displayName
) {
continue;
}
var isFriend = false;
var isFavorite = false;
if (ctx.userId) {
isFriend = this.friends.has(ctx.userId);
isFavorite = this.localFavoriteFriends.has(ctx.userId);
} else if (ctx.displayName) {
for (var ref of API.cachedUsers.values()) {
if (ref.displayName === ctx.displayName) {
isFriend = this.friends.has(ref.id);
isFavorite = this.localFavoriteFriends.has(ref.id);
break;
}
}
}
// add tag colour
var tagColour = '';
if (ctx.userId) {
var tagRef = this.customUserTags.get(ctx.userId);
if (typeof tagRef !== 'undefined') {
tagColour = tagRef.colour;
}
}
// BlockedOnPlayerJoined, BlockedOnPlayerLeft, MutedOnPlayerJoined, MutedOnPlayerLeft
if (
ctx.type === 'OnPlayerJoined' ||
ctx.type === 'OnPlayerLeft'
) {
for (var ref of API.cachedPlayerModerations.values()) {
if (
ref.targetDisplayName !== ctx.displayName &&
ref.sourceUserId !== ctx.userId
) {
continue;
}
if (ref.type === 'block') {
var type = `Blocked${ctx.type}`;
} else if (ref.type === 'mute') {
var type = `Muted${ctx.type}`;
} else {
continue;
}
var entry = {
created_at: ctx.created_at,
type,
displayName: ref.targetDisplayName,
userId: ref.targetUserId,
isFriend,
isFavorite
};
if (
wristFilter[type] &&
(wristFilter[type] === 'Everyone' ||
(wristFilter[type] === 'Friends' && isFriend) ||
(wristFilter[type] === 'VIP' && isFavorite))
) {
wristArr.unshift(entry);
}
this.queueGameLogNoty(entry);
}
}
// when too many user joins happen at once when switching instances
// the "w" counter maxes out and wont add any more entries
// until the onJoins are cleared by "Location"
// e.g. if a "VideoPlay" occurs between "OnPlayerJoined" and "Location" it wont be added
if (
w < 50 &&
wristFilter[ctx.type] &&
(wristFilter[ctx.type] === 'On' ||
wristFilter[ctx.type] === 'Everyone' ||
(wristFilter[ctx.type] === 'Friends' && isFriend) ||
(wristFilter[ctx.type] === 'VIP' && isFavorite))
) {
wristArr.push({
...ctx,
tagColour,
isFriend,
isFavorite
});
++w;
}
}
this.sharedFeed.gameLog.wrist = wristArr;
this.sharedFeed.pendingUpdate = true;
},
updateSharedFeedFeedTable(forceUpdate) {
// GPS, Online, Offline, Status, Avatar
var feedSession = this.feedSessionTable;
var i = feedSession.length;
if (i > 0) {
if (
feedSession[i - 1].created_at ===
this.sharedFeed.feedTable.lastEntryDate &&
forceUpdate === false
) {
return;
}
this.sharedFeed.feedTable.lastEntryDate =
feedSession[i - 1].created_at;
} else {
return;
}
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
var wristArr = [];
var w = 0;
var wristFilter = this.sharedFeedFilters.wrist;
for (var i = feedSession.length - 1; i > -1; i--) {
var ctx = feedSession[i];
if (ctx.created_at < bias) {
break;
}
if (ctx.type === 'Avatar') {
continue;
}
// hide private worlds from feed
if (
this.hidePrivateFromFeed &&
ctx.type === 'GPS' &&
ctx.location === 'private'
) {
continue;
}
var isFriend = this.friends.has(ctx.userId);
var isFavorite = this.localFavoriteFriends.has(ctx.userId);
if (
w < 20 &&
wristFilter[ctx.type] &&
(wristFilter[ctx.type] === 'Friends' ||
(wristFilter[ctx.type] === 'VIP' && isFavorite))
) {
wristArr.push({
...ctx,
isFriend,
isFavorite
});
++w;
}
}
this.sharedFeed.feedTable.wrist = wristArr;
this.sharedFeed.pendingUpdate = true;
},
updateSharedFeedNotificationTable(forceUpdate) {
// invite, requestInvite, requestInviteResponse, inviteResponse, friendRequest
var notificationTable = this.notificationTable.data;
var i = notificationTable.length;
if (i > 0) {
if (
notificationTable[i - 1].created_at ===
this.sharedFeed.notificationTable.lastEntryDate &&
forceUpdate === false
) {
return;
}
this.sharedFeed.notificationTable.lastEntryDate =
notificationTable[i - 1].created_at;
} else {
return;
}
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
var wristArr = [];
var w = 0;
var wristFilter = this.sharedFeedFilters.wrist;
for (var i = notificationTable.length - 1; i > -1; i--) {
var ctx = notificationTable[i];
if (ctx.created_at < bias) {
break;
}
if (ctx.senderUserId === API.currentUser.id) {
continue;
}
var isFriend = this.friends.has(ctx.senderUserId);
var isFavorite = this.localFavoriteFriends.has(
ctx.senderUserId
);
if (
w < 20 &&
wristFilter[ctx.type] &&
(wristFilter[ctx.type] === 'On' ||
wristFilter[ctx.type] === 'Friends' ||
(wristFilter[ctx.type] === 'VIP' && isFavorite))
) {
wristArr.push({
...ctx,
isFriend,
isFavorite
});
++w;
}
}
this.sharedFeed.notificationTable.wrist = wristArr;
this.sharedFeed.pendingUpdate = true;
},
updateSharedFeedFriendLogTable(forceUpdate) {
// TrustLevel, Friend, FriendRequest, Unfriend, DisplayName
var friendLog = this.friendLogTable.data;
var i = friendLog.length;
if (i > 0) {
if (
friendLog[i - 1].created_at ===
this.sharedFeed.friendLogTable.lastEntryDate &&
forceUpdate === false
) {
return;
}
this.sharedFeed.friendLogTable.lastEntryDate =
friendLog[i - 1].created_at;
} else {
return;
}
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
var wristArr = [];
var w = 0;
var wristFilter = this.sharedFeedFilters.wrist;
for (var i = friendLog.length - 1; i > -1; i--) {
var ctx = friendLog[i];
if (ctx.created_at < bias) {
break;
}
if (ctx.type === 'FriendRequest') {
continue;
}
var isFriend = this.friends.has(ctx.userId);
var isFavorite = this.localFavoriteFriends.has(ctx.userId);
if (
w < 20 &&
wristFilter[ctx.type] &&
(wristFilter[ctx.type] === 'On' ||
wristFilter[ctx.type] === 'Friends' ||
(wristFilter[ctx.type] === 'VIP' && isFavorite))
) {
wristArr.push({
...ctx,
isFriend,
isFavorite
});
++w;
}
}
this.sharedFeed.friendLogTable.wrist = wristArr;
this.sharedFeed.pendingUpdate = true;
},
updateSharedFeedModerationAgainstTable(forceUpdate) {
// Unblocked, Blocked, Muted, Unmuted
var moderationAgainst = this.moderationAgainstTable;
var i = moderationAgainst.length;
if (i > 0) {
if (
moderationAgainst[i - 1].created_at ===
this.sharedFeed.moderationAgainstTable.lastEntryDate &&
forceUpdate === false
) {
return;
}
this.sharedFeed.moderationAgainstTable.lastEntryDate =
moderationAgainst[i - 1].created_at;
} else {
return;
}
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
var wristArr = [];
var w = 0;
var wristFilter = this.sharedFeedFilters.wrist;
for (var i = moderationAgainst.length - 1; i > -1; i--) {
var ctx = moderationAgainst[i];
if (ctx.created_at < bias) {
break;
}
var isFriend = this.friends.has(ctx.userId);
var isFavorite = this.localFavoriteFriends.has(ctx.userId);
// add tag colour
var tagColour = '';
var tagRef = this.customUserTags.get(ctx.userId);
if (typeof tagRef !== 'undefined') {
tagColour = tagRef.colour;
}
if (
w < 20 &&
wristFilter[ctx.type] &&
wristFilter[ctx.type] === 'On'
) {
wristArr.push({
...ctx,
isFriend,
isFavorite,
tagColour
});
++w;
}
}
this.sharedFeed.moderationAgainstTable.wrist = wristArr;
this.sharedFeed.pendingUpdate = true;
}
};
}

View File

@@ -1,512 +0,0 @@
import Vue from 'vue';
import VueMarkdown from 'vue-markdown';
import { instanceRequest, userRequest } from '../api';
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) {
super(_app, _API, _t);
}
init() {
Vue.component('vue-markdown', VueMarkdown);
Vue.component('launch', {
template:
'<el-tooltip placement="top" :content="$t(`dialog.user.info.launch_invite_tooltip`)" :disabled="hideTooltips"><el-button @click="confirm" size="mini" icon="el-icon-switch-button" circle></el-button></el-tooltip>',
props: {
location: String,
hideTooltips: Boolean
},
methods: {
parse() {
this.$el.style.display = $app.checkCanInviteSelf(
this.location
)
? ''
: 'none';
},
confirm() {
this.$emit('show-launch-dialog', this.location);
}
},
watch: {
location() {
this.parse();
}
},
mounted() {
this.parse();
}
});
Vue.component('invite-yourself', {
template:
'<el-button @click="confirm" size="mini" icon="el-icon-message" circle></el-button>',
props: {
location: String,
shortname: String
},
methods: {
parse() {
this.$el.style.display = $app.checkCanInviteSelf(
this.location
)
? ''
: 'none';
},
confirm() {
this.selfInvite(this.location, this.shortname);
},
selfInvite(location, shortName) {
const L = parseLocation(location);
if (!L.isRealInstance) {
return;
}
instanceRequest
.selfInvite({
instanceId: L.instanceId,
worldId: L.worldId,
shortName
})
.then((args) => {
this.$message({
message: 'Self invite sent',
type: 'success'
});
return args;
});
}
},
watch: {
location() {
this.parse();
}
},
mounted() {
this.parse();
}
});
Vue.component('location-world', {
template:
'<span><span @click="showLaunchDialog" class="x-link">' +
'<i v-if="isUnlocked" class="el-icon el-icon-unlock" style="display:inline-block;margin-right:5px"></i>' +
'<span>#{{ instanceName }} {{ accessTypeName }}</span></span>' +
'<span v-if="groupName" @click="showGroupDialog" class="x-link">({{ groupName }})</span>' +
'<span class="flags" :class="region" style="display:inline-block;margin-left:5px"></span>' +
'<i v-if="strict" class="el-icon el-icon-lock" style="display:inline-block;margin-left:5px"></i></span>',
props: {
locationobject: Object,
currentuserid: String,
worlddialogshortname: String,
grouphint: {
type: String,
default: ''
}
},
data() {
return {
location: this.location,
instanceName: this.instanceName,
accessTypeName: this.accessTypeName,
region: this.region,
shortName: this.shortName,
isUnlocked: this.isUnlocked,
strict: this.strict,
groupName: this.groupName
};
},
methods: {
parse() {
this.location = this.locationobject.tag;
this.instanceName = this.locationobject.instanceName;
this.accessTypeName = this.locationobject.accessTypeName;
this.strict = this.locationobject.strict;
this.shortName = this.locationobject.shortName;
this.isUnlocked = false;
if (
(this.worlddialogshortname &&
this.locationobject.shortName &&
this.worlddialogshortname ===
this.locationobject.shortName) ||
this.currentuserid === this.locationobject.userId
) {
this.isUnlocked = true;
}
this.region = this.locationobject.region;
if (!this.region) {
this.region = 'us';
}
this.groupName = '';
if (this.grouphint) {
this.groupName = this.grouphint;
} else if (this.locationobject.groupId) {
this.groupName = this.locationobject.groupId;
$app.getGroupName(this.locationobject.groupId).then(
(groupName) => {
this.groupName = groupName;
}
);
}
},
showLaunchDialog() {
this.$emit(
'show-launch-dialog',
this.location,
this.shortName
);
},
showGroupDialog() {
if (!this.location) {
return;
}
var L = parseLocation(this.location);
if (!L.groupId) {
return;
}
$app.showGroupDialog(L.groupId);
}
},
watch: {
locationobject() {
this.parse();
}
},
created() {
this.parse();
}
});
Vue.component('last-join', {
template:
'<span v-if="lastJoin">' +
'<el-tooltip placement="top" style="margin-left:5px" >' +
'<div slot="content">' +
'<span>{{ $t("dialog.user.info.last_join") }} <timer :epoch="lastJoin"></timer></span>' +
'</div>' +
'<i class="el-icon el-icon-location-outline" style="display:inline-block"></i>' +
'</el-tooltip>' +
'</span>',
props: {
location: String,
currentlocation: String
},
data() {
return {
lastJoin: this.lastJoin
};
},
methods: {
parse() {
this.lastJoin = $app.instanceJoinHistory.get(this.location);
}
},
watch: {
location() {
this.parse();
},
currentlocation() {
this.parse();
}
},
created() {
this.parse();
}
});
Vue.component('instance-info', {
template:
'<div style="display:inline-block;margin-left:5px">' +
'<el-tooltip v-if="isValidInstance" placement="bottom">' +
'<div slot="content">' +
'<template v-if="isClosed"><span>Closed At: {{ closedAt | formatDate(\'long\') }}</span></br></template>' +
'<template v-if="canCloseInstance"><el-button :disabled="isClosed" size="mini" type="primary" @click="$root.closeInstance(location)">{{ $t("dialog.user.info.close_instance") }}</el-button></br></br></template>' +
'<span><span style="color:#409eff">PC: </span>{{ platforms.standalonewindows }}</span></br>' +
'<span><span style="color:#67c23a">Android: </span>{{ platforms.android }}</span></br>' +
'<span>{{ $t("dialog.user.info.instance_game_version") }} {{ gameServerVersion }}</span></br>' +
'<span v-if="queueEnabled">{{ $t("dialog.user.info.instance_queuing_enabled") }}</br></span>' +
'<span v-if="disabledContentSettings">{{ $t("dialog.user.info.instance_disabled_content") }} {{ disabledContentSettings }}</br></span>' +
'<span v-if="userList.length">{{ $t("dialog.user.info.instance_users") }}</br></span>' +
'<template v-for="user in userList"><span style="cursor:pointer;margin-right:5px" @click="showUserDialog(user.id)" v-text="user.displayName"></span></template>' +
'</div>' +
'<i class="el-icon-caret-bottom"></i>' +
'</el-tooltip>' +
'<span v-if="occupants" style="margin-left:5px">{{ occupants }}/{{ capacity }}</span>' +
'<span v-if="friendcount" style="margin-left:5px">({{ friendcount }})</span>' +
'<span v-if="isFull" style="margin-left:5px;color:lightcoral">{{ $t("dialog.user.info.instance_full") }}</span>' +
'<span v-if="isHardClosed" style="margin-left:5px;color:lightcoral">{{ $t("dialog.user.info.instance_hard_closed") }}</span>' +
'<span v-else-if="isClosed" style="margin-left:5px;color:lightcoral">{{ $t("dialog.user.info.instance_closed") }}</span>' +
'<span v-if="queueSize" style="margin-left:5px">{{ $t("dialog.user.info.instance_queue") }} {{ queueSize }}</span>' +
'<span v-if="isAgeGated" style="margin-left:5px;color:lightcoral">{{ $t("dialog.user.info.instance_age_gated") }}</span>' +
'</div>',
props: {
location: String,
instance: Object,
friendcount: Number,
updateelement: Number
},
data() {
return {
isValidInstance: this.isValidInstance,
isFull: this.isFull,
isClosed: this.isClosed,
isHardClosed: this.isHardClosed,
closedAt: this.closedAt,
occupants: this.occupants,
capacity: this.capacity,
queueSize: this.queueSize,
queueEnabled: this.queueEnabled,
platforms: this.platforms,
userList: this.userList,
gameServerVersion: this.gameServerVersion,
canCloseInstance: this.canCloseInstance
};
},
methods: {
parse() {
this.isValidInstance = false;
this.isFull = false;
this.isClosed = false;
this.isHardClosed = false;
this.closedAt = '';
this.occupants = 0;
this.capacity = 0;
this.queueSize = 0;
this.queueEnabled = false;
this.platforms = [];
this.userList = [];
this.gameServerVersion = '';
this.canCloseInstance = false;
this.isAgeGated = false;
this.disabledContentSettings = '';
if (
!this.location ||
!this.instance ||
Object.keys(this.instance).length === 0
) {
return;
}
this.isValidInstance = true;
this.isFull =
typeof this.instance.hasCapacityForYou !==
'undefined' && !this.instance.hasCapacityForYou;
if (this.instance.closedAt) {
this.isClosed = true;
this.closedAt = this.instance.closedAt;
}
this.isHardClosed = this.instance.hardClose === true;
this.occupants = this.instance.userCount;
if (this.location === $app.lastLocation.location) {
// use gameLog for occupants when in same location
this.occupants = $app.lastLocation.playerList.size;
}
this.capacity = this.instance.capacity;
this.gameServerVersion = this.instance.gameServerVersion;
this.queueSize = this.instance.queueSize;
if (this.instance.platforms) {
this.platforms = this.instance.platforms;
}
if (this.instance.users) {
this.userList = this.instance.users;
}
if (this.instance.ownerId === API.currentUser.id) {
this.canCloseInstance = true;
} else if (this.instance?.ownerId?.startsWith('grp_')) {
// check group perms
var groupId = this.instance.ownerId;
var group = API.cachedGroups.get(groupId);
this.canCloseInstance = hasGroupPermission(
group,
'group-instance-moderate'
);
}
this.isAgeGated = this.instance.ageGate === true;
if (this.location && this.location.includes('~ageGate')) {
// dumb workaround for API not returning `ageGate`
this.isAgeGated = true;
}
if (
this.instance.$disabledContentSettings &&
this.instance.$disabledContentSettings.length
) {
this.disabledContentSettings =
this.instance.$disabledContentSettings.join(', ');
}
},
showUserDialog(userId) {
this.showUserDialog(userId);
}
},
watch: {
updateelement() {
this.parse();
},
location() {
this.parse();
},
friendcount() {
this.parse();
}
},
created() {
this.parse();
}
});
Vue.component('avatar-info', {
template:
'<div @click="confirm" class="avatar-info">' +
'<span style="margin-right:5px">{{ avatarName }}</span>' +
'<span v-if="avatarType" style="margin-right:5px" :class="color">{{ avatarType }}</span>' +
'<span v-if="avatarTags" style="color:#909399;font-family:monospace;font-size:12px;">{{ avatarTags }}</span>' +
'</div>',
props: {
imageurl: String,
userid: String,
hintownerid: String,
hintavatarname: String,
avatartags: Array
},
data() {
return {
avatarName: this.avatarName,
avatarType: this.avatarType,
avatarTags: this.avatarTags,
color: this.color
};
},
methods: {
async parse() {
this.ownerId = '';
this.avatarName = '';
this.avatarType = '';
this.color = '';
this.avatarTags = '';
if (!this.imageurl) {
this.avatarName = '-';
} else if (this.hintownerid) {
this.avatarName = this.hintavatarname;
this.ownerId = this.hintownerid;
} else {
try {
var avatarInfo = await $app.getAvatarName(
this.imageurl
);
this.avatarName = avatarInfo.avatarName;
this.ownerId = avatarInfo.ownerId;
} catch (err) {}
}
if (typeof this.userid === 'undefined' || !this.ownerId) {
this.color = '';
this.avatarType = '';
} else if (this.ownerId === this.userid) {
this.color = 'avatar-info-own';
this.avatarType = '(own)';
} else {
this.color = 'avatar-info-public';
this.avatarType = '(public)';
}
if (typeof this.avatartags === 'object') {
var tagString = '';
for (var i = 0; i < this.avatartags.length; i++) {
var tagName = this.avatartags[i].replace(
'content_',
''
);
tagString += tagName;
if (i < this.avatartags.length - 1) {
tagString += ', ';
}
}
this.avatarTags = tagString;
}
},
confirm() {
if (!this.imageurl) {
return;
}
$app.showAvatarAuthorDialog(
this.userid,
this.ownerId,
this.imageurl
);
}
},
watch: {
imageurl() {
this.parse();
},
userid() {
this.parse();
},
avatartags() {
this.parse();
}
},
mounted() {
this.parse();
}
});
Vue.component('display-name', {
template:
'<span @click="showUserDialog" class="x-link">{{ username }}</span>',
props: {
userid: String,
location: String,
forceUpdateKey: Number,
hint: {
type: String,
default: ''
}
},
data() {
return {
username: this.username
};
},
methods: {
async parse() {
this.username = this.userid;
if (this.hint) {
this.username = this.hint;
} else if (this.userid) {
var args = await userRequest.getCachedUser({
userId: this.userid
});
}
if (
typeof args !== 'undefined' &&
typeof args.json !== 'undefined' &&
typeof args.json.displayName !== 'undefined'
) {
this.username = args.json.displayName;
}
},
showUserDialog() {
$app.showUserDialog(this.userid);
}
},
watch: {
location() {
this.parse();
},
forceUpdateKey() {
this.parse();
},
userid() {
this.parse();
}
},
mounted() {
this.parse();
}
});
}
}

View File

@@ -1,122 +0,0 @@
import * as workerTimers from 'worker-timers';
import { baseClass, $app, API } from './baseClass.js';
import { groupRequest } from '../api/index.js';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {
API.$on('LOGIN', function () {
$app.nextCurrentUserRefresh = 300;
$app.nextFriendsRefresh = 3600;
$app.nextGroupInstanceRefresh = 0;
});
}
_data = {
nextCurrentUserRefresh: 300,
nextFriendsRefresh: 3600,
nextGroupInstanceRefresh: 0,
nextAppUpdateCheck: 3600,
ipcTimeout: 0,
nextClearVRCXCacheCheck: 0,
nextDiscordUpdate: 0,
nextAutoStateChange: 0,
nextGetLogCheck: 0,
nextGameRunningCheck: 0,
nextDatabaseOptimize: 3600
};
_methods = {
async updateLoop() {
try {
if (API.isLoggedIn === true) {
if (--this.nextCurrentUserRefresh <= 0) {
this.nextCurrentUserRefresh = 300; // 5min
API.getCurrentUser();
}
if (--this.nextFriendsRefresh <= 0) {
this.nextFriendsRefresh = 3600; // 1hour
this.refreshFriendsList();
this.updateStoredUser(API.currentUser);
if (this.isGameRunning) {
API.refreshPlayerModerations();
}
}
if (--this.nextGroupInstanceRefresh <= 0) {
if (this.friendLogInitStatus) {
this.nextGroupInstanceRefresh = 300; // 5min
groupRequest.getUsersGroupInstances();
}
AppApi.CheckGameRunning();
}
if (--this.nextAppUpdateCheck <= 0) {
this.nextAppUpdateCheck = 3600; // 1hour
if (this.autoUpdateVRCX !== 'Off') {
this.checkForVRCXUpdate();
}
}
if (--this.ipcTimeout <= 0) {
this.ipcEnabled = false;
}
if (
--this.nextClearVRCXCacheCheck <= 0 &&
this.clearVRCXCacheFrequency > 0
) {
this.nextClearVRCXCacheCheck =
this.clearVRCXCacheFrequency / 2;
this.clearVRCXCache();
}
if (--this.nextDiscordUpdate <= 0) {
this.nextDiscordUpdate = 3;
if (this.discordActive) {
this.updateDiscord();
}
}
if (--this.nextAutoStateChange <= 0) {
this.nextAutoStateChange = 3;
this.updateAutoStateChange();
}
if (
(this.isRunningUnderWine || LINUX) &&
--this.nextGetLogCheck <= 0
) {
this.nextGetLogCheck = 0.5;
const logLines = await LogWatcher.GetLogLines();
if (logLines) {
logLines.forEach((logLine) => {
$app.addGameLogEvent(logLine);
});
}
}
if (
(this.isRunningUnderWine || LINUX) &&
--this.nextGameRunningCheck <= 0
) {
if (LINUX) {
this.nextGameRunningCheck = 1;
$app.updateIsGameRunning(
await AppApi.IsGameRunning(),
await AppApi.IsSteamVRRunning(),
false
);
} else {
this.nextGameRunningCheck = 3;
AppApi.CheckGameRunning();
}
}
if (--this.nextDatabaseOptimize <= 0) {
this.nextDatabaseOptimize = 86400; // 1 day
database.optimize();
}
}
} catch (err) {
API.isRefreshFriendsLoading = false;
console.error(err);
}
workerTimers.setTimeout(() => this.updateLoop(), 1000);
}
};
}

View File

@@ -1,118 +0,0 @@
import { userRequest } from '../api';
import database from '../service/database.js';
import utils from '../classes/utils';
import * as workerTimers from 'worker-timers';
const userNotes = {
lastNoteCheck: null,
lastDbNoteDate: null,
notes: new Map(),
async init() {
this.lastNoteCheck = new Date();
this.lastDbNoteDate = null;
this.notes.clear();
try {
// todo: get users from store
const users = window.API.cachedUsers;
const dbNotes = await database.getAllUserNotes();
for (const note of dbNotes) {
this.notes.set(note.userId, note.note);
const user = users.get(note.userId);
if (user) {
user.note = note.note;
}
if (
!this.lastDbNoteDate ||
this.lastDbNoteDate < note.createdAt
) {
this.lastDbNoteDate = note.createdAt;
}
}
await this.getLatestUserNotes();
} catch (error) {
console.error('Error initializing user notes:', error);
}
},
async getLatestUserNotes() {
this.lastNoteCheck = new Date();
const params = {
offset: 0,
n: 10 // start light
};
const newNotes = new Map();
let done = false;
try {
for (let i = 0; i < 100; i++) {
params.offset = i * params.n;
const args = await userRequest.getUserNotes(params);
for (const note of args.json) {
if (
this.lastDbNoteDate &&
this.lastDbNoteDate > note.createdAt
) {
done = true;
}
if (
!this.lastDbNoteDate ||
this.lastDbNoteDate < note.createdAt
) {
this.lastDbNoteDate = note.createdAt;
}
note.note = utils.replaceBioSymbols(note.note);
newNotes.set(note.targetUserId, note);
}
if (done || args.json.length === 0) {
break;
}
params.n = 100; // crank it after first run
await new Promise((resolve) => {
workerTimers.setTimeout(resolve, 1000);
});
}
} catch (error) {
console.error('Error fetching user notes:', error);
}
// todo: get users from store
const users = window.API.cachedUsers;
for (const note of newNotes.values()) {
const newNote = {
userId: note.targetUserId,
displayName: note.targetUser?.displayName || note.targetUserId,
note: note.note,
createdAt: note.createdAt
};
await database.addUserNote(newNote);
this.notes.set(note.targetUserId, note.note);
const user = users.get(note.targetUserId);
if (user) {
user.note = note.note;
}
}
},
async checkNote(userId, newNote) {
// last check was more than than 5 minutes ago
if (
!this.lastNoteCheck ||
this.lastNoteCheck.getTime() + 5 * 60 * 1000 > Date.now()
) {
return;
}
const existingNote = this.notes.get(userId);
if (typeof existingNote !== 'undefined' && !newNote) {
console.log('deleting note', userId);
this.notes.delete(userId);
await database.deleteUserNote(userId);
return;
}
if (typeof existingNote === 'undefined' || existingNote !== newNote) {
console.log('detected note change', userId, newNote);
await this.getLatestUserNotes();
}
}
};
export { userNotes };

View File

@@ -1,302 +0,0 @@
let echarts = null;
const _utils = {
removeFromArray(array, item) {
var { length } = array;
for (var i = 0; i < length; ++i) {
if (array[i] === item) {
array.splice(i, 1);
return true;
}
}
return false;
},
arraysMatch(a, b) {
if (!Array.isArray(a) || !Array.isArray(b)) {
return false;
}
return (
a.length === b.length &&
a.every(
(element, index) =>
JSON.stringify(element) === JSON.stringify(b[index])
)
);
},
moveArrayItem(array, fromIndex, toIndex) {
if (!Array.isArray(array) || fromIndex === toIndex) {
return;
}
if (fromIndex < 0 || fromIndex >= array.length) {
return;
}
if (toIndex < 0 || toIndex >= array.length) {
return;
}
const item = array[fromIndex];
array.splice(fromIndex, 1);
array.splice(toIndex, 0, item);
},
escapeTag(tag) {
var s = String(tag);
return s.replace(/["&'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
},
escapeTagRecursive(obj) {
if (typeof obj === 'string') {
return this.escapeTag(obj);
}
if (typeof obj === 'object') {
for (var key in obj) {
obj[key] = this.escapeTagRecursive(obj[key]);
}
}
return obj;
},
timeToText(sec, isNeedSeconds = false) {
let n = Number(sec);
if (isNaN(n)) {
return this.escapeTag(sec);
}
n = Math.floor(n / 1000);
const arr = [];
if (n < 0) {
n = -n;
}
if (n >= 86400) {
arr.push(`${Math.floor(n / 86400)}d`);
n %= 86400;
}
if (n >= 3600) {
arr.push(`${Math.floor(n / 3600)}h`);
n %= 3600;
}
if (n >= 60) {
arr.push(`${Math.floor(n / 60)}m`);
n %= 60;
}
if (isNeedSeconds || (arr.length === 0 && n < 60)) {
arr.push(`${n}s`);
}
return arr.join(' ');
},
textToHex(text) {
var s = String(text);
return s
.split('')
.map((c) => c.charCodeAt(0).toString(16))
.join(' ');
},
commaNumber(num) {
if (!num) {
return '0';
}
var s = String(Number(num));
return s.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
},
buildTreeData(json) {
var node = [];
for (var key in json) {
if (key[0] === '$') {
continue;
}
var value = json[key];
if (Array.isArray(value) && value.length === 0) {
node.push({
key,
value: '[]'
});
} else if (
value === Object(value) &&
Object.keys(value).length === 0
) {
node.push({
key,
value: '{}'
});
} else if (Array.isArray(value)) {
node.push({
children: value.map((val, idx) => {
if (val === Object(val)) {
return {
children: this.buildTreeData(val),
key: idx
};
}
return {
key: idx,
value: val
};
}),
key
});
} else if (value === Object(value)) {
node.push({
children: this.buildTreeData(value),
key
});
} else {
node.push({
key,
value: String(value)
});
}
}
node.sort(function (a, b) {
var A = String(a.key).toUpperCase();
var B = String(b.key).toUpperCase();
if (A < B) {
return -1;
}
if (A > B) {
return 1;
}
return 0;
});
return node;
},
// descending
compareByCreatedAt(a, b) {
if (
typeof a.created_at !== 'string' ||
typeof b.created_at !== 'string'
) {
return 0;
}
var A = a.created_at.toUpperCase();
var B = b.created_at.toUpperCase();
if (A < B) {
return 1;
}
if (A > B) {
return -1;
}
return 0;
},
// lazy load echarts
loadEcharts() {
if (echarts) {
return Promise.resolve(echarts);
}
return import('echarts').then((module) => {
echarts = module;
return echarts;
});
},
// CJK character in Japanese, Korean, Chinese are different
// so change font-family order when users change language to display CJK character correctly
changeCJKorder(lang) {
const otherFonts = window
.getComputedStyle(document.body)
.fontFamily.split(',')
.filter((item) => !item.includes('Noto Sans'))
.join(', ');
const notoSans = 'Noto Sans';
const fontFamilies = {
ja_JP: ['JP', 'KR', 'TC', 'SC'],
ko: ['KR', 'JP', 'TC', 'SC'],
zh_TW: ['TC', 'JP', 'KR', 'SC'],
zh_CN: ['SC', 'JP', 'KR', 'TC']
};
if (fontFamilies[lang]) {
const CJKFamily = fontFamilies[lang]
.map((item) => `${notoSans} ${item}`)
.join(', ');
document.body.style.fontFamily = `${CJKFamily}, ${otherFonts}`;
}
},
localeIncludes(str, search, comparer) {
// These checks are stolen from https://stackoverflow.com/a/69623589/11030436
if (search === '') {
return true;
} else if (!str || !search) {
return false;
}
const strObj = String(str);
const searchObj = String(search);
if (strObj.length === 0) {
return false;
}
if (searchObj.length > strObj.length) {
return false;
}
// Now simply loop through each substring and compare them
for (let i = 0; i < str.length - searchObj.length + 1; i++) {
const substr = strObj.substring(i, i + searchObj.length);
if (comparer.compare(substr, searchObj) === 0) {
return true;
}
}
return false;
},
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 '';
}
var symbolList = {
'@': '',
'#': '',
$: '',
'%': '',
'&': '',
'=': '',
'+': '',
'/': '',
'\\': '',
';': ';',
':': '˸',
',': '',
'?': '',
'!': 'ǃ',
'"': '',
'<': '≺',
'>': '≻',
'.': '',
'^': '',
'{': '',
'}': '',
'[': '',
']': '',
'(': '',
')': '',
'|': '',
'*': ''
};
var newText = text;
for (var key in symbolList) {
var regex = new RegExp(symbolList[key], 'g');
newText = newText.replace(regex, key);
}
return newText.replace(/ {1,}/g, ' ').trimRight();
},
// descending
compareByUpdatedAt(a, b) {
if (
typeof a.updated_at !== 'string' ||
typeof b.updated_at !== 'string'
) {
return 0;
}
var A = a.updated_at.toUpperCase();
var B = b.updated_at.toUpperCase();
if (A < B) {
return 1;
}
if (A > B) {
return -1;
}
return 0;
}
};
export default _utils;

View File

@@ -1,121 +0,0 @@
import configRepository from '../service/config.js';
import { baseClass, $t, $utils } from './baseClass.js';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {}
_data = {};
_methods = {
async backupVrcRegistry(name) {
var regJson;
if (LINUX) {
regJson = await AppApi.GetVRChatRegistryJson();
regJson = JSON.parse(regJson);
} else {
regJson = await AppApi.GetVRChatRegistry();
}
var newBackup = {
name,
date: new Date().toJSON(),
data: regJson
};
var backupsJson = await configRepository.getString(
'VRCX_VRChatRegistryBackups'
);
if (!backupsJson) {
backupsJson = JSON.stringify([]);
}
var backups = JSON.parse(backupsJson);
backups.push(newBackup);
await configRepository.setString(
'VRCX_VRChatRegistryBackups',
JSON.stringify(backups)
);
// await this.updateRegistryBackupDialog();
},
// Because it is a startup func, it is not integrated into RegistryBackupDialog.vue now
// func backupVrcRegistry is also split up
async checkAutoBackupRestoreVrcRegistry() {
if (!this.vrcRegistryAutoBackup) {
return;
}
// check for auto restore
var hasVRChatRegistryFolder =
await AppApi.HasVRChatRegistryFolder();
if (!hasVRChatRegistryFolder) {
var lastBackupDate = await configRepository.getString(
'VRCX_VRChatRegistryLastBackupDate'
);
var lastRestoreCheck = await configRepository.getString(
'VRCX_VRChatRegistryLastRestoreCheck'
);
if (
!lastBackupDate ||
(lastRestoreCheck &&
lastBackupDate &&
lastRestoreCheck === lastBackupDate)
) {
// only ask to restore once and when backup is present
return;
}
// popup message about auto restore
this.$alert(
$t('dialog.registry_backup.restore_prompt'),
$t('dialog.registry_backup.header')
);
this.showRegistryBackupDialog();
await AppApi.FocusWindow();
await configRepository.setString(
'VRCX_VRChatRegistryLastRestoreCheck',
lastBackupDate
);
} else {
await this.autoBackupVrcRegistry();
}
},
async autoBackupVrcRegistry() {
var date = new Date();
var lastBackupDate = await configRepository.getString(
'VRCX_VRChatRegistryLastBackupDate'
);
if (lastBackupDate) {
var lastBackup = new Date(lastBackupDate);
var diff = date.getTime() - lastBackup.getTime();
var diffDays = Math.floor(diff / (1000 * 60 * 60 * 24));
if (diffDays < 7) {
return;
}
}
var backupsJson = await configRepository.getString(
'VRCX_VRChatRegistryBackups'
);
if (!backupsJson) {
backupsJson = JSON.stringify([]);
}
var backups = JSON.parse(backupsJson);
backups.forEach((backup) => {
if (backup.name === 'Auto Backup') {
// remove old auto backup
$utils.removeFromArray(backups, backup);
}
});
await configRepository.setString(
'VRCX_VRChatRegistryBackups',
JSON.stringify(backups)
);
this.backupVrcRegistry('Auto Backup');
await configRepository.setString(
'VRCX_VRChatRegistryLastBackupDate',
date.toJSON()
);
}
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,351 +0,0 @@
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
import * as workerTimers from 'worker-timers';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
_data = {
VRCXUpdateDialog: {
visible: false,
updatePending: false,
updatePendingIsLatest: false,
release: '',
releases: [],
json: {}
},
branch: 'Stable',
autoUpdateVRCX: 'Auto Download',
checkingForVRCXUpdate: false,
pendingVRCXInstall: '',
pendingVRCXUpdate: false,
branches: {
Stable: {
name: 'Stable',
urlReleases: 'https://api0.vrcx.app/releases/stable',
urlLatest: 'https://api0.vrcx.app/releases/stable/latest'
},
Nightly: {
name: 'Nightly',
urlReleases: 'https://api0.vrcx.app/releases/nightly',
urlLatest: 'https://api0.vrcx.app/releases/nightly/latest'
}
// LinuxTest: {
// name: 'LinuxTest',
// urlReleases: 'https://api.github.com/repos/rs189/VRCX/releases',
// urlLatest:
// 'https://api.github.com/repos/rs189/VRCX/releases/latest'
// }
},
updateProgress: 0,
updateInProgress: false
};
_methods = {
async showVRCXUpdateDialog() {
var D = this.VRCXUpdateDialog;
D.visible = true;
D.updatePendingIsLatest = false;
D.updatePending = await AppApi.CheckForUpdateExe();
this.loadBranchVersions();
},
async downloadVRCXUpdate(
downloadUrl,
downloadName,
hashUrl,
size,
releaseName,
type
) {
if (this.updateInProgress) {
return;
}
try {
this.updateInProgress = true;
this.downloadFileProgress();
await AppApi.DownloadUpdate(
downloadUrl,
downloadName,
hashUrl,
size
);
this.pendingVRCXInstall = releaseName;
} catch (err) {
console.error(err);
this.$message({
message: `${$t('message.vrcx_updater.failed_install')} ${err}`,
type: 'error'
});
} finally {
this.updateInProgress = false;
this.updateProgress = 0;
}
},
async cancelUpdate() {
await AppApi.CancelUpdate();
this.updateInProgress = false;
this.updateProgress = 0;
},
async downloadFileProgress() {
this.updateProgress = await AppApi.CheckUpdateProgress();
if (this.updateInProgress) {
workerTimers.setTimeout(() => this.downloadFileProgress(), 150);
}
},
updateProgressText() {
if (this.updateProgress === 100) {
return $t('message.vrcx_updater.checking_hash');
}
return `${this.updateProgress}%`;
},
installVRCXUpdate() {
for (var release of this.VRCXUpdateDialog.releases) {
if (release.name !== this.VRCXUpdateDialog.release) {
continue;
}
var downloadUrl = '';
var downloadName = '';
var hashUrl = '';
var size = 0;
for (var asset of release.assets) {
if (asset.state !== 'uploaded') {
continue;
}
if (
WINDOWS &&
(asset.content_type === 'application/x-msdownload' ||
asset.content_type ===
'application/x-msdos-program')
) {
downloadUrl = asset.browser_download_url;
downloadName = asset.name;
size = asset.size;
continue;
}
if (
LINUX &&
asset.content_type === 'application/octet-stream'
) {
downloadUrl = asset.browser_download_url;
downloadName = asset.name;
size = asset.size;
continue;
}
if (
asset.name === 'SHA256SUMS.txt' &&
asset.content_type === 'text/plain'
) {
hashUrl = asset.browser_download_url;
continue;
}
}
if (!downloadUrl) {
return;
}
var releaseName = release.name;
var type = 'Manual';
this.downloadVRCXUpdate(
downloadUrl,
downloadName,
hashUrl,
size,
releaseName,
type
);
break;
}
},
async loadBranchVersions() {
var D = this.VRCXUpdateDialog;
var url = this.branches[this.branch].urlReleases;
this.checkingForVRCXUpdate = true;
try {
var response = await webApiService.execute({
url,
method: 'GET',
headers: {
'VRCX-ID': this.vrcxId
}
});
} finally {
this.checkingForVRCXUpdate = false;
}
var json = JSON.parse(response.data);
if (this.debugWebRequests) {
console.log(json, response);
}
var releases = [];
if (typeof json !== 'object' || json.message) {
$app.$message({
message: $t('message.vrcx_updater.failed', {
message: json.message
}),
type: 'error'
});
return;
}
for (var release of json) {
for (var asset of release.assets) {
if (
(asset.content_type === 'application/x-msdownload' ||
asset.content_type ===
'application/x-msdos-program') &&
asset.state === 'uploaded'
) {
releases.push(release);
}
}
}
D.releases = releases;
D.release = json[0].name;
this.VRCXUpdateDialog.updatePendingIsLatest = false;
if (D.release === this.pendingVRCXInstall) {
// update already downloaded and latest version
this.VRCXUpdateDialog.updatePendingIsLatest = true;
}
if (
(await configRepository.getString('VRCX_branch')) !==
this.branch
) {
await configRepository.setString('VRCX_branch', this.branch);
}
},
async checkForVRCXUpdate() {
var currentVersion = this.appVersion.replace(' (Linux)', '');
if (
!currentVersion ||
currentVersion === 'VRCX Nightly Build' ||
currentVersion === 'VRCX Build'
) {
// ignore custom builds
return;
}
if (this.branch === 'Beta') {
// move Beta users to stable
this.branch = 'Stable';
await configRepository.setString('VRCX_branch', this.branch);
}
if (typeof this.branches[this.branch] === 'undefined') {
// handle invalid branch
this.branch = 'Stable';
await configRepository.setString('VRCX_branch', this.branch);
}
var url = this.branches[this.branch].urlLatest;
this.checkingForVRCXUpdate = true;
try {
var response = await webApiService.execute({
url,
method: 'GET',
headers: {
'VRCX-ID': this.vrcxId
}
});
} finally {
this.checkingForVRCXUpdate = false;
}
this.pendingVRCXUpdate = false;
var json = JSON.parse(response.data);
if (this.debugWebRequests) {
console.log(json, response);
}
if (json === Object(json) && json.name && json.published_at) {
this.VRCXUpdateDialog.updateJson = json;
this.changeLogDialog.buildName = json.name;
this.changeLogDialog.changeLog = this.changeLogRemoveLinks(
json.body
);
var releaseName = json.name;
this.latestAppVersion = releaseName;
this.VRCXUpdateDialog.updatePendingIsLatest = false;
if (releaseName === this.pendingVRCXInstall) {
// update already downloaded
this.VRCXUpdateDialog.updatePendingIsLatest = true;
} else if (releaseName > currentVersion) {
var downloadUrl = '';
var downloadName = '';
var hashUrl = '';
var size = 0;
for (var asset of json.assets) {
if (asset.state !== 'uploaded') {
continue;
}
if (
!LINUX &&
(asset.content_type ===
'application/x-msdownload' ||
asset.content_type ===
'application/x-msdos-program')
) {
downloadUrl = asset.browser_download_url;
downloadName = asset.name;
size = asset.size;
continue;
}
if (
LINUX &&
asset.content_type === 'application/octet-stream'
) {
downloadUrl = asset.browser_download_url;
downloadName = asset.name;
size = asset.size;
continue;
}
if (
asset.name === 'SHA256SUMS.txt' &&
asset.content_type === 'text/plain'
) {
hashUrl = asset.browser_download_url;
continue;
}
}
if (!downloadUrl) {
return;
}
this.pendingVRCXUpdate = true;
this.notifyMenu('settings');
var type = 'Auto';
if (!API.isLoggedIn) {
this.showVRCXUpdateDialog();
} else if (this.autoUpdateVRCX === 'Notify') {
// this.showVRCXUpdateDialog();
} else if (this.autoUpdateVRCX === 'Auto Download') {
this.downloadVRCXUpdate(
downloadUrl,
downloadName,
hashUrl,
size,
releaseName,
type
);
}
}
}
},
restartVRCX(isUpgrade) {
if (!LINUX) {
AppApi.RestartApplication(isUpgrade);
} else {
window.electron.restartApp();
}
},
async saveAutoUpdateVRCX() {
if (this.autoUpdateVRCX === 'Off') {
this.pendingVRCXUpdate = false;
}
await configRepository.setString(
'VRCX_autoUpdateVRCX',
this.autoUpdateVRCX
);
}
};
}

View File

@@ -1,620 +0,0 @@
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';
export default class extends baseClass {
constructor(_app, _API, _t) {
super(_app, _API, _t);
}
init() {
API.webSocket = null;
API.lastWebSocketMessage = '';
API.$on('USER:CURRENT', function () {
if ($app.friendLogInitStatus && this.webSocket === null) {
this.getAuth();
}
});
API.getAuth = function () {
return this.call('auth', {
method: 'GET'
}).then((json) => {
var args = {
json
};
this.$emit('AUTH', args);
return args;
});
};
API.$on('AUTH', function (args) {
if (args.json.ok) {
this.connectWebSocket(args.json.token);
}
});
API.connectWebSocket = function (token) {
if (this.webSocket !== null) {
return;
}
var socket = new WebSocket(`${API.websocketDomain}/?auth=${token}`);
socket.onopen = () => {
if ($app.debugWebSocket) {
console.log('WebSocket connected');
}
};
socket.onclose = () => {
if (this.webSocket === socket) {
this.webSocket = null;
}
try {
socket.close();
} catch (err) {}
if ($app.debugWebSocket) {
console.log('WebSocket closed');
}
workerTimers.setTimeout(() => {
if (
this.isLoggedIn &&
$app.friendLogInitStatus &&
this.webSocket === null
) {
this.getAuth();
}
}, 5000);
};
socket.onerror = () => {
if (this.errorNoty) {
this.errorNoty.close();
}
this.errorNoty = new Noty({
type: 'error',
text: 'WebSocket Error'
}).show();
socket.onclose();
};
socket.onmessage = ({ data }) => {
try {
if (this.lastWebSocketMessage === data) {
// pls no spam
return;
}
this.lastWebSocketMessage = data;
var json = JSON.parse(data);
try {
json.content = JSON.parse(json.content);
} catch (err) {}
this.$emit('PIPELINE', {
json
});
if ($app.debugWebSocket && json.content) {
var displayName = '';
var user = this.cachedUsers.get(json.content.userId);
if (user) {
displayName = user.displayName;
}
console.log(
'WebSocket',
json.type,
displayName,
json.content
);
}
} catch (err) {
console.error(err);
}
};
this.webSocket = socket;
};
API.$on('LOGOUT', function () {
this.closeWebSocket();
});
API.closeWebSocket = function () {
var socket = this.webSocket;
if (socket === null) {
return;
}
this.webSocket = null;
try {
socket.close();
} catch (err) {}
};
API.reconnectWebSocket = function () {
if (!this.isLoggedIn || !$app.friendLogInitStatus) {
return;
}
this.closeWebSocket();
this.getAuth();
};
API.$on('PIPELINE', function (args) {
var { type, content, err } = args.json;
if (typeof err !== 'undefined') {
console.error('PIPELINE: error', args);
if (this.errorNoty) {
this.errorNoty.close();
}
this.errorNoty = new Noty({
type: 'error',
text: $app.escapeTag(`WebSocket Error: ${err}`)
}).show();
return;
}
if (typeof content === 'undefined') {
console.error('PIPELINE: missing content', args);
return;
}
if (typeof content.user !== 'undefined') {
// I forgot about this...
delete content.user.state;
}
switch (type) {
case 'notification':
this.$emit('NOTIFICATION', {
json: content,
params: {
notificationId: content.id
}
});
this.$emit('PIPELINE:NOTIFICATION', {
json: content,
params: {
notificationId: content.id
}
});
break;
case 'notification-v2':
console.log('notification-v2', content);
this.$emit('NOTIFICATION:V2', {
json: content,
params: {
notificationId: content.id
}
});
break;
case 'notification-v2-delete':
console.log('notification-v2-delete', content);
for (var id of content.ids) {
this.$emit('NOTIFICATION:HIDE', {
params: {
notificationId: id
}
});
this.$emit('NOTIFICATION:SEE', {
params: {
notificationId: id
}
});
}
break;
case 'notification-v2-update':
console.log('notification-v2-update', content);
this.$emit('NOTIFICATION:V2:UPDATE', {
json: content.updates,
params: {
notificationId: content.id
}
});
break;
case 'see-notification':
this.$emit('NOTIFICATION:SEE', {
params: {
notificationId: content
}
});
break;
case 'hide-notification':
this.$emit('NOTIFICATION:HIDE', {
params: {
notificationId: content
}
});
this.$emit('NOTIFICATION:SEE', {
params: {
notificationId: content
}
});
break;
case 'response-notification':
this.$emit('NOTIFICATION:HIDE', {
params: {
notificationId: content.notificationId
}
});
this.$emit('NOTIFICATION:SEE', {
params: {
notificationId: content.notificationId
}
});
break;
case 'friend-add':
this.$emit('USER', {
json: content.user,
params: {
userId: content.userId
}
});
this.$emit('FRIEND:ADD', {
params: {
userId: content.userId
}
});
break;
case 'friend-delete':
this.$emit('FRIEND:DELETE', {
params: {
userId: content.userId
}
});
break;
case 'friend-online':
// Where is instanceId, travelingToWorld, travelingToInstance?
// More JANK, what a mess
var $location = parseLocation(content.location);
var $travelingToLocation = parseLocation(
content.travelingToLocation
);
if (content?.user?.id) {
this.$emit('USER', {
json: {
id: content.userId,
platform: content.platform,
state: 'online',
location: content.location,
worldId: content.worldId,
instanceId: $location.instanceId,
travelingToLocation:
content.travelingToLocation,
travelingToWorld: $travelingToLocation.worldId,
travelingToInstance:
$travelingToLocation.instanceId,
...content.user
},
params: {
userId: content.userId
}
});
} else {
this.$emit('FRIEND:STATE', {
json: {
state: 'online'
},
params: {
userId: content.userId
}
});
}
break;
case 'friend-active':
if (content?.user?.id) {
this.$emit('USER', {
json: {
id: content.userId,
platform: content.platform,
state: 'active',
location: 'offline',
worldId: 'offline',
instanceId: 'offline',
travelingToLocation: 'offline',
travelingToWorld: 'offline',
travelingToInstance: 'offline',
...content.user
},
params: {
userId: content.userId
}
});
} else {
this.$emit('FRIEND:STATE', {
json: {
state: 'active'
},
params: {
userId: content.userId
}
});
}
break;
case 'friend-offline':
// more JANK, hell yeah
this.$emit('USER', {
json: {
id: content.userId,
platform: content.platform,
state: 'offline',
location: 'offline',
worldId: 'offline',
instanceId: 'offline',
travelingToLocation: 'offline',
travelingToWorld: 'offline',
travelingToInstance: 'offline'
},
params: {
userId: content.userId
}
});
break;
case 'friend-update':
this.$emit('USER', {
json: content.user,
params: {
userId: content.userId
}
});
break;
case 'friend-location':
var $location = parseLocation(content.location);
var $travelingToLocation = parseLocation(
content.travelingToLocation
);
if (!content?.user?.id) {
var ref = this.cachedUsers.get(content.userId);
if (typeof ref !== 'undefined') {
this.$emit('USER', {
json: {
...ref,
location: content.location,
worldId: content.worldId,
instanceId: $location.instanceId,
travelingToLocation:
content.travelingToLocation,
travelingToWorld:
$travelingToLocation.worldId,
travelingToInstance:
$travelingToLocation.instanceId
},
params: {
userId: content.userId
}
});
}
break;
}
this.$emit('USER', {
json: {
location: content.location,
worldId: content.worldId,
instanceId: $location.instanceId,
travelingToLocation: content.travelingToLocation,
travelingToWorld: $travelingToLocation.worldId,
travelingToInstance:
$travelingToLocation.instanceId,
...content.user,
state: 'online' // JANK
},
params: {
userId: content.userId
}
});
break;
case 'user-update':
this.$emit('USER:CURRENT', {
json: content.user,
params: {
userId: content.userId
}
});
break;
case 'user-location':
// update current user location
if (content.userId !== this.currentUser.id) {
console.error('user-location wrong userId', content);
break;
}
// content.user: {} // we don't trust this
// content.world: {} // this is long gone
// content.worldId // where did worldId go?
// content.instance // without worldId, this is useless
$app.setCurrentUserLocation(
content.location,
content.travelingToLocation
);
break;
case 'group-joined':
// var groupId = content.groupId;
// $app.onGroupJoined(groupId);
break;
case 'group-left':
// var groupId = content.groupId;
// $app.onGroupLeft(groupId);
break;
case 'group-role-updated':
var groupId = content.role.groupId;
groupRequest.getGroup({ groupId, includeRoles: true });
console.log('group-role-updated', content);
// content {
// role: {
// createdAt: string,
// description: string,
// groupId: string,
// id: string,
// isManagementRole: boolean,
// isSelfAssignable: boolean,
// name: string,
// order: number,
// permissions: string[],
// requiresPurchase: boolean,
// requiresTwoFactor: boolean
break;
case 'group-member-updated':
var member = content.member;
if (!member) {
console.error(
'group-member-updated missing member',
content
);
break;
}
var groupId = member.groupId;
if (
$app.groupDialog.visible &&
$app.groupDialog.id === groupId
) {
$app.getGroupDialogGroup(groupId);
}
this.$emit('GROUP:MEMBER', {
json: member,
params: {
groupId
}
});
console.log('group-member-updated', member);
break;
case 'instance-queue-joined':
case 'instance-queue-position':
var instanceId = content.instanceLocation;
var position = content.position ?? 0;
var queueSize = content.queueSize ?? 0;
$app.instanceQueueUpdate(instanceId, position, queueSize);
break;
case 'instance-queue-ready':
var instanceId = content.instanceLocation;
// var expiry = Date.parse(content.expiry);
$app.instanceQueueReady(instanceId);
break;
case 'instance-queue-left':
var instanceId = content.instanceLocation;
$app.removeQueuedInstance(instanceId);
// $app.instanceQueueClear();
break;
case 'content-refresh':
var contentType = content.contentType;
console.log('content-refresh', content);
if (contentType === 'icon') {
if (
$app.galleryDialogVisible &&
!$app.galleryDialogIconsLoading
) {
$app.refreshVRCPlusIconsTable();
}
} else if (contentType === 'gallery') {
if (
$app.galleryDialogVisible &&
!$app.galleryDialogGalleryLoading
) {
$app.refreshGalleryTable();
}
} else if (contentType === 'emoji') {
if (
$app.galleryDialogVisible &&
!$app.galleryDialogEmojisLoading
) {
$app.refreshEmojiTable();
}
} else if (contentType === 'sticker') {
// on sticker upload
} else if (contentType === 'print') {
if (
$app.autoDeleteOldPrints &&
content.actionType === 'created'
) {
$app.tryDeleteOldPrints();
} else if (
$app.galleryDialogVisible &&
!$app.galleryDialogPrintsLoading
) {
$app.refreshPrintTable();
}
} else if (contentType === 'prints') {
// lol
} else if (contentType === 'avatar') {
// hmm, utilizing this might be too spamy and cause UI to move around
} else if (contentType === 'world') {
// hmm
} else if (contentType === 'created') {
// on avatar upload, might be gone now
} else if (contentType === 'avatargallery') {
// on avatar gallery image upload
} else if (contentType === 'invitePhoto') {
// on uploading invite photo
} else if (contentType === 'inventory') {
if (
$app.galleryDialogVisible &&
!$app.galleryDialogInventoryLoading
) {
$app.getInventory();
}
// on consuming a bundle
// {contentType: 'inventory', itemId: 'inv_', itemType: 'prop', actionType: 'add'}
} else if (!contentType) {
console.log(
'content-refresh without contentType',
content
);
} else {
console.log(
'Unknown content-refresh type',
content.contentType
);
}
break;
case 'instance-closed':
// TODO: get worldName, groupName, hardClose
var noty = {
type: 'instance.closed',
location: content.instanceLocation,
message: 'Instance Closed',
created_at: new Date().toJSON()
};
if (
$app.notificationTable.filters[0].value.length === 0 ||
$app.notificationTable.filters[0].value.includes(
noty.type
)
) {
$app.notifyMenu('notification');
}
$app.queueNotificationNoty(noty);
$app.notificationTable.data.push(noty);
$app.updateSharedFeed(true);
break;
default:
console.log('Unknown pipeline type', args.json);
}
});
}
_data = {};
_methods = {};
}

View File

@@ -0,0 +1,73 @@
<template>
<div @click="confirm" class="avatar-info">
<span style="margin-right: 5px">{{ avatarName }}</span>
<span v-if="avatarType" :class="color" style="margin-right: 5px">{{ avatarType }}</span>
<span v-if="avatarTags" style="color: #909399; font-family: monospace; font-size: 12px">{{ avatarTags }}</span>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
import { useAvatarStore } from '../stores';
const avatarStore = useAvatarStore();
const props = defineProps({
imageurl: String,
userid: String,
hintownerid: String,
hintavatarname: String,
avatartags: Array
});
const avatarName = ref('');
const avatarType = ref('');
const avatarTags = ref('');
const color = ref('');
let ownerId = '';
const parse = async () => {
ownerId = '';
avatarName.value = '';
avatarType.value = '';
color.value = '';
avatarTags.value = '';
if (!props.imageurl) {
avatarName.value = '-';
} else if (props.hintownerid) {
avatarName.value = props.hintavatarname;
ownerId = props.hintownerid;
} else {
try {
const info = await avatarStore.getAvatarName(props.imageurl);
avatarName.value = info.avatarName;
ownerId = info.ownerId;
} catch {
console.error('Failed to fetch avatar name');
}
}
if (typeof props.userid === 'undefined' || !ownerId) {
color.value = '';
avatarType.value = '';
} else if (ownerId === props.userid) {
color.value = 'avatar-info-own';
avatarType.value = '(own)';
} else {
color.value = 'avatar-info-public';
avatarType.value = '(public)';
}
if (Array.isArray(props.avatartags)) {
avatarTags.value = props.avatartags.map((tag) => tag.replace('content_', '')).join(', ');
}
};
const confirm = () => {
if (!props.imageurl) return;
avatarStore.showAvatarAuthorDialog(props.userid, ownerId, props.imageurl);
};
watch([() => props.imageurl, () => props.userid, () => props.avatartags], parse, { immediate: true });
</script>

View File

@@ -0,0 +1,33 @@
<template>
<span>{{ text }}</span>
</template>
<script setup>
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
import * as workerTimers from 'worker-timers';
import { timeToText } from '../shared/utils';
const props = defineProps({
datetime: { type: String, default: '' },
hours: { type: Number, default: 1 }
});
const text = ref('');
function update() {
const epoch = new Date(props.datetime).getTime() + 1000 * 60 * 60 * props.hours - Date.now();
text.value = epoch >= 0 ? timeToText(epoch) : '-';
}
watch(() => props.datetime, update);
onMounted(() => {
update();
});
const timer = workerTimers.setInterval(update, 5000);
onBeforeUnmount(() => {
workerTimers.clearInterval(timer);
});
</script>

View File

@@ -0,0 +1,41 @@
<template>
<span @click="showUserDialog" class="x-link">{{ username }}</span>
</template>
<script setup>
import { ref, watch } from 'vue';
import { userRequest } from '../api';
import { useUserStore } from '../stores';
const userStore = useUserStore();
const props = defineProps({
userid: String,
location: String,
forceUpdateKey: Number,
hint: {
type: String,
default: ''
}
});
const username = ref(props.userid);
async function parse() {
username.value = props.userid;
if (props.hint) {
username.value = props.hint;
} else if (props.userid) {
const args = await userRequest.getCachedUser({ userId: props.userid });
if (args?.json?.displayName) {
username.value = args.json.displayName;
}
}
}
function showUserDialog() {
userStore.showUserDialog(props.userid);
}
watch([() => props.userid, () => props.location, () => props.forceUpdateKey], parse, { immediate: true });
</script>

View File

@@ -21,14 +21,12 @@
</span>
<template v-else-if="isGroupByInstance">
<i v-if="isFriendTraveling" class="el-icon el-icon-loading"></i>
<timer
<Timer
class="extra"
:epoch="epoch"
:style="
isFriendTraveling ? { display: 'inline-block', overflow: 'unset' } : undefined
"></timer>
:style="isFriendTraveling ? { display: 'inline-block', overflow: 'unset' } : undefined" />
</template>
<location
<Location
v-else
class="extra"
:location="friend.ref.location"
@@ -37,7 +35,7 @@
</template>
</div>
</template>
<template v-else-if="!friend.ref && !API.isRefreshFriendsLoading">
<template v-else-if="!friend.ref && !isRefreshFriendsLoading">
<span>{{ friend.name || friend.id }}</span>
<el-button
ttype="text"
@@ -62,38 +60,25 @@
</div>
</template>
<script>
import Location from './Location.vue';
<script setup>
import { storeToRefs } from 'pinia';
import { computed } from 'vue';
import { userImage, userStatusClass } from '../shared/utils';
import { useAppearanceSettingsStore, useFriendStore } from '../stores';
export default {
name: 'FriendItem',
components: {
Location
},
inject: ['API', 'userImage', 'userStatusClass'],
props: {
friend: {
type: Object,
required: true
},
hideNicknames: {
type: Boolean,
default: false
},
isGroupByInstance: Boolean
},
computed: {
isFriendTraveling() {
return this.friend.ref.location === 'traveling';
},
isFriendActiveOrOffline() {
return this.friend.state === 'active' || this.friend.state === 'offline';
},
epoch() {
return this.isFriendTraveling ? this.friend.ref.$travelingToTime : this.friend.ref.$location_at;
}
}
};
const props = defineProps({
friend: { type: Object, required: true },
isGroupByInstance: Boolean
});
const { hideNicknames } = storeToRefs(useAppearanceSettingsStore());
const { isRefreshFriendsLoading } = storeToRefs(useFriendStore());
const isFriendTraveling = computed(() => props.friend.ref.location === 'traveling');
const isFriendActiveOrOffline = computed(() => props.friend.state === 'active' || props.friend.state === 'offline');
const epoch = computed(() =>
isFriendTraveling.value ? props.friend.ref.$travelingToTime : props.friend.ref.$location_at
);
</script>
<style scoped>

View File

@@ -0,0 +1,170 @@
<template>
<div style="display: inline-block; margin-left: 5px">
<el-tooltip v-if="state.isValidInstance" placement="bottom">
<template #content>
<div>
<span v-if="state.isClosed">Closed At: {{ formatDateFilter(state.closedAt, 'long') }}<br /></span>
<template v-if="state.canCloseInstance">
<el-button
:disabled="state.isClosed"
size="mini"
type="primary"
@click="closeInstance(props.location)">
{{ t('dialog.user.info.close_instance') }} </el-button
><br /><br />
</template>
<span><span style="color: #409eff">PC: </span>{{ state.platforms.standalonewindows }}</span
><br />
<span><span style="color: #67c23a">Android: </span>{{ state.platforms.android }}</span
><br />
<span>{{ t('dialog.user.info.instance_game_version') }} {{ state.gameServerVersion }}</span
><br />
<span v-if="state.queueEnabled">{{ t('dialog.user.info.instance_queuing_enabled') }}<br /></span>
<span v-if="state.disabledContentSettings"
>{{ t('dialog.user.info.instance_disabled_content') }} {{ state.disabledContentSettings }}<br
/></span>
<span v-if="state.userList.length">{{ t('dialog.user.info.instance_users') }}<br /></span>
<template v-for="user in state.userList">
<span
style="cursor: pointer; margin-right: 5px"
@click="showUserDialog(user.id)"
:key="user.id"
>{{ user.displayName }}</span
>
</template>
</div>
</template>
<i class="el-icon-caret-bottom"></i>
</el-tooltip>
<span v-if="state.occupants" style="margin-left: 5px">{{ state.occupants }}/{{ state.capacity }}</span>
<span v-if="props.friendcount" style="margin-left: 5px">({{ props.friendcount }})</span>
<span v-if="state.isFull" style="margin-left: 5px; color: lightcoral">{{
t('dialog.user.info.instance_full')
}}</span>
<span v-if="state.isHardClosed" style="margin-left: 5px; color: lightcoral">{{
t('dialog.user.info.instance_hard_closed')
}}</span>
<span v-else-if="state.isClosed" style="margin-left: 5px; color: lightcoral">{{
t('dialog.user.info.instance_closed')
}}</span>
<span v-if="state.queueSize" style="margin-left: 5px"
>{{ t('dialog.user.info.instance_queue') }} {{ state.queueSize }}</span
>
<span v-if="state.isAgeGated" style="margin-left: 5px; color: lightcoral">{{
t('dialog.user.info.instance_age_gated')
}}</span>
</div>
</template>
<script setup>
import { getCurrentInstance, reactive, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { miscRequest } from '../api';
import { formatDateFilter, hasGroupPermission } from '../shared/utils';
import { useGroupStore, useInstanceStore, useLocationStore, useUserStore } from '../stores';
const { t } = useI18n();
const locationStore = useLocationStore();
const userStore = useUserStore();
const groupStore = useGroupStore();
const instanceStore = useInstanceStore();
const props = defineProps({
location: String,
instance: Object,
friendcount: Number
});
const state = reactive({
isValidInstance: false,
isFull: false,
isClosed: false,
isHardClosed: false,
closedAt: '',
occupants: 0,
capacity: 0,
queueSize: 0,
queueEnabled: false,
platforms: [],
userList: [],
gameServerVersion: '',
canCloseInstance: false,
isAgeGated: false,
disabledContentSettings: ''
});
const { proxy } = getCurrentInstance();
function parse() {
Object.assign(state, {
isValidInstance: false,
isFull: false,
isClosed: false,
isHardClosed: false,
closedAt: '',
occupants: 0,
capacity: 0,
queueSize: 0,
queueEnabled: false,
platforms: [],
userList: [],
gameServerVersion: '',
canCloseInstance: false,
isAgeGated: false,
disabledContentSettings: ''
});
if (!props.location || !props.instance || Object.keys(props.instance).length === 0) return;
state.isValidInstance = true;
state.isFull = props.instance.hasCapacityForYou === false;
if (props.instance.closedAt) {
state.isClosed = true;
state.closedAt = props.instance.closedAt;
}
state.isHardClosed = props.instance.hardClose === true;
state.occupants = props.instance.userCount;
if (props.location === locationStore.lastLocation.location) {
state.occupants = locationStore.lastLocation.playerList.size;
}
state.capacity = props.instance.capacity;
state.gameServerVersion = props.instance.gameServerVersion;
state.queueSize = props.instance.queueSize;
if (props.instance.platforms) state.platforms = props.instance.platforms;
if (props.instance.users) state.userList = props.instance.users;
if (props.instance.ownerId === userStore.currentUser.id) {
state.canCloseInstance = true;
} else if (props.instance.ownerId?.startsWith('grp_')) {
const group = groupStore.cachedGroups.get(props.instance.ownerId);
state.canCloseInstance = hasGroupPermission(group, 'group-instance-moderate');
}
state.isAgeGated = props.instance.ageGate === true;
if (props.location?.includes('~ageGate')) state.isAgeGated = true;
if (props.instance.$disabledContentSettings?.length) {
state.disabledContentSettings = props.instance.$disabledContentSettings.join(', ');
}
}
watch([() => props.location, () => props.instance, () => props.friendcount], parse, { immediate: true });
function showUserDialog(userId) {
userStore.showUserDialog(userId);
}
function closeInstance(location) {
proxy.$confirm('Continue? Close Instance, nobody will be able to join', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'warning',
callback: async (action) => {
if (action !== 'confirm') return;
const args = await miscRequest.closeInstance({ location, hardClose: false });
if (args.json) {
proxy.$message({ message: t('message.instance.closed'), type: 'success' });
instanceStore.applyInstance(args.json);
}
}
});
}
</script>

View File

@@ -0,0 +1,34 @@
<template>
<el-button v-show="isVisible" @click="confirmInvite" size="mini" icon="el-icon-message" circle />
</template>
<script setup>
import { computed, getCurrentInstance } from 'vue';
import { instanceRequest } from '../api';
import { checkCanInviteSelf, parseLocation } from '../shared/utils';
const props = defineProps({
location: String,
shortname: String
});
const { proxy } = getCurrentInstance();
const isVisible = computed(() => checkCanInviteSelf(props.location));
function confirmInvite() {
const L = parseLocation(props.location);
if (!L.isRealInstance) return;
instanceRequest
.selfInvite({
instanceId: L.instanceId,
worldId: L.worldId,
shortName: props.shortname
})
.then((args) => {
proxy.$message({ message: 'Self invite sent', type: 'success' });
return args;
});
}
</script>

View File

@@ -0,0 +1,32 @@
<template>
<span v-if="lastJoin">
<el-tooltip placement="top" style="margin-left: 5px">
<template #content>
<span>{{ $t('dialog.user.info.last_join') }} <Timer :epoch="lastJoin" /></span>
</template>
<i class="el-icon el-icon-location-outline" style="display: inline-block" />
</el-tooltip>
</span>
</template>
<script setup>
import { storeToRefs } from 'pinia';
import { ref, watch } from 'vue';
import { useInstanceStore } from '../stores';
const { instanceJoinHistory } = storeToRefs(useInstanceStore());
const props = defineProps({
location: String,
currentlocation: String
});
const lastJoin = ref(null);
function parse() {
lastJoin.value = instanceJoinHistory.value.get(props.location);
}
watch(() => props.location, parse, { immediate: true });
watch(() => props.currentlocation, parse);
</script>

32
src/components/Launch.vue Normal file
View File

@@ -0,0 +1,32 @@
<template>
<el-tooltip
v-show="isVisible"
placement="top"
:content="t('dialog.user.info.launch_invite_tooltip')"
:disabled="hideTooltips">
<el-button @click="confirm" size="mini" icon="el-icon-switch-button" circle />
</el-tooltip>
</template>
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { checkCanInviteSelf } from '../shared/utils';
import { useLaunchStore } from '../stores';
const launchStore = useLaunchStore();
const { t } = useI18n();
const props = defineProps({
location: String,
hideTooltips: Boolean
});
const isVisible = computed(() => {
return checkCanInviteSelf(props.location);
});
function confirm() {
launchStore.showLaunchDialog(props.location);
}
</script>

View File

@@ -15,147 +15,140 @@
</div>
</template>
<script>
import { parseLocation } from '../composables/instance/utils';
<script setup>
import { storeToRefs } from 'pinia';
import { ref, watch } from 'vue';
import { getGroupName, getWorldName, parseLocation } from '../shared/utils';
import { useGroupStore, useInstanceStore, useSearchStore, useWorldStore } from '../stores';
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Location',
inject: {
// prevent randomly error
// not good idea, it's temporary
API: { default: window.API },
getWorldName: { default: window.$app?.getWorldName },
getGroupName: { default: window.$app?.getGroupName },
showWorldDialog: { default: window.$app?.showWorldDialog },
showGroupDialog: { default: window.$app?.showGroupDialog }
const { cachedWorlds } = storeToRefs(useWorldStore());
const { showWorldDialog } = useWorldStore();
const { showGroupDialog } = useGroupStore();
const { showPreviousInstancesInfoDialog } = useInstanceStore();
const { verifyShortName } = useSearchStore();
const props = defineProps({
location: String,
traveling: String,
hint: {
type: String,
default: ''
},
props: {
location: String,
traveling: String,
hint: {
type: String,
default: ''
},
grouphint: {
type: String,
default: ''
},
link: {
type: Boolean,
default: true
},
isOpenPreviousInstanceInfoDialog: Boolean
grouphint: {
type: String,
default: ''
},
data() {
return {
text: '',
region: this.region,
strict: this.strict,
isTraveling: this.isTraveling,
groupName: this.groupName
};
link: {
type: Boolean,
default: true
},
watch: {
location() {
this.parse();
isOpenPreviousInstanceInfoDialog: Boolean
});
const text = ref('');
const region = ref('');
const strict = ref(false);
const isTraveling = ref(false);
const groupName = ref('');
watch(
() => props.location,
() => {
parse();
}
);
parse();
function parse() {
isTraveling.value = false;
groupName.value = '';
let instanceId = props.location;
if (typeof props.traveling !== 'undefined' && props.location === 'traveling') {
instanceId = props.traveling;
isTraveling.value = true;
}
const L = parseLocation(instanceId);
if (L.isOffline) {
text.value = 'Offline';
} else if (L.isPrivate) {
text.value = 'Private';
} else if (L.isTraveling) {
text.value = 'Traveling';
} else if (typeof props.hint === 'string' && props.hint !== '') {
if (L.instanceId) {
text.value = `${props.hint} #${L.instanceName} ${L.accessTypeName}`;
} else {
text.value = props.hint;
}
},
created() {
this.parse();
},
methods: {
parse() {
if (!this.API) return;
this.isTraveling = false;
this.groupName = '';
let instanceId = this.location;
if (typeof this.traveling !== 'undefined' && this.location === 'traveling') {
instanceId = this.traveling;
this.isTraveling = true;
}
const L = parseLocation(instanceId);
if (L.isOffline) {
this.text = 'Offline';
} else if (L.isPrivate) {
this.text = 'Private';
} else if (L.isTraveling) {
this.text = 'Traveling';
} else if (typeof this.hint === 'string' && this.hint !== '') {
if (L.instanceId) {
this.text = `${this.hint} #${L.instanceName} ${L.accessTypeName}`;
} else {
this.text = this.hint;
}
} else if (L.worldId) {
var ref = this.API.cachedWorlds.get(L.worldId);
if (typeof ref === 'undefined') {
this.getWorldName(L.worldId).then((worldName) => {
if (L.tag === instanceId) {
if (L.instanceId) {
this.text = `${worldName} #${L.instanceName} ${L.accessTypeName}`;
} else {
this.text = worldName;
}
}
});
} else if (L.instanceId) {
this.text = `${ref.name} #${L.instanceName} ${L.accessTypeName}`;
} else {
this.text = ref.name;
}
}
if (this.grouphint) {
this.groupName = this.grouphint;
} else if (L.groupId) {
this.groupName = L.groupId;
this.getGroupName(instanceId).then((groupName) => {
if (L.tag === instanceId) {
this.groupName = groupName;
} else if (L.worldId) {
const ref = cachedWorlds.value.get(L.worldId);
if (typeof ref === 'undefined') {
getWorldName(L.worldId).then((worldName) => {
if (L.tag === instanceId) {
if (L.instanceId) {
text.value = `${worldName} #${L.instanceName} ${L.accessTypeName}`;
} else {
text.value = worldName;
}
});
}
this.region = '';
if (!L.isOffline && !L.isPrivate && !L.isTraveling) {
this.region = L.region;
if (!L.region && L.instanceId) {
this.region = 'us';
}
}
this.strict = L.strict;
},
handleShowWorldDialog() {
if (this.link) {
let instanceId = this.location;
if (this.traveling && this.location === 'traveling') {
instanceId = this.traveling;
}
if (!instanceId && this.hint.length === 8) {
// shortName
this.API.$emit('SHOW_WORLD_DIALOG_SHORTNAME', this.hint);
return;
}
if (this.isOpenPreviousInstanceInfoDialog) {
this.$emit('open-previous-instance-info-dialog', instanceId);
} else {
this.showWorldDialog(instanceId);
}
}
},
handleShowGroupDialog() {
let location = this.location;
if (this.isTraveling) {
location = this.traveling;
}
if (!location || !this.link) {
return;
}
const L = parseLocation(location);
if (!L.groupId) {
return;
}
this.showGroupDialog(L.groupId);
});
} else if (L.instanceId) {
text.value = `${ref.name} #${L.instanceName} ${L.accessTypeName}`;
} else {
text.value = ref.name;
}
}
};
if (props.grouphint) {
groupName.value = props.grouphint;
} else if (L.groupId) {
groupName.value = L.groupId;
getGroupName(instanceId).then((name) => {
if (L.tag === instanceId) {
groupName.value = name;
}
});
}
region.value = '';
if (!L.isOffline && !L.isPrivate && !L.isTraveling) {
region.value = L.region;
if (!L.region && L.instanceId) {
region.value = 'us';
}
}
strict.value = L.strict;
}
function handleShowWorldDialog() {
if (props.link) {
let instanceId = props.location;
if (props.traveling && props.location === 'traveling') {
instanceId = props.traveling;
}
if (!instanceId && props.hint.length === 8) {
verifyShortName('', props.hint);
return;
}
if (props.isOpenPreviousInstanceInfoDialog) {
showPreviousInstancesInfoDialog(instanceId);
} else {
showWorldDialog(instanceId);
}
}
}
function handleShowGroupDialog() {
let location = props.location;
if (isTraveling.value) {
location = props.traveling;
}
if (!location || !props.link) {
return;
}
const L = parseLocation(location);
if (!L.groupId) {
return;
}
showGroupDialog(L.groupId);
}
</script>

View File

@@ -0,0 +1,78 @@
<template>
<span>
<span @click="showLaunchDialog" class="x-link">
<i v-if="isUnlocked" class="el-icon el-icon-unlock" style="display: inline-block; margin-right: 5px"></i>
<span>#{{ instanceName }} {{ accessTypeName }}</span>
</span>
<span v-if="groupName" @click="showGroupDialog" class="x-link">({{ groupName }})</span>
<span class="flags" :class="region" style="display: inline-block; margin-left: 5px"></span>
<i v-if="strict" class="el-icon el-icon-lock" style="display: inline-block; margin-left: 5px"></i>
</span>
</template>
<script setup>
import { ref, watch } from 'vue';
import { getGroupName, parseLocation } from '../shared/utils';
import { useGroupStore, useLaunchStore } from '../stores';
const launchStore = useLaunchStore();
const groupStore = useGroupStore();
const props = defineProps({
locationobject: Object,
currentuserid: String,
worlddialogshortname: String,
grouphint: {
type: String,
default: ''
}
});
const location = ref('');
const instanceName = ref('');
const accessTypeName = ref('');
const region = ref('us');
const shortName = ref('');
const isUnlocked = ref(false);
const strict = ref(false);
const groupName = ref('');
function parse() {
const locObj = props.locationobject;
location.value = locObj.tag;
instanceName.value = locObj.instanceName;
accessTypeName.value = locObj.accessTypeName;
strict.value = locObj.strict;
shortName.value = locObj.shortName;
isUnlocked.value =
(props.worlddialogshortname && locObj.shortName && props.worlddialogshortname === locObj.shortName) ||
props.currentuserid === locObj.userId;
region.value = locObj.region || 'us';
if (props.grouphint) {
groupName.value = props.grouphint;
} else if (locObj.groupId) {
groupName.value = locObj.groupId;
getGroupName(locObj.groupId).then((name) => {
groupName.value = name;
});
} else {
groupName.value = '';
}
}
watch(() => props.locationobject, parse, { immediate: true });
function showLaunchDialog() {
launchStore.showLaunchDialog(location.value, shortName.value);
}
function showGroupDialog() {
if (!location.value) return;
const L = parseLocation(location.value);
if (!L.groupId) return;
groupStore.showGroupDialog(L.groupId);
}
</script>

View File

@@ -1,104 +1,65 @@
<template>
<el-menu
ref="navRef"
collapse
@select="$emit('select', $event)"
:default-active="menuActiveIndex"
:collapse-transition="false">
<el-menu-item index="feed">
<i class="el-icon-news"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.feed') }}</span>
</template>
</el-menu-item>
<el-menu-item index="gameLog">
<i class="el-icon-s-data"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.game_log') }}</span>
</template>
</el-menu-item>
<el-menu-item index="playerList">
<i class="el-icon-tickets"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.player_list') }}</span>
</template>
</el-menu-item>
<el-menu-item index="search">
<i class="el-icon-search"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.search') }}</span>
</template>
</el-menu-item>
<el-menu-item index="favorite">
<i class="el-icon-star-off"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.favorites') }}</span>
</template>
</el-menu-item>
<el-menu-item index="friendLog">
<i class="el-icon-notebook-2"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.friend_log') }}</span>
</template>
</el-menu-item>
<el-menu-item index="moderation">
<i class="el-icon-finished"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.moderation') }}</span>
</template>
</el-menu-item>
<el-menu-item index="notification">
<i class="el-icon-bell"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.notification') }}</span>
</template>
</el-menu-item>
<el-menu-item index="friendList">
<i class="el-icon-s-management"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.friend_list') }}</span>
</template>
</el-menu-item>
<el-menu-item index="charts">
<i class="el-icon-data-analysis"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.charts') }}</span>
</template>
</el-menu-item>
<el-menu-item index="profile">
<i class="el-icon-user"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.profile') }}</span>
</template>
</el-menu-item>
<el-menu-item index="settings">
<i class="el-icon-s-tools"></i>
<template slot="title">
<span>{{ $t('nav_tooltip.settings') }}</span>
</template>
</el-menu-item>
</el-menu>
<div class="x-menu-container">
<div v-if="updateInProgress" class="pending-update" @click="showVRCXUpdateDialog">
<el-progress
type="circle"
width="50"
stroke-width="3"
:percentage="updateProgress"
:format="updateProgressText"></el-progress>
</div>
<div v-else-if="pendingVRCXUpdate || pendingVRCXInstall" class="pending-update">
<el-button
type="default"
size="mini"
icon="el-icon-download"
circle
style="font-size: 14px; height: 50px; width: 50px"
@click="showVRCXUpdateDialog" />
</div>
<el-menu
ref="navRef"
collapse
:default-active="menuActiveIndex"
:collapse-transition="false"
@select="selectMenu">
<el-menu-item
v-for="item in navItems"
:key="item.index"
:index="item.index"
:class="{ notify: notifiedMenus.includes(item.index) }">
<i :class="item.icon" />
<template #title>
<span>{{ $t(item.tooltip) }}</span>
</template>
</el-menu-item>
</el-menu>
</div>
</template>
<script>
export default {
name: 'NavMenu',
props: {
menuActiveIndex: {
type: String,
default: 'feed'
}
}
};
<script setup>
import { storeToRefs } from 'pinia';
import { useUiStore, useVRCXUpdaterStore } from '../stores';
const navItems = [
{ index: 'feed', icon: 'el-icon-news', tooltip: 'nav_tooltip.feed' },
{ index: 'gameLog', icon: 'el-icon-s-data', tooltip: 'nav_tooltip.game_log' },
{ index: 'playerList', icon: 'el-icon-tickets', tooltip: 'nav_tooltip.player_list' },
{ index: 'search', icon: 'el-icon-search', tooltip: 'nav_tooltip.search' },
{ index: 'favorite', icon: 'el-icon-star-off', tooltip: 'nav_tooltip.favorites' },
{ index: 'friendLog', icon: 'el-icon-notebook-2', tooltip: 'nav_tooltip.friend_log' },
{ index: 'moderation', icon: 'el-icon-finished', tooltip: 'nav_tooltip.moderation' },
{ index: 'notification', icon: 'el-icon-bell', tooltip: 'nav_tooltip.notification' },
{ index: 'friendList', icon: 'el-icon-s-management', tooltip: 'nav_tooltip.friend_list' },
{ index: 'charts', icon: 'el-icon-data-analysis', tooltip: 'nav_tooltip.charts' },
{ index: 'profile', icon: 'el-icon-user', tooltip: 'nav_tooltip.profile' },
{ index: 'settings', icon: 'el-icon-s-tools', tooltip: 'nav_tooltip.settings' }
];
const VRCXUpdaterStore = useVRCXUpdaterStore();
const { pendingVRCXUpdate, pendingVRCXInstall, updateInProgress, updateProgress } = storeToRefs(VRCXUpdaterStore);
const { showVRCXUpdateDialog, updateProgressText } = VRCXUpdaterStore;
const uiStore = useUiStore();
const { menuActiveIndex, notifiedMenus } = storeToRefs(uiStore);
const { selectMenu } = uiStore;
</script>

View File

@@ -11,35 +11,23 @@
</div>
</template>
<script>
export default {
name: 'SimpleSwitch',
props: {
label: {
type: String
},
value: {
type: Boolean
},
tooltip: {
type: String
},
disabled: {
type: Boolean
},
longLabel: {
type: Boolean
}
},
methods: {
change(event) {
this.$emit('change', event);
}
}
};
<script setup>
defineProps({
label: String,
value: Boolean,
tooltip: String,
disabled: Boolean,
longLabel: Boolean
});
const emit = defineEmits(['change']);
function change(event) {
emit('change', event);
}
</script>
<style lang="scss">
<style scoped>
.simple-switch {
font-size: 12px;
margin-top: 5px;

29
src/components/Timer.vue Normal file
View File

@@ -0,0 +1,29 @@
<template>
<span>{{ text }}</span>
</template>
<script setup>
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { timeToText } from '../shared/utils';
const props = defineProps({
epoch: {
type: Number,
required: true
}
});
const now = ref(Date.now());
const text = computed(() => {
return props.epoch ? timeToText(now.value - props.epoch) : '-';
});
let timerId = null;
onMounted(() => {
timerId = setInterval(() => {
now.value = Date.now();
}, 5000);
});
onBeforeUnmount(() => {
clearInterval(timerId);
});
</script>

View File

@@ -261,7 +261,7 @@
type="default"
icon="el-icon-check"
circle
:disabled="API.currentUser.currentAvatar === avatarDialog.id"
:disabled="currentUser.currentAvatar === avatarDialog.id"
style="margin-left: 5px"
@click="selectAvatar(avatarDialog.id)"></el-button>
</el-tooltip>
@@ -299,12 +299,12 @@
>{{ t('dialog.avatar.actions.select_fallback') }}</el-dropdown-item
>
<el-dropdown-item
v-if="avatarDialog.ref.authorId !== API.currentUser.id"
v-if="avatarDialog.ref.authorId !== currentUser.id"
icon="el-icon-picture-outline"
command="Previous Images"
>{{ t('dialog.avatar.actions.show_previous_images') }}</el-dropdown-item
>
<template v-if="avatarDialog.ref.authorId === API.currentUser.id">
<template v-if="avatarDialog.ref.authorId === currentUser.id">
<el-dropdown-item
v-if="avatarDialog.ref.releaseStatus === 'public'"
icon="el-icon-user-solid"
@@ -367,7 +367,7 @@
<el-tab-pane :label="t('dialog.avatar.info.header')">
<div class="x-friend-list" style="max-height: unset">
<div
v-if="avatarDialog.galleryImages.length || avatarDialog.ref.authorId === API.currentUser.id"
v-if="avatarDialog.galleryImages.length || avatarDialog.ref.authorId === currentUser.id"
style="width: 100%">
<span class="name">{{ t('dialog.avatar.info.gallery') }}</span>
<input
@@ -377,8 +377,8 @@
style="display: none"
@change="onFileChangeAvatarGallery" />
<el-button
v-if="avatarDialog.ref.authorId === API.currentUser.id"
v-loading="avatarDialog.galleryLoading"
v-if="avatarDialog.ref.authorId === currentUser.id"
:disabled="!!avatarDialog.galleryLoading"
size="small"
icon="el-icon-upload2"
style="margin-left: 5px"
@@ -396,7 +396,7 @@
style="width: 100%; height: 100%; object-fit: contain"
@click="showFullscreenImageDialog(imageUrl)" />
<div
v-if="avatarDialog.ref.authorId === API.currentUser.id"
v-if="avatarDialog.ref.authorId === currentUser.id"
style="position: absolute; bottom: 5px; left: 33.3%">
<el-button
size="mini"
@@ -497,16 +497,18 @@
<div class="x-friend-item" style="cursor: default">
<div class="detail">
<span class="name">{{ t('dialog.avatar.info.created_at') }}</span>
<span class="extra">{{ avatarDialog.ref.created_at | formatDate('long') }}</span>
<span class="extra">{{ formatDateFilter(avatarDialog.ref.created_at, 'long') }}</span>
</div>
</div>
<div class="x-friend-item" style="cursor: default">
<div class="detail">
<span class="name">{{ t('dialog.avatar.info.last_updated') }}</span>
<span v-if="avatarDialog.lastUpdated" class="extra">{{
avatarDialog.lastUpdated | formatDate('long')
formatDateFilter(avatarDialog.lastUpdated, 'long')
}}</span>
<span v-else class="extra">{{
formatDateFilter(avatarDialog.ref.updated_at, 'long')
}}</span>
<span v-else class="extra">{{ avatarDialog.ref.updated_at | formatDate('long') }}</span>
</div>
</div>
<div class="x-friend-item" style="cursor: default">
@@ -596,71 +598,68 @@
<SetAvatarStylesDialog :set-avatar-styles-dialog="setAvatarStylesDialog" />
<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" />
<PreviousImagesDialog />
</safe-dialog>
</template>
<script setup>
import { computed, getCurrentInstance, inject, nextTick, reactive, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { computed, getCurrentInstance, nextTick, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { avatarModerationRequest, avatarRequest, favoriteRequest, imageRequest, miscRequest } from '../../../api';
import utils from '../../../classes/utils';
import { compareUnityVersion, storeAvatarImage } from '../../../composables/avatar/utils';
import { database } from '../../../service/database';
import {
adjustDialogZ,
buildTreeData,
commaNumber,
compareUnityVersion,
copyToClipboard,
downloadAndSaveJson,
extractFileId,
extractFileVersion,
replaceVrcPackageUrl
} from '../../../composables/shared/utils';
import database from '../../../service/database';
openExternalLink,
openFolderGeneric,
replaceVrcPackageUrl,
storeAvatarImage,
timeToText,
moveArrayItem,
formatDateFilter
} from '../../../shared/utils';
import {
useAppearanceSettingsStore,
useAvatarStore,
useFavoriteStore,
useGalleryStore,
useGameStore,
useUserStore
} from '../../../stores';
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 showFullscreenImageDialog = inject('showFullscreenImageDialog');
const showUserDialog = inject('showUserDialog');
const showAvatarDialog = inject('showAvatarDialog');
const showFavoriteDialog = inject('showFavoriteDialog');
const openExternalLink = inject('openExternalLink');
const adjustDialogZ = inject('adjustDialogZ');
const getImageUrlFromImageId = inject('getImageUrlFromImageId');
const getAvatarGallery = inject('getAvatarGallery');
const { hideTooltips } = storeToRefs(useAppearanceSettingsStore());
const { showUserDialog, sortUserDialogAvatars } = useUserStore();
const { userDialog, currentUser } = storeToRefs(useUserStore());
const { avatarDialog, cachedAvatarModerations, cachedAvatars, cachedAvatarNames } = storeToRefs(useAvatarStore());
const { showAvatarDialog, getAvatarGallery, applyAvatarModeration, applyAvatar } = useAvatarStore();
const { showFavoriteDialog } = useFavoriteStore();
const { isGameRunning } = storeToRefs(useGameStore());
const { deleteVRChatCache } = useGameStore();
const { previousImagesDialogVisible, previousImagesTable } = storeToRefs(useGalleryStore());
const { showFullscreenImageDialog, checkPreviousImageAvailable } = useGalleryStore();
const { t } = useI18n();
const instance = getCurrentInstance();
const { $message, $confirm, $prompt } = instance.proxy;
const emit = defineEmits(['openFolderGeneric', 'deleteVRChatCache', 'openPreviousImagesDialog']);
const props = defineProps({
avatarDialog: {
type: Object,
required: true
},
hideTooltips: {
type: Boolean,
default: false
},
isGameRunning: {
type: Boolean,
default: false
}
});
defineEmits(['openPreviousImagesDialog']);
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);
@@ -695,7 +694,7 @@
});
const avatarDialogPlatform = computed(() => {
const { ref } = props.avatarDialog;
const { ref } = avatarDialog.value;
const platforms = [];
if (ref.unityPackages) {
for (const unityPackage of ref.unityPackages) {
@@ -721,11 +720,11 @@
});
watch(
() => props.avatarDialog.loading,
() => avatarDialog.value.loading,
(newVal) => {
if (newVal) {
nextTick(() => {
const D = props.avatarDialog;
const D = avatarDialog.value;
if (D.visible) {
adjustDialogZ(avatarDialogRef.value.$el);
}
@@ -735,6 +734,10 @@
}
);
function getImageUrlFromImageId(imageId) {
return `https://api.vrchat.cloud/api/1/file/${imageId}/1/`;
}
function handleDialogOpen() {
fileAnalysis.value = {};
memo.value = '';
@@ -744,19 +747,19 @@
}
function getAvatarTimeSpent() {
const D = props.avatarDialog;
const D = avatarDialog.value;
database.getAvatarTimeSpent(D.id).then((aviTime) => {
if (D.id === aviTime.avatarId) {
timeSpent.value = aviTime.timeSpent;
if (D.id === API.currentUser.currentAvatar && API.currentUser.$previousAvatarSwapTime) {
timeSpent.value += Date.now() - API.currentUser.$previousAvatarSwapTime;
if (D.id === currentUser.value.currentAvatar && currentUser.value.$previousAvatarSwapTime) {
timeSpent.value += Date.now() - currentUser.value.$previousAvatarSwapTime;
}
}
});
}
function getAvatarMemo() {
const D = props.avatarDialog;
const D = avatarDialog.value;
database.getAvatarMemoDB(D.id).then((res) => {
if (D.id === res.avatarId) {
memo.value = res.memo;
@@ -764,20 +767,8 @@
});
}
function openFolderGeneric(path) {
emit('openFolderGeneric', path);
}
function deleteVRChatCache(ref) {
emit('deleteVRChatCache', ref);
}
function commaNumber(num) {
return utils.commaNumber(num);
}
function avatarDialogCommand(command) {
const D = props.avatarDialog;
const D = avatarDialog.value;
switch (command) {
case 'Refresh':
showAvatarDialog(D.id);
@@ -804,7 +795,7 @@
showSetAvatarStylesDialog(D.id);
break;
case 'Download Unity Package':
openExternalLink(replaceVrcPackageUrl(props.avatarDialog.ref.unityPackageUrl));
openExternalLink(replaceVrcPackageUrl(avatarDialog.value.ref.unityPackageUrl));
break;
case 'Add Favorite':
showFavoriteDialog('avatar', D.id);
@@ -845,7 +836,7 @@
})
.then((args) => {
// 'AVATAR-MODERATION';
args.ref = API.applyAvatarModeration(args.json);
applyAvatarModeration(args.json);
$message({
message: 'Avatar blocked',
type: 'success'
@@ -860,9 +851,8 @@
targetAvatarId: D.id
})
.then((args) => {
// 'AVATAR-MODERATION:DELETE';
API.cachedAvatarModerations.delete(args.params.targetAvatarId);
const D = props.avatarDialog;
cachedAvatarModerations.value.delete(args.params.targetAvatarId);
const D = avatarDialog.value;
if (
args.params.avatarModerationType === 'block' &&
D.id === args.params.targetAvatarId
@@ -878,6 +868,7 @@
releaseStatus: 'public'
})
.then((args) => {
applyAvatar(args.json);
$message({
message: 'Avatar updated to public',
type: 'success'
@@ -892,6 +883,7 @@
releaseStatus: 'private'
})
.then((args) => {
applyAvatar(args.json);
$message({
message: 'Avatar updated to private',
type: 'success'
@@ -905,6 +897,19 @@
avatarId: D.id
})
.then((args) => {
const { json } = args;
cachedAvatars.value.delete(json._id);
if (userDialog.value.id === json.authorId) {
const map = new Map();
for (const ref of cachedAvatars.value.values()) {
if (ref.authorId === json.authorId) {
map.set(ref.id, ref);
}
}
const array = Array.from(map.values());
sortUserDialogAvatars(array);
}
$message({
message: 'Avatar deleted',
type: 'success'
@@ -973,7 +978,7 @@
function displayPreviousImages(command) {
previousImagesTable.value = [];
previousImagesFileId.value = '';
const { imageUrl } = props.avatarDialog.ref;
const { imageUrl } = avatarDialog.value.ref;
const fileId = extractFileId(imageUrl);
if (!fileId) {
return;
@@ -988,7 +993,7 @@
changeAvatarImageDialogVisible.value = true;
}
imageRequest.getAvatarImages(params).then((args) => {
storeAvatarImage(args);
storeAvatarImage(args, cachedAvatarNames.value);
previousImagesFileId.value = args.json.id;
const images = [];
@@ -1001,23 +1006,6 @@
});
}
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({
@@ -1047,6 +1035,7 @@
description: instance.inputValue
})
.then((args) => {
applyAvatar(args.json);
$message({
message: t('prompt.change_avatar_description.message.success'),
type: 'success'
@@ -1073,6 +1062,7 @@
name: instance.inputValue
})
.then((args) => {
applyAvatar(args.json);
$message({
message: t('prompt.rename_avatar.message.success'),
type: 'success'
@@ -1087,12 +1077,12 @@
function onAvatarMemoChange() {
if (memo.value) {
database.setAvatarMemo({
avatarId: props.avatarDialog.id,
avatarId: avatarDialog.value.id,
editedAt: new Date().toJSON(),
memo: memo.value
});
} else {
database.deleteAvatarMemo(props.avatarDialog.id);
database.deleteAvatarMemo(avatarDialog.value.id);
}
}
@@ -1104,17 +1094,13 @@
copyToClipboard(`https://vrchat.com/home/avatar/${id}`);
}
function timeToText(time) {
return utils.timeToText(time);
}
function refreshAvatarDialogTreeData() {
treeData.value = utils.buildTreeData(props.avatarDialog.ref);
treeData.value = buildTreeData(avatarDialog.value.ref);
}
function getAvatarFileAnalysis() {
let unityPackage;
const D = props.avatarDialog;
const D = avatarDialog.value;
const avatarId = D.ref.id;
let assetUrl = '';
let variant = 'security';
@@ -1154,8 +1140,7 @@
return;
}
miscRequest.getFileAnalysis({ fileId, version, variant, avatarId }).then((args) => {
// API.$on('FILE:ANALYSIS', function (args) {
if (!props.avatarDialog.visible || props.avatarDialog.id !== args.params.avatarId) {
if (!avatarDialog.value.visible || avatarDialog.value.id !== args.params.avatarId) {
return;
}
const ref = args.json;
@@ -1169,9 +1154,8 @@
ref._totalTextureUsage = `${(ref.avatarStats.totalTextureUsage / 1048576).toFixed(2)} MB`;
}
fileAnalysis.value = utils.buildTreeData(args.json);
fileAnalysis.value = buildTreeData(args.json);
});
// });
}
function showSetAvatarTagsDialog(avatarId) {
@@ -1187,7 +1171,7 @@
D.contentViolence = false;
D.contentAdult = false;
D.contentSex = false;
const oldTags = props.avatarDialog.ref.tags;
const oldTags = avatarDialog.value.ref.tags;
oldTags.forEach((tag) => {
switch (tag) {
case 'content_horror':
@@ -1212,8 +1196,8 @@
break;
}
});
for (const ref of API.cachedAvatars.values()) {
if (ref.authorId === API.currentUser.id) {
for (const ref of cachedAvatars.value.values()) {
if (ref.authorId === currentUser.value.id) {
ref.$selected = false;
ref.$tagString = '';
if (avatarId === ref.id) {
@@ -1245,12 +1229,12 @@
const D = setAvatarStylesDialog;
D.visible = true;
D.loading = true;
D.avatarId = props.avatarDialog.id;
D.primaryStyle = props.avatarDialog.ref.styles?.primary || '';
D.secondaryStyle = props.avatarDialog.ref.styles?.secondary || '';
D.avatarId = avatarDialog.value.id;
D.primaryStyle = avatarDialog.value.ref.styles?.primary || '';
D.secondaryStyle = avatarDialog.value.ref.styles?.secondary || '';
D.initialPrimaryStyle = D.primaryStyle;
D.initialSecondaryStyle = D.secondaryStyle;
D.initialTags = props.avatarDialog.ref.tags;
D.initialTags = avatarDialog.value.ref.tags;
D.authorTags = '';
for (const tag of D.initialTags) {
if (tag.startsWith('author_tag_')) {
@@ -1298,43 +1282,31 @@
}
const r = new FileReader();
r.onload = function () {
props.avatarDialog.galleryLoading = true;
avatarDialog.value.galleryLoading = true;
const base64Body = btoa(r.result);
avatarRequest
.uploadAvatarGalleryImage(base64Body, props.avatarDialog.id)
.uploadAvatarGalleryImage(base64Body, avatarDialog.value.id)
.then((args) => {
$message({
message: t('message.avatar_gallery.uploaded'),
type: 'success'
});
console.log(args);
props.avatarDialog.galleryImages = getAvatarGallery(props.avatarDialog.id);
avatarDialog.value.galleryImages = getAvatarGallery(avatarDialog.value.id);
return args;
})
.finally(() => {
props.avatarDialog.galleryLoading = false;
avatarDialog.value.galleryLoading = false;
});
};
r.readAsBinaryString(files[0]);
clearFile();
}
function deleteAvatarGalleryImage(imageUrl) {
const fileId = extractFileId(imageUrl);
miscRequest.deleteFile(fileId).then((args) => {
$message({
message: t('message.avatar_gallery.deleted'),
type: 'success'
});
props.avatarDialog.galleryImages = getAvatarGallery(props.avatarDialog.id);
return args;
});
}
function reorderAvatarGalleryImage(imageUrl, direction) {
const fileId = extractFileId(imageUrl);
let fileIds = [];
props.avatarDialog.ref.gallery.forEach((item) => {
avatarDialog.value.ref.gallery.forEach((item) => {
fileIds.push(extractFileId(item.id));
});
const index = fileIds.indexOf(fileId);
@@ -1360,16 +1332,28 @@
return;
}
if (direction === -1) {
utils.moveArrayItem(fileIds, index, index - 1);
moveArrayItem(fileIds, index, index - 1);
} else {
utils.moveArrayItem(fileIds, index, index + 1);
moveArrayItem(fileIds, index, index + 1);
}
avatarRequest.setAvatarGalleryOrder(fileIds).then((args) => {
$message({
message: t('message.avatar_gallery.reordered'),
type: 'success'
});
props.avatarDialog.galleryImages = getAvatarGallery(props.avatarDialog.id);
avatarDialog.value.galleryImages = getAvatarGallery(avatarDialog.value.id);
return args;
});
}
function deleteAvatarGalleryImage(imageUrl) {
const fileId = extractFileId(imageUrl);
miscRequest.deleteFile(fileId).then((args) => {
$message({
message: t('message.avatar_gallery.deleted'),
type: 'success'
});
getAvatarGallery(avatarDialog.value.id);
return args;
});
}

View File

@@ -42,32 +42,29 @@
</template>
<script setup>
import { getCurrentInstance, inject, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance, ref } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { imageRequest } from '../../../api';
import { extractFileId } from '../../../composables/shared/utils';
import webApiService from '../../../service/webapi';
import { AppGlobal } from '../../../service/appConfig';
import { $throw } from '../../../service/request';
import { extractFileId } from '../../../shared/utils';
import { useAvatarStore, useGalleryStore } from '../../../stores';
const { t } = useI18n();
const instance = getCurrentInstance();
const $message = instance.proxy.$message;
const API = inject('API');
const { avatarDialog } = storeToRefs(useAvatarStore());
const { previousImagesTable } = storeToRefs(useGalleryStore());
const { applyAvatar } = useAvatarStore();
const props = defineProps({
changeAvatarImageDialogVisible: {
type: Boolean,
default: false
},
previousImagesTable: {
type: Array,
default: () => []
},
avatarDialog: {
type: Object,
default: () => ({})
},
previousImagesFileId: {
type: String,
default: ''
@@ -121,7 +118,7 @@
}
};
const files = e.target.files || e.dataTransfer.files;
if (!files.length || !props.avatarDialog.visible || props.avatarDialog.loading) {
if (!files.length || !avatarDialog.value.visible || avatarDialog.value.loading) {
clearFile();
return;
}
@@ -156,8 +153,8 @@
const signatureMd5 = await genMd5(base64SignatureFile);
const signatureSizeInBytes = parseInt(await genLength(base64SignatureFile), 10);
const avatarId = props.avatarDialog.id;
const { imageUrl } = props.avatarDialog.ref;
const avatarId = avatarDialog.value.id;
const { imageUrl } = avatarDialog.value.ref;
const fileId = extractFileId(imageUrl);
if (!fileId) {
@@ -206,7 +203,6 @@
}
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 = {
@@ -218,7 +214,6 @@
}
async function avatarImageFileStart(args) {
// API.$on('AVATARIMAGE:FILESTART')
const { url } = args.json;
const { fileId, fileVersion } = args.params;
const params = {
@@ -242,7 +237,7 @@
if (json.status !== 200) {
changeAvatarImageDialogLoading.value = false;
API.$throw('Avatar image upload failed', json, params.url);
$throw('Avatar image upload failed', json, params.url);
}
const args = {
json,
@@ -252,7 +247,6 @@
}
async function avatarImageFileAWS(args) {
// API.$on('AVATARIMAGE:FILEAWS')
const { fileId, fileVersion } = args.params;
const params = {
fileId,
@@ -263,7 +257,6 @@
}
async function avatarImageFileFinish(args) {
// API.$on('AVATARIMAGE:FILEFINISH')
const { fileId, fileVersion } = args.params;
const params = {
fileId,
@@ -274,7 +267,6 @@
}
async function avatarImageSigStart(args) {
// API.$on('AVATARIMAGE:SIGSTART')
const { url } = args.json;
const { fileId, fileVersion } = args.params;
const params = {
@@ -298,7 +290,7 @@
if (json.status !== 200) {
changeAvatarImageDialogLoading.value = false;
API.$throw('Avatar image upload failed', json, params.url);
$throw('Avatar image upload failed', json, params.url);
}
const args = {
json,
@@ -308,7 +300,6 @@
}
async function avatarImageSigAWS(args) {
// API.$on('AVATARIMAGE:SIGAWS')
const { fileId, fileVersion } = args.params;
const params = {
fileId,
@@ -319,18 +310,17 @@
}
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`
imageUrl: `${AppGlobal.endpointDomain}/file/${fileId}/${fileVersion}/file`
};
const res = await imageRequest.setAvatarImage(parmas);
return avatarImageSet(res);
}
async function avatarImageSet(args) {
// API.$on('AVATARIMAGE:SET')
applyAvatar(args.json);
changeAvatarImageDialogLoading.value = false;
if (args.json.imageUrl === args.params.imageUrl) {
$message({
@@ -339,7 +329,7 @@
});
refresh();
} else {
API.$throw(0, 'Avatar image change failed', args.params.imageUrl);
$throw(0, 'Avatar image change failed', args.params.imageUrl);
}
}
@@ -352,42 +342,22 @@
function setAvatarImage(image) {
changeAvatarImageDialogLoading.value = true;
const parmas = {
id: props.avatarDialog.id,
imageUrl: `${API.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
id: avatarDialog.value.id,
imageUrl: `${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
};
imageRequest.setAvatarImage(parmas).finally(() => {
changeAvatarImageDialogLoading.value = false;
closeDialog();
});
imageRequest
.setAvatarImage(parmas)
.then((args) => applyAvatar(args.json))
.finally(() => {
changeAvatarImageDialogLoading.value = false;
closeDialog();
});
}
function compareCurrentImage(image) {
return (
`${API.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
props.avatarDialog.ref.imageUrl
`${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
avatarDialog.value.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>

View File

@@ -64,13 +64,16 @@
import { watch, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import utils from '../../../classes/utils';
import { arraysMatch } from '../../../shared/utils';
import { avatarRequest } from '../../../api';
import { useAvatarStore } from '../../../stores';
const { t } = useI18n();
const instance = getCurrentInstance();
const $message = instance.proxy.$message;
const { applyAvatar } = useAvatarStore();
const props = defineProps({
setAvatarStylesDialog: {
type: Object,
@@ -125,7 +128,7 @@
if (
props.setAvatarStylesDialog.initialPrimaryStyle === props.setAvatarStylesDialog.primaryStyle &&
props.setAvatarStylesDialog.initialSecondaryStyle === props.setAvatarStylesDialog.secondaryStyle &&
utils.arraysMatch(props.setAvatarStylesDialog.initialTags, tags)
arraysMatch(props.setAvatarStylesDialog.initialTags, tags)
) {
props.setAvatarStylesDialog.visible = false;
return;
@@ -139,7 +142,8 @@
};
avatarRequest
.saveAvatar(params)
.then(() => {
.then((args) => {
applyAvatar(args.json);
$message.success(t('dialog.set_avatar_styles.save_success'));
props.setAvatarStylesDialog.visible = false;
})

View File

@@ -94,12 +94,12 @@
</template>
<script setup>
import { inject, watch, getCurrentInstance } from 'vue';
import { getCurrentInstance, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { avatarRequest } from '../../../api';
import { useAvatarStore } from '../../../stores';
const showAvatarDialog = inject('showAvatarDialog');
const { showAvatarDialog, applyAvatar } = useAvatarStore();
const { t } = useI18n();
const instance = getCurrentInstance();
@@ -220,10 +220,11 @@
tags.push(tag);
}
}
await avatarRequest.saveAvatar({
const args = await avatarRequest.saveAvatar({
id: ref.id,
tags
});
applyAvatar(args.json);
D.selectedCount--;
}
} catch (err) {
@@ -270,15 +271,6 @@
}
}
}
// useless
// $app.data.avatarContentTags = [
// 'content_horror',
// 'content_gore',
// 'content_violence',
// 'content_adult',
// 'content_sex'
// ];
</script>
<style scoped></style>

View File

@@ -1,7 +1,7 @@
<template>
<safe-dialog ref="favoriteDialog" :visible.sync="isVisible" :title="$t('dialog.favorite.header')" width="300px">
<safe-dialog ref="favoriteDialogRef" :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>
<span style="display: block; text-align: center">{{ t('dialog.favorite.vrchat_favorites') }}</span>
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
<el-button
style="display: block; width: 100%; margin: 10px 0"
@@ -22,7 +22,7 @@
</template>
</div>
<div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px">
<span style="display: block; text-align: center">{{ $t('dialog.favorite.local_favorites') }}</span>
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span>
<template v-for="group in localWorldFavoriteGroups">
<el-button
v-if="hasLocalWorldFavorite(favoriteDialog.objectId, group)"
@@ -42,7 +42,7 @@
</template>
</div>
<div v-if="favoriteDialog.type === 'avatar'" style="margin-top: 20px">
<span style="display: block; text-align: center">{{ $t('dialog.favorite.local_avatar_favorites') }}</span>
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_avatar_favorites') }}</span>
<template v-for="group in localAvatarFavoriteGroups">
<el-button
v-if="hasLocalAvatarFavorite(favoriteDialog.objectId, group)"
@@ -65,122 +65,88 @@
</safe-dialog>
</template>
<script>
import { favoriteRequest } from '../../api';
<script setup>
import Noty from 'noty';
import { storeToRefs } from 'pinia';
import { computed, nextTick, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { favoriteRequest } from '../../api';
import { adjustDialogZ } from '../../shared/utils';
import { useFavoriteStore, useUserStore } from '../../stores';
export default {
name: 'ChooseFavoriteGroupDialog',
inject: ['API', 'adjustDialogZ'],
props: {
favoriteDialog: {
type: Object,
default: () => ({
visible: false,
type: '',
objectId: '',
currentGroup: {}
})
},
localWorldFavoriteGroups: {
type: Array,
default: () => []
},
localAvatarFavoriteGroups: {
type: Array,
default: () => []
},
hasLocalWorldFavorite: {
type: Function,
default: () => () => false
},
getLocalWorldFavoriteGroupLength: {
type: Function,
default: () => () => 0
},
hasLocalAvatarFavorite: {
type: Function,
default: () => () => false
},
getLocalAvatarFavoriteGroupLength: {
type: Function,
default: () => () => 0
}
},
data() {
return {
groups: [],
loading: false
};
},
computed: {
isVisible: {
get() {
return this.favoriteDialog.visible;
},
set(value) {
this.$emit('update:favorite-dialog', { ...this.favoriteDialog, visible: value });
}
},
isLocalUserVrcplusSupporter() {
return this.API.currentUser.$isVRCPlus;
}
},
watch: {
'favoriteDialog.visible'(value) {
if (value) {
this.initFavoriteDialog();
this.$nextTick(() => {
this.adjustDialogZ(this.$refs.favoriteDialog.$el);
});
}
}
},
methods: {
initFavoriteDialog() {
if (this.favoriteDialog.type === 'friend') {
this.groups = this.API.favoriteFriendGroups;
} else if (this.favoriteDialog.type === 'world') {
this.groups = this.API.favoriteWorldGroups;
} else if (this.favoriteDialog.type === 'avatar') {
this.groups = this.API.favoriteAvatarGroups;
}
},
addFavorite(group) {
const D = this.favoriteDialog;
this.loading = true;
favoriteRequest
.addFavorite({
type: D.type,
favoriteId: D.objectId,
tags: group.name
})
.then(() => {
this.isVisible = false;
new Noty({
type: 'success',
text: 'Favorite added'
}).show();
})
.finally(() => {
this.loading = false;
});
},
addLocalWorldFavorite(...args) {
this.$emit('addLocalWorldFavorite', ...args);
},
removeLocalWorldFavorite(...args) {
this.$emit('removeLocalWorldFavorite', ...args);
},
addLocalAvatarFavorite(...args) {
this.$emit('addLocalAvatarFavorite', ...args);
},
removeLocalAvatarFavorite(...args) {
this.$emit('removeLocalAvatarFavorite', ...args);
},
deleteFavoriteNoConfirm(...args) {
this.$emit('deleteFavoriteNoConfirm', ...args);
const { t } = useI18n();
const favoriteStore = useFavoriteStore();
const {
favoriteFriendGroups,
favoriteAvatarGroups,
favoriteWorldGroups,
localAvatarFavoriteGroups,
favoriteDialog,
localWorldFavoriteGroups
} = storeToRefs(favoriteStore);
const {
getLocalWorldFavoriteGroupLength,
addLocalWorldFavorite,
hasLocalWorldFavorite,
hasLocalAvatarFavorite,
addLocalAvatarFavorite,
getLocalAvatarFavoriteGroupLength,
removeLocalAvatarFavorite,
removeLocalWorldFavorite,
deleteFavoriteNoConfirm
} = favoriteStore;
const { currentUser } = storeToRefs(useUserStore());
const favoriteDialogRef = ref(null);
const groups = ref([]);
const loading = ref(false);
const isVisible = computed({
get: () => favoriteDialog.value.visible,
set: (v) => {
favoriteDialog.value.visible = v;
}
});
const isLocalUserVrcplusSupporter = computed(() => currentUser.value.$isVRCPlus);
watch(
() => favoriteDialog.value.visible,
async (value) => {
if (value) {
initFavoriteDialog();
await nextTick();
adjustDialogZ(favoriteDialogRef.value.$el);
}
}
};
);
function initFavoriteDialog() {
if (favoriteDialog.value.type === 'friend') {
groups.value = favoriteFriendGroups.value;
} else if (favoriteDialog.value.type === 'world') {
groups.value = favoriteWorldGroups.value;
} else if (favoriteDialog.value.type === 'avatar') {
groups.value = favoriteAvatarGroups.value;
}
}
function addFavorite(group) {
const D = favoriteDialog.value;
loading.value = true;
favoriteRequest
.addFavorite({
type: D.type,
favoriteId: D.objectId,
tags: group.name
})
.then(() => {
isVisible.value = false;
new Noty({ type: 'success', text: 'favorite added!' }).show();
})
.finally(() => {
loading.value = false;
});
}
</script>

View File

@@ -1,11 +1,5 @@
<template>
<safe-dialog
ref="fullscreenImageDialog"
class="x-dialog"
:visible.sync="fullscreenImageDialog.visible"
append-to-body
top="1vh"
width="97vw">
<safe-dialog class="x-dialog" :visible.sync="fullscreenImageDialog.visible" append-to-body top="1vh" width="97vw">
<div>
<div style="margin: 0 0 5px 5px">
<el-button
@@ -29,21 +23,16 @@
</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';
import { storeToRefs } from 'pinia';
import { getCurrentInstance } from 'vue';
import { copyToClipboard, escapeTag, extractFileId } from '../../shared/utils';
import { useGalleryStore } from '../../stores';
const { proxy } = getCurrentInstance();
const { $message } = proxy;
defineProps({
fullscreenImageDialog: {
type: Object,
default: () => ({})
}
});
const { fullscreenImageDialog } = storeToRefs(useGalleryStore());
function copyImageUrl(imageUrl) {
copyToClipboard(imageUrl, 'ImageUrl copied to clipboard');
@@ -84,7 +73,7 @@
} catch {
new Noty({
type: 'error',
text: utils.escapeTag(`Failed to download image. ${url}`)
text: escapeTag(`Failed to download image. ${url}`)
}).show();
}
}

View File

@@ -33,7 +33,7 @@
size="small"
@click="displayGalleryUpload"
icon="el-icon-upload2"
:disabled="!API.currentUser.$isVRCPlus">
:disabled="!currentUser.$isVRCPlus">
{{ t('dialog.gallery_icons.upload') }}
</el-button>
<el-button
@@ -41,7 +41,7 @@
size="small"
@click="setProfilePicOverride('')"
icon="el-icon-close"
:disabled="!API.currentUser.profilePicOverride">
:disabled="!currentUser.profilePicOverride">
{{ t('dialog.gallery_icons.clear') }}
</el-button>
</el-button-group>
@@ -102,7 +102,7 @@
size="small"
@click="displayVRCPlusIconUpload"
icon="el-icon-upload2"
:disabled="!API.currentUser.$isVRCPlus">
:disabled="!currentUser.$isVRCPlus">
{{ t('dialog.gallery_icons.upload') }}
</el-button>
<el-button
@@ -110,7 +110,7 @@
size="small"
@click="setVRCPlusIcon('')"
icon="el-icon-close"
:disabled="!API.currentUser.userIcon">
:disabled="!currentUser.userIcon">
{{ t('dialog.gallery_icons.clear') }}
</el-button>
</el-button-group>
@@ -150,7 +150,7 @@
<span slot="label">
{{ t('dialog.gallery_icons.emojis') }}
<span style="color: #909399; font-size: 12px; margin-left: 5px">
{{ emojiTable.length }}/{{ API.cachedConfig?.maxUserEmoji }}
{{ emojiTable.length }}/{{ cachedConfig?.maxUserEmoji }}
</span>
</span>
<input
@@ -172,7 +172,7 @@
size="small"
@click="displayEmojiUpload"
icon="el-icon-upload2"
:disabled="!API.currentUser.$isVRCPlus">
:disabled="!currentUser.$isVRCPlus">
{{ t('dialog.gallery_icons.upload') }}
</el-button>
</el-button-group>
@@ -237,7 +237,7 @@
@click="
showFullscreenImageDialog(
image.versions[image.versions.length - 1].file.url,
getEmojiName(image)
getEmojiFileName(image)
)
">
<template v-if="image.frames">
@@ -271,7 +271,7 @@
@click="
showFullscreenImageDialog(
image.versions[image.versions.length - 1].file.url,
getEmojiName(image)
getEmojiFileName(image)
)
"
size="mini"
@@ -292,7 +292,7 @@
<span slot="label">
{{ t('dialog.gallery_icons.stickers') }}
<span style="color: #909399; font-size: 12px; margin-left: 5px">
{{ stickerTable.length }}/{{ API.cachedConfig?.maxUserStickers }}
{{ stickerTable.length }}/{{ cachedConfig?.maxUserStickers }}
</span>
</span>
<input
@@ -313,7 +313,7 @@
size="small"
@click="displayStickerUpload"
icon="el-icon-upload2"
:disabled="!API.currentUser.$isVRCPlus">
:disabled="!currentUser.$isVRCPlus">
{{ t('dialog.gallery_icons.upload') }}
</el-button>
</el-button-group>
@@ -373,7 +373,7 @@
size="small"
@click="displayPrintUpload"
icon="el-icon-upload2"
:disabled="!API.currentUser.$isVRCPlus">
:disabled="!currentUser.$isVRCPlus">
{{ t('dialog.gallery_icons.upload') }}
</el-button>
</el-button-group>
@@ -405,25 +405,25 @@
<div style="margin-top: 5px; width: 208px">
<span class="x-ellipsis" v-if="image.note" v-text="image.note" style="display: block"></span>
<span v-else style="display: block">&nbsp;</span>
<location
<Location
class="x-ellipsis"
v-if="image.worldId"
:location="image.worldId"
:hint="image.worldName"
style="display: block"></location>
style="display: block" />
<span v-else style="display: block">&nbsp;</span>
<display-name
<DisplayName
class="x-ellipsis"
v-if="image.authorId"
:userid="image.authorId"
:hint="image.authorName"
style="color: #909399; font-family: monospace; display: block"></display-name>
style="color: #909399; font-family: monospace; display: block" />
<span v-else style="font-family: monospace; display: block">&nbsp;</span>
<span
class="x-ellipsis"
v-if="image.createdAt"
style="color: #909399; font-family: monospace; font-size: 11px; display: block">
{{ image.createdAt | formatDate('long') }}
{{ formatDateFilter(image.createdAt, 'long') }}
</span>
<span v-else style="display: block">&nbsp;</span>
</div>
@@ -479,7 +479,7 @@
<span
class="x-ellipsis"
style="color: #909399; font-family: monospace; font-size: 11px; display: block">
{{ item.created_at | formatDate('long') }}
{{ formatDateFilter(item.created_at, 'long') }}
</span>
<span v-text="item.itemType" style="display: block"></span>
</div>
@@ -500,95 +500,50 @@
</template>
<script setup>
import { getCurrentInstance, inject, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance, ref } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { inventoryRequest, miscRequest, userRequest, vrcPlusIconRequest, vrcPlusImageRequest } from '../../api';
import { extractFileId } from '../../composables/shared/utils';
import { emojiAnimationStyleList, emojiAnimationStyleUrl } from '../../composables/user/constants/emoji';
import { getPrintFileName, getEmojiFileName } from '../../composables/user/utils';
import Location from '../Location.vue';
import { AppGlobal } from '../../service/appConfig';
import { emojiAnimationStyleList, emojiAnimationStyleUrl } from '../../shared/constants';
import { extractFileId, formatDateFilter, getEmojiFileName, getPrintFileName } from '../../shared/utils';
import { useAdvancedSettingsStore, useAuthStore, useGalleryStore, useUserStore } from '../../stores';
const { t } = useI18n();
const { proxy } = getCurrentInstance();
const { $message } = proxy;
const {
galleryTable,
galleryDialogVisible,
galleryDialogGalleryLoading,
galleryDialogIconsLoading,
galleryDialogEmojisLoading,
galleryDialogStickersLoading,
galleryDialogPrintsLoading,
galleryDialogInventoryLoading,
VRCPlusIconsTable,
printUploadNote,
printCropBorder,
stickerTable,
printTable,
emojiTable,
inventoryTable
} = storeToRefs(useGalleryStore());
const {
refreshGalleryTable,
refreshVRCPlusIconsTable,
refreshStickerTable,
refreshPrintTable,
refreshEmojiTable,
getInventory,
handleStickerAdd,
handleGalleryImageAdd
} = useGalleryStore();
const API = inject('API');
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
const props = defineProps({
galleryDialogVisible: {
type: Boolean,
required: true
},
galleryDialogGalleryLoading: {
type: Boolean,
required: true
},
galleryDialogIconsLoading: {
type: Boolean,
required: true
},
galleryDialogEmojisLoading: {
type: Boolean,
required: true
},
galleryDialogStickersLoading: {
type: Boolean,
required: true
},
galleryDialogPrintsLoading: {
type: Boolean,
required: true
},
galleryDialogInventoryLoading: {
type: Boolean,
required: true
},
galleryTable: {
type: Array,
required: true
},
// eslint-disable-next-line vue/prop-name-casing
VRCPlusIconsTable: {
type: Array,
required: true
},
emojiTable: {
type: Array,
required: true
},
stickerTable: {
type: Array,
required: true
},
printUploadNote: {
type: String,
required: true
},
printCropBorder: {
type: Boolean,
required: true
},
printTable: {
type: Array,
required: true
},
inventoryTable: {
type: Array,
required: true
}
});
const emit = defineEmits([
'refreshGalleryTable',
'refreshVRCPlusIconsTable',
'refreshStickerTable',
'refreshEmojiTable',
'refreshPrintTable',
'getInventory',
'closeGalleryDialog'
]);
const { currentUserInventory } = storeToRefs(useAdvancedSettingsStore());
const { showFullscreenImageDialog } = useGalleryStore();
const { currentUser } = storeToRefs(useUserStore());
const { cachedConfig } = storeToRefs(useAuthStore());
const emojiAnimFps = ref(15);
const emojiAnimFrameCount = ref(4);
@@ -596,12 +551,8 @@
const emojiAnimationStyle = ref('Stop');
const emojiAnimLoopPingPong = ref(false);
function refreshGalleryTable() {
emit('refreshGalleryTable');
}
function closeGalleryDialog() {
emit('closeGalleryDialog');
galleryDialogVisible.value = false;
}
function onFileChangeGallery(e) {
@@ -616,7 +567,7 @@
}
if (files[0].size >= 100000000) {
// 100MB
$message({
proxy.$message({
message: t('message.file.too_large'),
type: 'error'
});
@@ -624,7 +575,7 @@
return;
}
if (!files[0].type.match(/image.*/)) {
$message({
proxy.$message({
message: t('message.file.not_image'),
type: 'error'
});
@@ -635,7 +586,8 @@
r.onload = function () {
const base64Body = btoa(r.result);
vrcPlusImageRequest.uploadGalleryImage(base64Body).then((args) => {
$message({
handleGalleryImageAdd(args);
proxy.$message({
message: t('message.gallery.uploaded'),
type: 'success'
});
@@ -651,8 +603,8 @@
}
function setProfilePicOverride(fileId) {
if (!API.currentUser.$isVRCPlus) {
$message({
if (!currentUser.value.$isVRCPlus) {
proxy.$message({
message: 'VRCPlus required',
type: 'error'
});
@@ -660,9 +612,9 @@
}
let profilePicOverride = '';
if (fileId) {
profilePicOverride = `${API.endpointDomain}/file/${fileId}/1`;
profilePicOverride = `${AppGlobal.endpointDomain}/file/${fileId}/1`;
}
if (profilePicOverride === API.currentUser.profilePicOverride) {
if (profilePicOverride === currentUser.value.profilePicOverride) {
return;
}
userRequest
@@ -670,7 +622,7 @@
profilePicOverride
})
.then((args) => {
$message({
proxy.$message({
message: 'Profile picture changed',
type: 'success'
});
@@ -679,7 +631,7 @@
}
function compareCurrentProfilePic(fileId) {
const currentProfilePicOverride = extractFileId(API.currentUser.profilePicOverride);
const currentProfilePicOverride = extractFileId(currentUser.value.profilePicOverride);
if (fileId === currentProfilePicOverride) {
return true;
}
@@ -688,9 +640,7 @@
function deleteGalleryImage(fileId) {
miscRequest.deleteFile(fileId).then((args) => {
// API.$emit('GALLERYIMAGE:DELETE', args);
// API.$on('GALLERYIMAGE:DELETE')
const array = props.galleryTable;
const array = galleryTable.value;
const { length } = array;
for (let i = 0; i < length; ++i) {
if (args.fileId === array[i].id) {
@@ -715,7 +665,7 @@
}
if (files[0].size >= 100000000) {
// 100MB
$message({
proxy.$message({
message: t('message.file.too_large'),
type: 'error'
});
@@ -723,7 +673,7 @@
return;
}
if (!files[0].type.match(/image.*/)) {
$message({
proxy.$message({
message: t('message.file.not_image'),
type: 'error'
});
@@ -734,7 +684,10 @@
r.onload = function () {
const base64Body = btoa(r.result);
vrcPlusIconRequest.uploadVRCPlusIcon(base64Body).then((args) => {
$message({
if (Object.keys(VRCPlusIconsTable.value).length !== 0) {
VRCPlusIconsTable.value.unshift(args.json);
}
proxy.$message({
message: t('message.icon.uploaded'),
type: 'success'
});
@@ -745,17 +698,13 @@
clearFile();
}
function refreshVRCPlusIconsTable() {
emit('refreshVRCPlusIconsTable');
}
function displayVRCPlusIconUpload() {
document.getElementById('VRCPlusIconUploadButton').click();
}
function setVRCPlusIcon(fileId) {
if (!API.currentUser.$isVRCPlus) {
$message({
if (!currentUser.value.$isVRCPlus) {
proxy.$message({
message: 'VRCPlus required',
type: 'error'
});
@@ -763,9 +712,9 @@
}
let userIcon = '';
if (fileId) {
userIcon = `${API.endpointDomain}/file/${fileId}/1`;
userIcon = `${AppGlobal.endpointDomain}/file/${fileId}/1`;
}
if (userIcon === API.currentUser.userIcon) {
if (userIcon === currentUser.value.userIcon) {
return;
}
userRequest
@@ -773,7 +722,7 @@
userIcon
})
.then((args) => {
$message({
proxy.$message({
message: 'Icon changed',
type: 'success'
});
@@ -782,7 +731,7 @@
}
function compareCurrentVRCPlusIcon(userIcon) {
const currentUserIcon = extractFileId(API.currentUser.userIcon);
const currentUserIcon = extractFileId(currentUser.value.userIcon);
if (userIcon === currentUserIcon) {
return true;
}
@@ -791,9 +740,7 @@
function deleteVRCPlusIcon(fileId) {
miscRequest.deleteFile(fileId).then((args) => {
// API.$emit('VRCPLUSICON:DELETE', args);
// API.$on('VRCPLUSICON:DELETE')
const array = props.VRCPlusIconsTable;
const array = VRCPlusIconsTable.value;
const { length } = array;
for (let i = 0; i < length; ++i) {
if (args.fileId === array[i].id) {
@@ -823,7 +770,7 @@
emojiAnimFps.value = parseInt(value.replace('fps', ''));
}
if (value.endsWith('loopStyle')) {
emojiAnimLoopPingPong.value = value.replace('loopStyle', '').toLowerCase() === 'pingpong';
emojiAnimLoopPingPong.value = value.replace('loopStyle', '').toLowerCase();
}
}
}
@@ -840,7 +787,7 @@
}
if (files[0].size >= 100000000) {
// 100MB
$message({
proxy.$message({
message: t('message.file.too_large'),
type: 'error'
});
@@ -848,7 +795,7 @@
return;
}
if (!files[0].type.match(/image.*/)) {
$message({
proxy.$message({
message: t('message.file.not_image'),
type: 'error'
});
@@ -873,7 +820,10 @@
}
const base64Body = btoa(r.result);
vrcPlusImageRequest.uploadEmoji(base64Body, params).then((args) => {
$message({
if (Object.keys(emojiTable).length !== 0) {
emojiTable.unshift(args.json);
}
proxy.$message({
message: t('message.emoji.uploaded'),
type: 'success'
});
@@ -884,18 +834,10 @@
clearFile();
}
function refreshEmojiTable() {
emit('refreshEmojiTable');
}
function displayEmojiUpload() {
document.getElementById('EmojiUploadButton').click();
}
function getEmojiName(emoji) {
getEmojiFileName(emoji);
}
function generateEmojiStyle(url, fps, frameCount, loopStyle) {
let framesPerLine = 2;
if (frameCount > 4) framesPerLine = 4;
@@ -917,9 +859,7 @@
function deleteEmoji(fileId) {
miscRequest.deleteFile(fileId).then((args) => {
// API.$emit('EMOJI:DELETE', args);
// API.$on('EMOJI:DELETE')
const array = props.emojiTable;
const array = emojiTable.value;
const { length } = array;
for (let i = 0; i < length; ++i) {
if (args.fileId === array[i].id) {
@@ -943,7 +883,7 @@
}
if (files[0].size >= 100000000) {
// 100MB
$message({
proxy.$message({
message: t('message.file.too_large'),
type: 'error'
});
@@ -951,7 +891,7 @@
return;
}
if (!files[0].type.match(/image.*/)) {
$message({
proxy.$message({
message: t('message.file.not_image'),
type: 'error'
});
@@ -966,7 +906,8 @@
};
const base64Body = btoa(r.result);
vrcPlusImageRequest.uploadSticker(base64Body, params).then((args) => {
$message({
handleStickerAdd(args);
proxy.$message({
message: t('message.sticker.uploaded'),
type: 'success'
});
@@ -977,19 +918,13 @@
clearFile();
}
function refreshStickerTable() {
emit('refreshStickerTable');
}
function displayStickerUpload() {
document.getElementById('StickerUploadButton').click();
}
function deleteSticker(fileId) {
miscRequest.deleteFile(fileId).then((args) => {
// API.$emit('STICKER:DELETE', args);
// API.$on('STICKER:DELETE')
const array = props.stickerTable;
const array = stickerTable.value;
const { length } = array;
for (let i = 0; i < length; ++i) {
if (args.fileId === array[i].id) {
@@ -1014,7 +949,7 @@
}
if (files[0].size >= 100000000) {
// 100MB
$message({
proxy.$message({
message: t('message.file.too_large'),
type: 'error'
});
@@ -1022,7 +957,7 @@
return;
}
if (!files[0].type.match(/image.*/)) {
$message({
proxy.$message({
message: t('message.file.not_image'),
type: 'error'
});
@@ -1036,20 +971,19 @@
date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
const timestamp = date.toISOString().slice(0, 19);
const params = {
note: props.printUploadNote,
note: printUploadNote.value,
// worldId: '',
timestamp
};
const base64Body = btoa(r.result);
const cropWhiteBorder = props.printCropBorder;
const cropWhiteBorder = printCropBorder.value;
vrcPlusImageRequest.uploadPrint(base64Body, cropWhiteBorder, params).then((args) => {
$message({
proxy.$message({
message: t('message.print.uploaded'),
type: 'success'
});
// API.$on('PRINT:ADD')
if (Object.keys(props.printTable).length !== 0) {
props.printTable.unshift(args.json);
if (Object.keys(printTable.value).length !== 0) {
printTable.value.unshift(args.json);
}
return args;
@@ -1059,22 +993,13 @@
clearFile();
}
function refreshPrintTable() {
emit('refreshPrintTable');
}
function getInventory() {
emit('getInventory');
}
function displayPrintUpload() {
document.getElementById('PrintUploadButton').click();
}
function deletePrint(printId) {
vrcPlusImageRequest.deletePrint(printId).then((args) => {
// API.$on('PRINT:DELETE');
const array = props.printTable;
const array = printTable.value;
const { length } = array;
for (let i = 0; i < length; ++i) {
if (args.printId === array[i].id) {
@@ -1090,8 +1015,8 @@
const args = await inventoryRequest.consumeInventoryBundle({
inventoryId
});
API.currentUserInventory.delete(inventoryId);
const array = props.inventoryTable;
currentUserInventory.value.delete(inventoryId);
const array = inventoryTable.value;
const { length } = array;
for (let i = 0; i < length; ++i) {
if (inventoryId === array[i].id) {

View File

@@ -26,7 +26,7 @@
type="default"
size="small"
icon="el-icon-upload2"
:disabled="!API.currentUser.$isVRCPlus"
:disabled="!currentUser.$isVRCPlus"
@click="displayGalleryUpload"
>{{ t('dialog.gallery_select.upload') }}</el-button
>
@@ -50,30 +50,27 @@
</template>
<script setup>
import { inject, getCurrentInstance } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { vrcPlusImageRequest } from '../../../api';
import { useGalleryStore, useUserStore } from '../../../stores';
const { t } = useI18n();
const { proxy } = getCurrentInstance();
const { $message } = proxy;
const API = inject('API');
const { galleryTable } = storeToRefs(useGalleryStore());
const { refreshGalleryTable, handleGalleryImageAdd } = useGalleryStore();
const { currentUser } = storeToRefs(useUserStore());
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;
@@ -116,13 +113,13 @@
r.onload = function () {
const base64Body = btoa(r.result);
vrcPlusImageRequest.uploadGalleryImage(base64Body).then((args) => {
handleGalleryImageAdd(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);
if (Object.keys(galleryTable.value).length !== 0) {
galleryTable.value.unshift(args.json);
}
return args;
});
@@ -130,7 +127,4 @@
r.readAsBinaryString(files[0]);
clearFile();
}
function refreshGalleryTable() {
emit('refreshGalleryTable');
}
</script>

View File

@@ -36,7 +36,7 @@
</el-popover>
<div style="flex: 1; display: flex; align-items: center; margin-left: 15px">
<div class="group-header" style="flex: 1">
<span v-if="groupDialog.ref.ownerId === API.currentUser.id" style="margin-right: 5px">👑</span>
<span v-if="groupDialog.ref.ownerId === currentUser.id" style="margin-right: 5px">👑</span>
<span class="dialog-title" style="margin-right: 5px" v-text="groupDialog.ref.name"></span>
<span
class="group-discriminator x-grey"
@@ -399,9 +399,9 @@
</span>
<div v-for="room in groupDialog.instances" :key="room.tag" style="width: 100%">
<div style="margin: 5px 0">
<location :location="room.tag" style="display: inline-block" />
<Location :location="room.tag" style="display: inline-block" />
<el-tooltip placement="top" content="Invite yourself" :disabled="hideTooltips">
<invite-yourself :location="room.tag" style="margin-left: 5px" />
<InviteYourself :location="room.tag" style="margin-left: 5px" />
</el-tooltip>
<el-tooltip placement="top" content="Refresh player count" :disabled="hideTooltips">
<el-button
@@ -411,12 +411,11 @@
circle
@click="refreshInstancePlayerCount(room.tag)" />
</el-tooltip>
<last-join :location="room.tag" :currentlocation="lastLocation.location" />
<instance-info
<LastJoin :location="room.tag" :currentlocation="lastLocation.location" />
<InstanceInfo
:location="room.tag"
:instance="room.ref"
:friendcount="room.friendCount"
:updateelement="updateInstanceInfo" />
:friendcount="room.friendCount" />
</div>
<div
v-if="room.users.length"
@@ -437,10 +436,10 @@
v-text="user.displayName" />
<span v-if="user.location === 'traveling'" class="extra">
<i class="el-icon-loading" style="margin-right: 5px" />
<timer :epoch="user.$travelingToTime" />
<Timer :epoch="user.$travelingToTime" />
</span>
<span v-else class="extra">
<timer :epoch="user.$location_at" />
<Timer :epoch="user.$location_at" />
</span>
</div>
</div>
@@ -494,13 +493,14 @@
<span>{{ t('dialog.group.posts.visibility') }}</span>
<br />
<template v-for="roleId in groupDialog.announcement.roleIds">
<template v-for="role in groupDialog.ref.roles"
><span
v-if="role.id === roleId"
:key="roleId + role.id"
v-text="role.name"
/></template>
<span
v-for="(role, rIndex) in groupDialog.ref.roles"
v-if="role.id === roleId"
:key="rIndex"
v-text="role.name" />
<span
:key="rIndex"
:key="roleId"
v-if="
groupDialog.announcement.roleIds.indexOf(roleId) <
groupDialog.announcement.roleIds.length - 1
@@ -511,18 +511,18 @@
</template>
<i class="el-icon-view" style="margin-right: 5px" />
</el-tooltip>
<display-name
<DisplayName
:userid="groupDialog.announcement.authorId"
style="margin-right: 5px" />
<span v-if="groupDialog.announcement.editorId" style="margin-right: 5px">
({{ t('dialog.group.posts.edited_by') }}
<display-name :userid="groupDialog.announcement.editorId" />)
<DisplayName :userid="groupDialog.announcement.editorId" />)
</span>
<el-tooltip placement="bottom">
<template #content>
<span
>{{ t('dialog.group.posts.created_at') }}
{{ groupDialog.announcement.createdAt | formatDate('long') }}</span
{{ formatDateFilter(groupDialog.announcement.createdAt, 'long') }}</span
>
<template
v-if="
@@ -532,11 +532,13 @@
<br />
<span
>{{ t('dialog.group.posts.edited_at') }}
{{ groupDialog.announcement.updatedAt | formatDate('long') }}</span
{{
formatDateFilter(groupDialog.announcement.updatedAt, 'long')
}}</span
>
</template>
</template>
<timer :epoch="Date.parse(groupDialog.announcement.updatedAt)" />
<Timer :epoch="Date.parse(groupDialog.announcement.updatedAt)" />
</el-tooltip>
<template v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')">
<el-tooltip
@@ -593,7 +595,7 @@
<div class="x-friend-item" style="cursor: default">
<div class="detail">
<span class="name">{{ t('dialog.group.info.created_at') }}</span>
<span class="extra">{{ groupDialog.ref.createdAt | formatDate('long') }}</span>
<span class="extra">{{ formatDateFilter(groupDialog.ref.createdAt, 'long') }}</span>
</div>
</div>
<div class="x-friend-item" style="cursor: default">
@@ -667,7 +669,7 @@
<div class="detail">
<span class="name">{{ t('dialog.group.info.joined_at') }}</span>
<span class="extra">{{
groupDialog.ref.myMember.joinedAt | formatDate('long')
formatDateFilter(groupDialog.ref.myMember.joinedAt, 'long')
}}</span>
</div>
</div>
@@ -688,18 +690,18 @@
<br />
<span v-if="role.updatedAt"
>{{ t('dialog.group.info.role_updated_at') }}
{{ role.updatedAt | formatDate('long') }}</span
{{ formatDateFilter(role.updatedAt, 'long') }}</span
>
<span v-else
>{{ t('dialog.group.info.role_created_at') }}
{{ role.createdAt | formatDate('long') }}</span
{{ formatDateFilter(role.createdAt, 'long') }}</span
>
<br />
<span>{{ t('dialog.group.info.role_permissions') }}</span>
<br />
<template v-for="(permission, pIndex) in role.permissions">
<span :key="pIndex">{{ permission }}</span>
<br />
<br :key="pIndex + permission" />
</template>
</template>
<span
@@ -776,38 +778,40 @@
<span>{{ t('dialog.group.posts.visibility') }}</span>
<br />
<template v-for="roleId in post.roleIds">
<span
v-for="(role, rIndex) in groupDialog.ref.roles"
v-if="role.id === roleId"
:key="rIndex"
v-text="role.name" />
<span v-if="post.roleIds.indexOf(roleId) < post.roleIds.length - 1"
>,&nbsp;</span
<template v-for="role in groupDialog.ref.roles"
><span
v-if="role.id === roleId"
:key="role.id + roleId"
v-text="role.name" />
</template>
<template
v-if="post.roleIds.indexOf(roleId) < post.roleIds.length - 1"
><span :key="roleId">,&nbsp;</span></template
>
</template>
</template>
<i class="el-icon-view" style="margin-right: 5px" />
</el-tooltip>
<display-name :userid="post.authorId" style="margin-right: 5px" />
<DisplayName :userid="post.authorId" style="margin-right: 5px" />
<span v-if="post.editorId" style="margin-right: 5px"
>({{ t('dialog.group.posts.edited_by') }}
<display-name :userid="post.editorId" />)</span
<DisplayName :userid="post.editorId" />)</span
>
<el-tooltip placement="bottom">
<template slot="content">
<span
>{{ t('dialog.group.posts.created_at') }}
{{ post.createdAt | formatDate('long') }}</span
{{ formatDateFilter(post.createdAt, 'long') }}</span
>
<template v-if="post.updatedAt !== post.createdAt">
<br />
<span
>{{ t('dialog.group.posts.edited_at') }}
{{ post.updatedAt | formatDate('long') }}</span
{{ formatDateFilter(post.updatedAt, 'long') }}</span
>
</template>
</template>
<timer :epoch="Date.parse(post.updatedAt)" />
<Timer :epoch="Date.parse(post.updatedAt)" />
</el-tooltip>
<template
v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')">
@@ -885,7 +889,7 @@
@click.native.stop>
<el-button size="mini">
<span
>{{ groupDialog.memberSortOrder.name }}
>{{ t(groupDialog.memberSortOrder.name) }}
<i class="el-icon-arrow-down el-icon--right"
/></span>
</el-button>
@@ -894,7 +898,7 @@
v-for="item in groupDialogSortingOptions"
:key="item.name"
@click.native="setGroupMemberSortOrder(item)"
v-text="item.name" />
v-text="t(item.name)" />
</el-dropdown-menu>
</el-dropdown>
<span style="margin-right: 5px">{{ t('dialog.group.members.filter') }}</span>
@@ -906,7 +910,7 @@
@click.native.stop>
<el-button size="mini">
<span
>{{ groupDialog.memberFilter.name }}
>{{ t(groupDialog.memberFilter.name) }}
<i class="el-icon-arrow-down el-icon--right"
/></span>
</el-button>
@@ -915,7 +919,7 @@
v-for="item in groupDialogFilterOptions"
:key="item.name"
@click.native="setGroupMemberFilter(item)"
v-text="item.name" />
v-text="t(item.name)" />
<el-dropdown-item
v-for="item in groupDialog.ref.roles"
v-if="!item.defaultRole"
@@ -984,13 +988,13 @@
</el-tooltip>
</template>
<template v-for="roleId in user.roleIds">
<span
v-for="(role, rIndex) in groupDialog.ref.roles"
v-if="role.id === roleId"
:key="rIndex"
v-text="role.name" />
<span v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1"
>,&nbsp;</span
<template v-for="role in groupDialog.ref.roles"
><span
v-if="role.id === roleId"
:key="role.id + roleId"
v-text="role.name" /></template
><template v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1"
><span :key="roleId">,&nbsp;</span></template
>
</template>
</span>
@@ -1048,19 +1052,18 @@
</el-tooltip>
</template>
<template v-for="roleId in user.roleIds">
<span
v-for="(role, rIndex) in groupDialog.ref.roles"
v-if="role.id === roleId"
:key="rIndex"
v-text="role.name" />
<span v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1"
>,&nbsp;</span
<template v-for="role in groupDialog.ref.roles"
><span
v-if="role.id === roleId"
:key="roleId + role"
v-text="role.name" /></template
><template v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1"
><span :key="roleId">&nbsp;</span></template
>
</template>
</span>
</div>
</li>
<!--FIXME: div in ul-->
<div
v-if="!isGroupMembersDone"
v-loading="isGroupMembersLoading"
@@ -1150,108 +1153,75 @@
<!--Nested-->
<GroupPostEditDialog :dialog-data.sync="groupPostEditDialog" :selected-gallery-file="selectedGalleryFile" />
<GroupMemberModerationDialog
:group-dialog="groupDialog"
:is-group-members-loading.sync="isGroupMembersLoading"
:group-dialog-filter-options="groupDialogFilterOptions"
:group-dialog-sorting-options="groupDialogSortingOptions"
:random-user-colours="randomUserColours"
:group-member-moderation="groupMemberModeration"
@close-dialog="closeMemberModerationDialog"
@group-members-search="groupMembersSearch"
@load-all-group-members="loadAllGroupMembers"
@set-group-member-filter="setGroupMemberFilter"
@set-group-member-sort-order="setGroupMemberSortOrder" />
<InviteGroupDialog
:dialog-data.sync="inviteGroupDialog"
:vip-friends="vipFriends"
:online-friends="onlineFriends"
:offline-friends="offlineFriends"
:active-friends="activeFriends" />
<InviteGroupDialog />
</safe-dialog>
</template>
<script setup>
import { getCurrentInstance, inject, nextTick, reactive, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance, nextTick, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
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 { $app } from '../../../app';
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
import {
adjustDialogZ,
buildTreeData,
copyToClipboard,
downloadAndSaveJson,
getFaviconUrl,
hasGroupPermission,
languageClass,
openExternalLink,
refreshInstancePlayerCount,
removeFromArray,
userImage,
userStatusClass,
formatDateFilter
} from '../../../shared/utils';
import {
useAppearanceSettingsStore,
useGalleryStore,
useGroupStore,
useLocationStore,
useUserStore
} from '../../../stores';
import InviteGroupDialog from '../InviteGroupDialog.vue';
import GroupMemberModerationDialog from './GroupMemberModerationDialog.vue';
import GroupPostEditDialog from './GroupPostEditDialog.vue';
const API = inject('API');
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
const showUserDialog = inject('showUserDialog');
const userStatusClass = inject('userStatusClass');
const userImage = inject('userImage');
const openExternalLink = inject('openExternalLink');
const adjustDialogZ = inject('adjustDialogZ');
const { t } = useI18n();
const { hideTooltips } = storeToRefs(useAppearanceSettingsStore());
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { groupDialog, inviteGroupDialog } = storeToRefs(useGroupStore());
const {
getGroupDialogGroup,
updateGroupPostSearch,
showGroupDialog,
leaveGroupPrompt,
setGroupVisibility,
applyGroupMember,
handleGroupMember,
handleGroupMemberProps
} = useGroupStore();
const { lastLocation } = storeToRefs(useLocationStore());
const { showFullscreenImageDialog } = useGalleryStore();
const instance = getCurrentInstance();
const $confirm = instance.proxy.$confirm;
const $message = instance.proxy.$message;
const props = defineProps({
groupDialog: {
type: Object,
required: true
},
hideTooltips: {
type: Boolean,
default: false
},
lastLocation: {
type: Object,
required: true
},
updateInstanceInfo: {
type: Number,
required: true
},
groupDialogSortingOptions: {
type: Object,
required: true
},
groupDialogFilterOptions: {
type: Object,
required: true
},
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',
'groupDialogCommand',
'getGroupDialogGroup',
'updateGroupPostSearch'
]);
const groupDialogRef = ref(null);
const isGroupMembersDone = ref(false);
const isGroupMembersLoading = ref(false);
@@ -1283,20 +1253,10 @@
auditLogTypes: []
});
const inviteGroupDialog = ref({
visible: false,
loading: false,
groupId: '',
groupName: '',
userId: '',
userIds: [],
userObject: {}
});
let loadMoreGroupMembersParams = {};
watch(
() => props.groupDialog.loading,
() => groupDialog.value.loading,
(val) => {
if (val) {
nextTick(() => adjustDialogZ(groupDialogRef.value.$el));
@@ -1305,7 +1265,7 @@
);
watch(
() => props.groupDialog.isGetGroupDialogGroupLoading,
() => groupDialog.value.isGetGroupDialogGroupLoading,
(val) => {
if (val) {
getCurrentTabData();
@@ -1314,14 +1274,9 @@
);
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;
inviteGroupDialog.value.groupId = groupId;
inviteGroupDialog.value.userId = userId;
inviteGroupDialog.value.visible = true;
}
function setGroupRepresentation(groupId) {
@@ -1360,7 +1315,7 @@
}
function groupMembersSearchDebounce() {
const D = props.groupDialog;
const D = groupDialog.value;
const search = D.memberSearch;
D.memberSearchResults = [];
if (!search || search.length < 3) {
@@ -1375,16 +1330,14 @@
offset: 0
})
.then((args) => {
// API.$on('GROUP:MEMBERS:SEARCH', function (args) {
for (const json of args.json.results) {
API.$emit('GROUP:MEMBER', {
handleGroupMember({
json,
params: {
groupId: args.params.groupId
}
});
}
// });
if (D.id === args.params.groupId) {
D.memberSearchResults = args.json.results;
}
@@ -1400,11 +1353,10 @@
isRepresenting: isSet
})
.then((args) => {
// API.$on('GROUP:SETREPRESENTATION', function (args) {
if (props.groupDialog.visible && props.groupDialog.id === groupId) {
if (groupDialog.value.visible && groupDialog.value.id === args.groupId) {
updateGroupDialogData({
...props.groupDialog,
ref: { ...props.groupDialog.ref, isRepresenting: args.params.isRepresenting }
...groupDialog.value,
ref: { ...groupDialog.value.ref, isRepresenting: args.params.isRepresenting }
});
getGroupDialogGroup(groupId);
}
@@ -1417,11 +1369,9 @@
groupId: id
})
.then((args) => {
// API.$on('GROUP:CANCELJOINREQUEST', function (args) {
if (props.groupDialog.visible && props.groupDialog.id === id) {
if (groupDialog.value.visible && groupDialog.value.id === id) {
getGroupDialogGroup(id);
}
// });
});
}
function confirmDeleteGroupPost(post) {
@@ -1437,8 +1387,7 @@
postId: post.id
})
.then((args) => {
// API.$on('GROUP:POST:DELETE', function (args) {
const D = props.groupDialog;
const D = groupDialog.value;
if (D.id !== args.params.groupId) {
return;
}
@@ -1447,7 +1396,7 @@
// remove existing post
for (const item of D.posts) {
if (item.id === postId) {
utils.removeFromArray(D.posts, item);
removeFromArray(D.posts, item);
break;
}
}
@@ -1460,7 +1409,6 @@
}
}
updateGroupPostSearch();
// });
});
}
}
@@ -1480,30 +1428,113 @@
}
function groupDialogCommand(command) {
const D = props.groupDialog;
const D = groupDialog.value;
if (D.visible === false) {
return;
}
switch (command) {
case 'Share':
copyToClipboard(props.groupDialog.ref.$url);
copyToClipboard(groupDialog.value.ref.$url);
break;
case 'Create Post':
showGroupPostEditDialog(props.groupDialog.id, null);
showGroupPostEditDialog(groupDialog.value.id, null);
break;
case 'Moderation Tools':
showGroupMemberModerationDialog(props.groupDialog.id);
showGroupMemberModerationDialog(groupDialog.value.id);
break;
case 'Invite To Group':
showInviteGroupDialog(D.id, '');
break;
default:
emit('groupDialogCommand', command);
case 'Refresh':
showGroupDialog(D.id);
break;
case 'Leave Group':
leaveGroupPrompt(D.id);
break;
case 'Block Group':
blockGroup(D.id);
break;
case 'Unblock Group':
unblockGroup(D.id);
break;
case 'Visibility Everyone':
setGroupVisibility(D.id, 'visible');
break;
case 'Visibility Friends':
setGroupVisibility(D.id, 'friends');
break;
case 'Visibility Hidden':
setGroupVisibility(D.id, 'hidden');
break;
case 'Subscribe To Announcements':
setGroupSubscription(D.id, true);
break;
case 'Unsubscribe To Announcements':
setGroupSubscription(D.id, false);
break;
}
}
function setGroupSubscription(groupId, subscribe) {
return groupRequest
.setGroupMemberProps(currentUser.value.id, groupId, {
isSubscribedToAnnouncements: subscribe
})
.then((args) => {
handleGroupMemberProps(args);
$app.$message({
message: 'Group subscription updated',
type: 'success'
});
return args;
});
}
function blockGroup(groupId) {
$app.$confirm('Are you sure you want to block this group?', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
groupRequest
.blockGroup({
groupId
})
.then((args) => {
if (groupDialog.value.visible && groupDialog.value.id === args.params.groupId) {
showGroupDialog(args.params.groupId);
}
});
}
}
});
}
function unblockGroup(groupId) {
$app.$confirm('Are you sure you want to unblock this group?', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
groupRequest
.unblockGroup({
groupId,
userId: currentUser.value.id
})
.then((args) => {
if (groupDialog.value.visible && groupDialog.value.id === args.params.groupId) {
showGroupDialog(args.params.groupId);
}
});
}
}
});
}
function showGroupMemberModerationDialog(groupId) {
if (groupId !== props.groupDialog.id) {
if (groupId !== groupDialog.value.id) {
return;
}
const D = groupMemberModeration;
@@ -1511,16 +1542,14 @@
D.groupRef = {};
D.auditLogTypes = [];
API.getCachedGroup({ groupId }).then((args) => {
groupRequest.getCachedGroup({ groupId }).then((args) => {
D.groupRef = args.ref;
if (hasGroupPermission(D.groupRef, 'group-audit-view')) {
groupRequest.getGroupAuditLogTypes({ groupId }).then((args) => {
// API.$on('GROUP:AUDITLOGTYPES', function (args) {
if (groupMemberModeration.id !== args.params.groupId) {
return;
}
groupMemberModeration.auditLogTypes = args.json;
// });
});
}
});
@@ -1535,16 +1564,14 @@
groupId: id
})
.then((args) => {
// API.$on('GROUP:JOIN', function (args) {
if (props.groupDialog.visible && props.groupDialog.id === id) {
if (groupDialog.value.visible && groupDialog.value.id === id) {
updateGroupDialogData({
...props.groupDialog,
...groupDialog.value,
inGroup: args.json.membershipStatus === 'member'
});
// props.groupDialog.inGroup = args.json.membershipStatus === 'member';
// groupDialog.value.inGroup = json.membershipStatus === 'member';
getGroupDialogGroup(id);
}
// });
if (args.json.membershipStatus === 'member') {
$message({
message: 'Group joined',
@@ -1595,14 +1622,14 @@
selectedImageUrl: post.imageUrl
};
}
API.getCachedGroup({ groupId }).then((args) => {
groupRequest.getCachedGroup({ groupId }).then((args) => {
D.groupRef = args.ref;
});
D.visible = true;
}
async function getGroupDialogGroupMembers() {
const D = props.groupDialog;
const D = groupDialog.value;
D.members = [];
isGroupMembersDone.value = false;
loadMoreGroupMembersParams = {
@@ -1620,12 +1647,12 @@
await groupRequest
.getGroupMember({
groupId: D.id,
userId: API.currentUser.id
userId: currentUser.value.id
})
.then((args) => {
args.ref = API.applyGroupMember(args.json);
args.ref = applyGroupMember(args.json);
if (args.json) {
args.json.user = API.currentUser;
args.json.user = currentUser.value;
if (D.memberFilter.id === null) {
// when flitered by role don't include self
D.members.push(args.json);
@@ -1641,7 +1668,7 @@
if (isGroupMembersDone.value || isGroupMembersLoading.value) {
return;
}
const D = props.groupDialog;
const D = groupDialog.value;
const params = loadMoreGroupMembersParams;
D.memberSearch = '';
isGroupMembersLoading.value = true;
@@ -1651,10 +1678,18 @@
isGroupMembersLoading.value = false;
})
.then((args) => {
for (const json of args.json) {
handleGroupMember({
json,
params: {
groupId: args.params.groupId
}
});
}
for (let i = 0; i < args.json.length; i++) {
const member = args.json[i];
if (member.userId === API.currentUser.id) {
if (D.members.length > 0 && D.members[0].userId === API.currentUser.id) {
if (member.userId === currentUser.value.id) {
if (D.members.length > 0 && D.members[0].userId === currentUser.value.id) {
// remove duplicate and keep sort order
D.members.splice(0, 1);
}
@@ -1685,12 +1720,12 @@
}
async function getGroupGalleries() {
updateGroupDialogData({ ...props.groupDialog, galleries: {} });
updateGroupDialogData({ ...groupDialog.value, galleries: {} });
groupDialogGalleryCurrentName.value = '0';
isGroupGalleryLoading.value = true;
for (let i = 0; i < props.groupDialog.ref.galleries.length; i++) {
const gallery = props.groupDialog.ref.galleries[i];
await getGroupGallery(props.groupDialog.id, gallery.id);
for (let i = 0; i < groupDialog.value.ref.galleries.length; i++) {
const gallery = groupDialog.value.ref.galleries[i];
await getGroupGallery(groupDialog.value.id, gallery.id);
}
isGroupGalleryLoading.value = false;
}
@@ -1707,16 +1742,14 @@
for (let i = 0; i < count; i++) {
const args = await groupRequest.getGroupGallery(params);
if (args) {
// API.$on('GROUP:GALLERY', function (args) {
for (const json of args.json) {
if (props.groupDialog.id === json.groupId) {
if (!props.groupDialog.galleries[json.galleryId]) {
props.groupDialog.galleries[json.galleryId] = [];
if (groupDialog.value.id === json.groupId) {
if (!groupDialog.value.galleries[json.galleryId]) {
groupDialog.value.galleries[json.galleryId] = [];
}
props.groupDialog.galleries[json.galleryId].push(json);
groupDialog.value.galleries[json.galleryId].push(json);
}
}
// });
}
params.offset += 100;
if (args.json.length < 100) {
@@ -1729,8 +1762,8 @@
}
function refreshGroupDialogTreeData() {
const D = props.groupDialog;
const treeData = utils.buildTreeData({
const D = groupDialog.value;
const treeData = buildTreeData({
group: D.ref,
posts: D.posts,
instances: D.instances,
@@ -1738,7 +1771,7 @@
galleries: D.galleries
});
updateGroupDialogData({
...props.groupDialog,
...groupDialog.value,
treeData
});
}
@@ -1748,19 +1781,19 @@
return;
}
await getGroupDialogGroupMembers();
while (props.groupDialog.visible && !isGroupMembersDone.value) {
while (groupDialog.value.visible && !isGroupMembersDone.value) {
isGroupMembersLoading.value = true;
await new Promise((resolve) => {
workerTimers.setTimeout(resolve, 1000);
});
isGroupMembersLoading.value = false;
await this.loadMoreGroupMembers();
await loadMoreGroupMembers();
}
}
async function setGroupMemberSortOrder(sortOrder) {
const D = props.groupDialog;
if (D.memberSortOrder === sortOrder) {
const D = groupDialog.value;
if (D.memberSortOrder.value === sortOrder) {
return;
}
D.memberSortOrder = sortOrder;
@@ -1768,8 +1801,8 @@
}
async function setGroupMemberFilter(filter) {
const D = props.groupDialog;
if (D.memberFilter === filter) {
const D = groupDialog.value;
if (D.memberFilter.value === filter) {
return;
}
D.memberFilter = filter;
@@ -1777,13 +1810,9 @@
}
function updateGroupDialogData(obj) {
// Be careful with the deep merge
emit('update:group-dialog', obj);
}
function getGroupDialogGroup(groupId) {
emit('getGroupDialogGroup', groupId);
}
function updateGroupPostSearch() {
emit('updateGroupPostSearch');
groupDialog.value = {
...groupDialog.value,
...obj
};
}
</script>

View File

@@ -18,7 +18,7 @@
icon="el-icon-refresh"
:loading="isGroupMembersLoading"
circle
@click="loadAllGroupMembers"></el-button>
@click="loadAllGroupMembers" />
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">
{{ groupMemberModerationTable.data.length }}/{{
groupMemberModeration.groupRef.memberCount
@@ -40,7 +40,7 @@
@click.native.stop>
<el-button size="mini">
<span
>{{ groupDialog.memberSortOrder.name }}
>{{ t(groupDialog.memberSortOrder.name) }}
<i class="el-icon-arrow-down el-icon--right"></i
></span>
</el-button>
@@ -49,7 +49,7 @@
v-for="item in groupDialogSortingOptions"
:key="item.name"
@click.native="setGroupMemberSortOrder(item)">
{{ item.name }}
{{ t(item.name) }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
@@ -68,7 +68,7 @@
@click.native.stop>
<el-button size="mini">
<span
>{{ groupDialog.memberFilter.name }}
>{{ t(groupDialog.memberFilter.name) }}
<i class="el-icon-arrow-down el-icon--right"></i
></span>
</el-button>
@@ -77,7 +77,7 @@
v-for="item in groupDialogFilterOptions"
:key="item.name"
@click.native="setGroupMemberFilter(item)"
v-text="item.name"></el-dropdown-item>
v-text="t(item.name)"></el-dropdown-item>
<el-dropdown-item
v-for="item in groupDialog.ref.roles"
v-if="!item.defaultRole"
@@ -147,12 +147,11 @@
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
<template slot-scope="scope">
<template v-for="(roleId, index) in scope.row.roleIds">
<span
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
v-if="role?.id === roleId"
:key="rIndex"
>{{ role.name
}}<span v-if="index < scope.row.roleIds.length - 1">, </span></span
<template v-for="(role, rIndex) in groupMemberModeration.groupRef.roles">
<span v-if="role?.id === roleId" :key="roleId + rIndex"
>{{ role.name
}}<span v-if="index < scope.row.roleIds.length - 1">, </span></span
></template
>
</template>
</template>
@@ -171,7 +170,7 @@
prop="joinedAt"
sortable>
<template slot-scope="scope">
<span>{{ scope.row.joinedAt | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.joinedAt, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
@@ -284,7 +283,7 @@
prop="joinedAt"
sortable>
<template slot-scope="scope">
<span>{{ scope.row.joinedAt | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.joinedAt, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
@@ -293,7 +292,7 @@
prop="bannedAt"
sortable>
<template slot-scope="scope">
<span>{{ scope.row.bannedAt | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.bannedAt, 'long') }}</span>
</template>
</el-table-column>
</data-tables>
@@ -629,7 +628,7 @@
prop="created_at"
sortable>
<template slot-scope="scope">
<span>{{ scope.row.created_at | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
@@ -656,7 +655,7 @@
:label="t('dialog.group_member_moderation.description')"
prop="description">
<template slot-scope="scope">
<location
<Location
v-if="scope.row?.targetId.startsWith('wrld_')"
:location="scope.row.targetId" />
<span v-text="scope.row.description"></span>
@@ -810,45 +809,165 @@
</template>
<script setup>
import Location from '../../Location.vue';
import { getCurrentInstance, inject, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { groupRequest, userRequest } from '../../../api';
import { useModerationTable, useSelectedUsers } from '../../../composables/group/useGroupMemberModeration';
import { hasGroupPermission } from '../../../composables/group/utils';
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
import { hasGroupPermission, userImage, userImageFull, formatDateFilter } from '../../../shared/utils';
import { useAppearanceSettingsStore, useGalleryStore, useGroupStore, useUserStore } from '../../../stores';
import GroupMemberModerationExportDialog from './GroupMemberModerationExportDialog.vue';
const API = inject('API');
const showUserDialog = inject('showUserDialog');
const userImage = inject('userImage');
const userImageFull = inject('userImageFull');
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { groupDialog } = storeToRefs(useGroupStore());
const { applyGroupMember, handleGroupMemberProps } = useGroupStore();
const { showFullscreenImageDialog } = useGalleryStore();
const { t } = useI18n();
const instance = getCurrentInstance();
const $message = instance.proxy.$message;
const selectedUsers = reactive({});
const selectedUsersArray = ref([]);
function setSelectedUsers(usersId, user) {
if (!user) {
return;
}
selectedUsers[usersId] = user;
selectedUsersArray.value = Object.values(selectedUsers);
}
function deselectedUsers(userId, isAll = false) {
if (isAll) {
for (const id in selectedUsers) {
if (Object.prototype.hasOwnProperty.call(selectedUsers, id)) {
delete selectedUsers[id];
}
}
} else {
if (Object.prototype.hasOwnProperty.call(selectedUsers, userId)) {
delete selectedUsers[userId];
}
}
selectedUsersArray.value = Object.values(selectedUsers);
}
function groupMemberModerationTableSelectionChange(row) {
if (row.$selected && !selectedUsers[row.userId]) {
setSelectedUsers(row.userId, row);
} else if (!row.$selected && selectedUsers[row.userId]) {
deselectedUsers(row.userId);
}
}
const groupInvitesModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupJoinRequestsModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupBlockedModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupLogsModerationTable = reactive({
data: [],
filters: [{ prop: ['description'], value: '' }],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupBansModerationTable = reactive({
data: [],
filters: [{ prop: ['$displayName'], value: '' }],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupMemberModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
async function initializePageSize() {
try {
const { tablePageSize } = storeToRefs(useAppearanceSettingsStore());
groupMemberModerationTable.pageSize = tablePageSize.value;
groupBansModerationTable.pageSize = tablePageSize.value;
groupLogsModerationTable.pageSize = tablePageSize.value;
groupInvitesModerationTable.pageSize = tablePageSize.value;
groupJoinRequestsModerationTable.pageSize = tablePageSize.value;
groupBlockedModerationTable.pageSize = tablePageSize.value;
} catch (error) {
console.error('Failed to initialize table page size:', error);
}
}
function deselectGroupMember(userId) {
const deselectInTable = (tableData) => {
if (userId) {
const row = tableData.find((item) => item.userId === userId);
if (row) {
row.$selected = false;
}
} else {
tableData.forEach((row) => {
if (row.$selected) {
row.$selected = false;
}
});
}
};
deselectInTable(groupMemberModerationTable.data);
deselectInTable(groupBansModerationTable.data);
deselectInTable(groupInvitesModerationTable.data);
deselectInTable(groupJoinRequestsModerationTable.data);
deselectInTable(groupBlockedModerationTable.data);
}
const props = defineProps({
isGroupMembersLoading: {
type: Boolean,
default: false
},
groupDialog: {
type: Object,
required: true
},
groupDialogSortingOptions: {
type: Object,
required: true
},
groupDialogFilterOptions: {
type: Object,
required: true
},
randomUserColours: {
type: Boolean,
default: false
},
groupMemberModeration: {
type: Object,
required: true
@@ -864,25 +983,6 @@
'group-members-search'
]);
const {
groupInvitesModerationTable,
groupJoinRequestsModerationTable,
groupBlockedModerationTable,
groupLogsModerationTable,
groupBansModerationTable,
groupMemberModerationTable,
initializePageSize,
deselectGroupMember
} = useModerationTable();
const {
selectedUsers,
selectedUsersArray,
groupMemberModerationTableSelectionChange,
deselectedUsers,
setSelectedUsers
} = useSelectedUsers();
const selectUserId = ref('');
const progressCurrent = ref(0);
const progressTotal = ref(0);
@@ -895,7 +995,7 @@
() => props.groupMemberModeration.visible,
(newVal) => {
if (newVal) {
if (props.groupMemberModeration.id !== props.groupDialog.id) {
if (props.groupMemberModeration.id !== groupDialog.value.id) {
return;
}
groupMemberModerationTable.data = [];
@@ -905,6 +1005,7 @@
groupBlockedModerationTable.data = [];
groupLogsModerationTable.data = [];
Object.assign(selectedUsers, {});
selectedUsersArray.value = [];
selectUserId.value = '';
selectedRoles.value = [];
note.value = '';
@@ -913,7 +1014,7 @@
);
watch(
() => props.groupDialog.members,
() => groupDialog.value.members,
(newVal) => {
if (newVal) {
setGroupMemberModerationTable(newVal);
@@ -923,7 +1024,7 @@
);
watch(
() => props.groupDialog.memberSearchResults,
() => groupDialog.value.memberSearchResults,
(newVal) => {
if (newVal) {
setGroupMemberModerationTable(newVal);
@@ -965,9 +1066,8 @@
}
function handleGroupMemberRoleChange(args) {
// 'GROUP:MEMBER:ROLE:CHANGE'
if (props.groupDialog.id === args.params.groupId) {
props.groupDialog.members.forEach((member) => {
if (groupDialog.value.id === args.params.groupId) {
groupDialog.value.members.forEach((member) => {
if (member.userId === args.params.userId) {
member.roleIds = args.json;
return true;
@@ -988,7 +1088,7 @@
}
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) {
if (user.userId === currentUser.value.id) {
continue;
}
console.log(`Deleting group invite ${user.userId} ${i + 1}/${memberCount}`);
@@ -1014,7 +1114,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvites(D.id, groupInvitesModerationTable);
getAllGroupInvites(D.id);
}
function selectAllGroupMembers() {
@@ -1038,7 +1138,7 @@
continue;
}
args.json.forEach((json) => {
const ref = API.applyGroupMember(json);
const ref = applyGroupMember(json);
fetchedBans.push(ref);
});
if (args.json.length < params.n) {
@@ -1076,7 +1176,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Banning ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.banGroupMember({ groupId: D.id, userId: user.userId });
@@ -1105,7 +1205,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Unbanning ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.unbanGroupMember({ groupId: D.id, userId: user.userId });
@@ -1138,7 +1238,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Kicking ${user.userId} ${i + 1}/${memberCount}`);
try {
@@ -1158,6 +1258,7 @@
progressCurrent.value = 0;
progressTotal.value = 0;
deselectedUsers(null, true);
loadAllGroupMembers();
}
async function groupMembersSaveNote() {
@@ -1176,7 +1277,8 @@
}
console.log(`Setting note ${noteToSave} for ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.setGroupMemberProps(user.userId, D.id, { managerNotes: noteToSave });
const args = await groupRequest.setGroupMemberProps(user.userId, D.id, { managerNotes: noteToSave });
handleGroupMemberProps(args);
} catch (err) {
console.error(err);
$message({
@@ -1334,7 +1436,7 @@
let member = {};
const memberArgs = await groupRequest.getGroupMember({ groupId: D.id, userId });
if (memberArgs && memberArgs.json) {
member = API.applyGroupMember(memberArgs.json);
member = applyGroupMember(memberArgs.json);
}
if (member && member.user) {
setSelectedUsers(member.userId, member);
@@ -1400,7 +1502,7 @@
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) {
if (user.userId === currentUser.value.id) {
continue;
}
@@ -1424,7 +1526,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests();
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
@@ -1438,7 +1540,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Blocking group join request from ${user.userId} ${i + 1}/${memberCount}`);
try {
@@ -1460,7 +1562,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests();
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
@@ -1475,7 +1577,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Rejecting group join request from ${user.userId} ${i + 1}/${memberCount}`);
try {
@@ -1497,7 +1599,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests();
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
@@ -1511,7 +1613,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Accepting group join request from ${user.userId} ${i + 1}/${memberCount}`);
try {
@@ -1533,7 +1635,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests();
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
@@ -1565,7 +1667,7 @@
? groupBlockedModerationTable
: groupJoinRequestsModerationTable;
for (const json of args.json) {
const ref = API.applyGroupMember(json);
const ref = applyGroupMember(json);
targetTable.data.push(ref);
}
params.offset += params.n;
@@ -1598,7 +1700,7 @@
? groupBlockedModerationTable
: groupJoinRequestsModerationTable;
for (const json of args.json) {
const ref = API.applyGroupMember(json);
const ref = applyGroupMember(json);
targetTable.data.push(ref);
}
params.offset += params.n;
@@ -1631,7 +1733,7 @@
}
for (const json of args.json) {
const ref = API.applyGroupMember(json);
const ref = applyGroupMember(json);
groupInvitesModerationTable.data.push(ref);
}
}
@@ -1649,7 +1751,7 @@
type: 'error'
});
} finally {
updateIsGroupMembersLoading(false); // Use emit
updateIsGroupMembersLoading(false);
}
}

View File

@@ -32,7 +32,7 @@
<script setup>
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { copyToClipboard } from '../../../composables/shared/utils';
import { copyToClipboard } from '../../../shared/utils';
const { t } = useI18n();

View File

@@ -1,16 +1,16 @@
<template>
<safe-dialog
:visible.sync="groupPostEditDialog.visible"
:title="$t('dialog.group_post_edit.header')"
:title="t('dialog.group_post_edit.header')"
width="650px"
append-to-body>
<div v-if="groupPostEditDialog.visible">
<h3 v-text="groupPostEditDialog.groupRef.name"></h3>
<el-form :model="groupPostEditDialog" label-width="150px">
<el-form-item :label="$t('dialog.group_post_edit.title')">
<el-form-item :label="t('dialog.group_post_edit.title')">
<el-input v-model="groupPostEditDialog.title" size="mini"></el-input>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.message')">
<el-form-item :label="t('dialog.group_post_edit.message')">
<el-input
v-model="groupPostEditDialog.text"
type="textarea"
@@ -24,29 +24,27 @@
v-if="!groupPostEditDialog.postId"
v-model="groupPostEditDialog.sendNotification"
size="small">
{{ $t('dialog.group_post_edit.send_notification') }}
{{ t('dialog.group_post_edit.send_notification') }}
</el-checkbox>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.post_visibility')">
<el-form-item :label="t('dialog.group_post_edit.post_visibility')">
<el-radio-group v-model="groupPostEditDialog.visibility" size="small">
<el-radio label="public">
{{ $t('dialog.group_post_edit.visibility_public') }}
{{ t('dialog.group_post_edit.visibility_public') }}
</el-radio>
<el-radio label="group">
{{ $t('dialog.group_post_edit.visibility_group') }}
{{ t('dialog.group_post_edit.visibility_group') }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="groupPostEditDialog.visibility === 'group'"
:label="$t('dialog.new_instance.roles')">
<el-form-item v-if="groupPostEditDialog.visibility === 'group'" :label="t('dialog.new_instance.roles')">
<el-select
v-model="groupPostEditDialog.roleIds"
multiple
clearable
:placeholder="$t('dialog.new_instance.role_placeholder')"
:placeholder="t('dialog.new_instance.role_placeholder')"
style="width: 100%">
<el-option-group :label="$t('dialog.new_instance.role_placeholder')">
<el-option-group :label="t('dialog.new_instance.role_placeholder')">
<el-option
v-for="role in groupPostEditDialog.groupRef?.roles"
:key="role.id"
@@ -60,7 +58,7 @@
</el-option-group>
</el-select>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.image')">
<el-form-item :label="t('dialog.group_post_edit.image')">
<template v-if="gallerySelectDialog.selectedFileId">
<div style="display: inline-block; flex: none; margin-right: 5px">
<el-popover placement="right" width="500px" trigger="click">
@@ -80,13 +78,13 @@
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />
</el-popover>
<el-button size="mini" style="vertical-align: top" @click="clearImageGallerySelect">
{{ $t('dialog.invite_message.clear_selected_image') }}
{{ t('dialog.invite_message.clear_selected_image') }}
</el-button>
</div>
</template>
<template v-else>
<el-button size="mini" style="margin-right: 5px" @click="showGallerySelectDialog">
{{ $t('dialog.invite_message.select_image') }}
{{ t('dialog.invite_message.select_image') }}
</el-button>
</template>
</el-form-item>
@@ -94,13 +92,13 @@
</div>
<template #footer>
<el-button size="small" @click="groupPostEditDialog.visible = false">
{{ $t('dialog.group_post_edit.cancel') }}
{{ t('dialog.group_post_edit.cancel') }}
</el-button>
<el-button v-if="groupPostEditDialog.postId" size="small" @click="editGroupPost">
{{ $t('dialog.group_post_edit.edit_post') }}
{{ t('dialog.group_post_edit.edit_post') }}
</el-button>
<el-button v-else size="small" @click="createGroupPost">
{{ $t('dialog.group_post_edit.create_post') }}
{{ t('dialog.group_post_edit.create_post') }}
</el-button>
</template>
<GallerySelectDialog
@@ -110,114 +108,115 @@
</safe-dialog>
</template>
<script>
<script setup>
import { ref, computed, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { groupRequest, vrcPlusIconRequest } from '../../../api';
import { useGalleryStore, useGroupStore } from '../../../stores';
import GallerySelectDialog from './GallerySelectDialog.vue';
export default {
name: 'GroupPostEditDialog',
components: {
GallerySelectDialog
const props = defineProps({
dialogData: {
type: Object,
required: true
},
inject: ['showFullscreenImageDialog'],
props: {
dialogData: {
type: Object,
required: true
},
selectedGalleryFile: { type: Object, default: () => ({}) }
selectedGalleryFile: { type: Object, default: () => ({}) }
});
const emit = defineEmits(['update:dialogData']);
const { proxy } = getCurrentInstance();
const { t } = useI18n();
const { showFullscreenImageDialog, handleFilesList } = useGalleryStore();
const { handleGroupPost } = useGroupStore();
const gallerySelectDialog = ref({
visible: false,
selectedFileId: '',
selectedImageUrl: ''
});
const galleryTable = ref([]);
const groupPostEditDialog = computed({
get() {
return props.dialogData;
},
data() {
return {
gallerySelectDialog: {
visible: false,
selectedFileId: '',
selectedImageUrl: ''
},
galleryTable: []
};
},
computed: {
groupPostEditDialog: {
get() {
return this.dialogData;
},
set(value) {
this.$emit('update:dialog-data', value);
}
}
},
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) {
return;
}
const params = {
groupId: D.groupId,
postId: D.postId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
imageId: null
};
if (this.gallerySelectDialog.selectedFileId) {
params.imageId = this.gallerySelectDialog.selectedFileId;
}
groupRequest.editGroupPost(params).then((args) => {
this.$message({
message: 'Group post edited',
type: 'success'
});
return args;
});
D.visible = false;
},
createGroupPost() {
const D = this.groupPostEditDialog;
const params = {
groupId: D.groupId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
sendNotification: D.sendNotification,
imageId: null
};
if (this.gallerySelectDialog.selectedFileId) {
params.imageId = this.gallerySelectDialog.selectedFileId;
}
groupRequest.createGroupPost(params).then((args) => {
this.$message({
message: 'Group post created',
type: 'success'
});
return args;
});
D.visible = false;
},
clearImageGallerySelect() {
const D = this.gallerySelectDialog;
D.selectedFileId = '';
D.selectedImageUrl = '';
}
set(value) {
emit('update:dialogData', value);
}
};
});
function showGallerySelectDialog() {
const D = gallerySelectDialog.value;
D.visible = true;
refreshGalleryTable();
}
async function refreshGalleryTable() {
const params = {
n: 100,
tag: 'gallery'
};
const args = await vrcPlusIconRequest.getFileList(params);
handleFilesList(args);
if (args.params.tag === 'gallery') {
galleryTable.value = args.json.reverse();
}
}
function editGroupPost() {
const D = groupPostEditDialog.value;
if (!D.groupId || !D.postId) {
return;
}
const params = {
groupId: D.groupId,
postId: D.postId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
imageId: null
};
if (gallerySelectDialog.value.selectedFileId) {
params.imageId = gallerySelectDialog.value.selectedFileId;
}
groupRequest.editGroupPost(params).then((args) => {
handleGroupPost();
proxy.$message({
message: 'Group post edited',
type: 'success'
});
return args;
});
D.visible = false;
}
function createGroupPost() {
const D = groupPostEditDialog.value;
const params = {
groupId: D.groupId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
sendNotification: D.sendNotification,
imageId: null
};
if (gallerySelectDialog.value.selectedFileId) {
params.imageId = gallerySelectDialog.value.selectedFileId;
}
groupRequest.createGroupPost(params).then((args) => {
handleGroupPost();
proxy.$message({
message: 'Group post created',
type: 'success'
});
return args;
});
D.visible = false;
}
function clearImageGallerySelect() {
const D = gallerySelectDialog.value;
D.selectedFileId = '';
D.selectedImageUrl = '';
}
</script>

View File

@@ -32,17 +32,20 @@
</template>
<script setup>
import { getCurrentInstance, inject } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { instanceRequest, inviteMessagesRequest, notificationRequest } from '../../../api';
import { parseLocation } from '../../../composables/instance/utils';
import { parseLocation } from '../../../shared/utils';
import { useGalleryStore, useUserStore } from '../../../stores';
const { t } = useI18n();
const instance = getCurrentInstance();
const $message = instance.proxy.$message;
const API = inject('API');
const clearInviteImageUpload = inject('clearInviteImageUpload');
const { uploadImage } = storeToRefs(useGalleryStore());
const { clearInviteImageUpload } = useGalleryStore();
const { currentUser } = storeToRefs(useUserStore());
const props = defineProps({
editAndSendInviteDialog: {
@@ -57,9 +60,6 @@
type: Object,
required: false,
default: () => ({})
},
uploadImage: {
type: String
}
});
@@ -85,7 +85,6 @@
throw err;
})
.then((args) => {
API.$emit(`INVITE:${messageType.toUpperCase()}`, args);
if (args.json[slot].message === I.messageSlot.message) {
$message({
message: "VRChat API didn't update message, try again",
@@ -103,7 +102,7 @@
const inviteLoop = () => {
if (J.userIds.length > 0) {
const receiverUserId = J.userIds.shift();
if (receiverUserId === API.currentUser.id) {
if (receiverUserId === currentUser.value.id) {
// can't invite self!?
const L = parseLocation(J.worldId);
instanceRequest
@@ -112,7 +111,7 @@
worldId: L.worldId
})
.finally(inviteLoop);
} else if (props.uploadImage) {
} else if (uploadImage.value) {
notificationRequest
.sendInvitePhoto(
{
@@ -149,7 +148,7 @@
inviteLoop();
} else if (messageType === 'invite') {
I.params.messageSlot = slot;
if (props.uploadImage) {
if (uploadImage.value) {
notificationRequest
.sendInvitePhoto(I.params, I.userId)
.catch((err) => {
@@ -178,7 +177,7 @@
}
} else if (messageType === 'request') {
I.params.requestSlot = slot;
if (props.uploadImage) {
if (uploadImage.value) {
notificationRequest
.sendRequestInvitePhoto(I.params, I.userId)
.catch((err) => {

View File

@@ -6,7 +6,7 @@
width="500px"
append-to-body>
<div v-if="inviteDialog.visible" v-loading="inviteDialog.loading">
<location :location="inviteDialog.worldId" :link="false"></location>
<Location :location="inviteDialog.worldId" :link="false" />
<br />
<el-button size="mini" style="margin-top: 10px" @click="addSelfToInvite">{{
t('dialog.invite.add_self')
@@ -34,17 +34,17 @@
filterable
:disabled="inviteDialog.loading"
style="width: 100%; margin-top: 15px">
<el-option-group v-if="API.currentUser" :label="t('side_panel.me')">
<el-option-group v-if="currentUser" :label="t('side_panel.me')">
<el-option
class="x-friend-item"
:label="API.currentUser.displayName"
:value="API.currentUser.id"
:label="currentUser.displayName"
:value="currentUser.id"
style="height: auto">
<div :class="['avatar', userStatusClass(API.currentUser)]">
<img v-lazy="userImage(API.currentUser)" />
<div :class="['avatar', userStatusClass(currentUser)]">
<img v-lazy="userImage(currentUser)" />
</div>
<div class="detail">
<span class="name">{{ API.currentUser.displayName }}</span>
<span class="name">{{ currentUser.displayName }}</span>
</div>
</el-option>
</el-option-group>
@@ -156,57 +156,35 @@
</template>
<SendInviteDialog
:send-invite-dialog-visible.sync="sendInviteDialogVisible"
:invite-message-table="inviteMessageTable"
:send-invite-dialog="sendInviteDialog"
:invite-dialog="inviteDialog"
:upload-image="uploadImage"
@closeInviteDialog="closeInviteDialog" />
</safe-dialog>
</template>
<script setup>
import { getCurrentInstance, inject, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance, 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 { instanceRequest, notificationRequest } from '../../../api';
import { parseLocation, userImage, userStatusClass } from '../../../shared/utils';
import { useFriendStore, useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
import SendInviteDialog from './SendInviteDialog.vue';
const { vipFriends, onlineFriends, activeFriends } = storeToRefs(useFriendStore());
const { refreshInviteMessageTableData } = useInviteStore();
const { currentUser } = storeToRefs(useUserStore());
const { clearInviteImageUpload } = useGalleryStore();
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 clearInviteImageUpload = inject('clearInviteImageUpload');
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: ''
}
});
@@ -229,15 +207,15 @@
userId,
messageSlot: {}
};
inviteMessagesRequest.refreshInviteMessageTableData('message');
refreshInviteMessageTableData('message');
clearInviteImageUpload();
sendInviteDialogVisible.value = true;
}
function addSelfToInvite() {
const D = props.inviteDialog;
if (!D.userIds.includes(API.currentUser.id)) {
D.userIds.push(API.currentUser.id);
if (!D.userIds.includes(currentUser.value.id)) {
D.userIds.push(currentUser.value.id);
}
}
@@ -273,7 +251,7 @@
const inviteLoop = () => {
if (D.userIds.length > 0) {
const receiverUserId = D.userIds.shift();
if (receiverUserId === API.currentUser.id) {
if (receiverUserId === currentUser.value.id) {
// can't invite self!?
const L = parseLocation(D.worldId);
instanceRequest

View File

@@ -22,18 +22,21 @@
</template>
<script setup>
import { getCurrentInstance, inject } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { instanceRequest, notificationRequest } from '../../../api';
import { parseLocation } from '../../../composables/instance/utils';
import { parseLocation } from '../../../shared/utils';
import { useGalleryStore, useUserStore } from '../../../stores';
const { t } = useI18n();
const instance = getCurrentInstance();
const $message = instance.proxy.$message;
const API = inject('API');
const clearInviteImageUpload = inject('clearInviteImageUpload');
const { uploadImage } = storeToRefs(useGalleryStore());
const { clearInviteImageUpload } = useGalleryStore();
const { currentUser } = storeToRefs(useUserStore());
const props = defineProps({
visible: {
@@ -48,9 +51,6 @@
type: Object,
required: false,
default: () => ({})
},
uploadImage: {
type: String
}
});
@@ -69,7 +69,7 @@
const inviteLoop = () => {
if (J.userIds.length > 0) {
const receiverUserId = J.userIds.shift();
if (receiverUserId === API.currentUser.id) {
if (receiverUserId === currentUser.value.id) {
// can't invite self!?
const L = parseLocation(J.worldId);
instanceRequest
@@ -78,7 +78,7 @@
worldId: L.worldId
})
.finally(inviteLoop);
} else if (props.uploadImage) {
} else if (uploadImage.value) {
notificationRequest
.sendInvitePhoto(
{
@@ -115,7 +115,7 @@
inviteLoop();
} else if (messageType === 'invite') {
D.params.messageSlot = slot;
if (props.uploadImage) {
if (uploadImage.value) {
notificationRequest
.sendInvitePhoto(D.params, D.userId)
.catch((err) => {
@@ -144,7 +144,7 @@
}
} else if (messageType === 'request') {
D.params.requestSlot = slot;
if (props.uploadImage) {
if (uploadImage.value) {
notificationRequest
.sendRequestInvitePhoto(D.params, D.userId)
.catch((err) => {

View File

@@ -6,7 +6,7 @@
width="800px"
append-to-body
@close="cancelSendInvite">
<template v-if="API.currentUser.$isVRCPlus">
<template v-if="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">-->
@@ -70,7 +70,7 @@
<el-button type="small" @click="cancelSendInvite">
{{ t('dialog.invite_message.cancel') }}
</el-button>
<el-button type="small" @click="API.refreshInviteMessageTableData('message')">
<el-button type="small" @click="refreshInviteMessageTableData('message')">
{{ t('dialog.invite_message.refresh') }}
</el-button>
</template>
@@ -78,37 +78,35 @@
: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 { storeToRefs } from 'pinia';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
import EditAndSendInviteDialog from './EditAndSendInviteDialog.vue';
import SendInviteConfirmDialog from './SendInviteConfirmDialog.vue';
const { t } = useI18n();
const API = inject('API');
const inviteImageUpload = inject('inviteImageUpload');
const { refreshInviteMessageTableData } = useInviteStore();
const { inviteMessageTable } = storeToRefs(useInviteStore());
const { inviteImageUpload } = useGalleryStore();
const { currentUser } = storeToRefs(useUserStore());
const props = defineProps({
sendInviteDialogVisible: {
type: Boolean,
default: false
},
inviteMessageTable: {
type: Object,
default: () => ({})
},
sendInviteDialog: {
type: Object,
required: true
@@ -117,10 +115,6 @@
type: Object,
required: false,
default: () => ({})
},
uploadImage: {
type: String,
default: ''
}
});

View File

@@ -1,6 +1,6 @@
<template>
<safe-dialog
ref="inviteGroupDialog"
ref="inviteGroupDialogRef"
:visible.sync="inviteGroupDialog.visible"
:title="$t('dialog.invite_to_group.header')"
width="450px"
@@ -17,11 +17,11 @@
style="margin-top: 15px"
@change="isAllowedToInviteToGroup">
<el-option-group
v-if="API.currentUserGroups.size"
v-if="currentUserGroups.size"
:label="$t('dialog.invite_to_group.groups')"
style="width: 410px">
<el-option
v-for="group in API.currentUserGroups.values()"
v-for="group in currentUserGroups.values()"
:key="group.id"
:label="group.name"
:value="group.id"
@@ -166,132 +166,111 @@
</safe-dialog>
</template>
<script>
<script setup>
import { ref, watch, getCurrentInstance, nextTick } from 'vue';
import { storeToRefs } from 'pinia';
import { groupRequest, userRequest } from '../../api';
import { hasGroupPermission } from '../../composables/group/utils';
import { adjustDialogZ, hasGroupPermission, userImage, userStatusClass } from '../../shared/utils';
import { useFriendStore, useGroupStore } from '../../stores';
export default {
name: 'InviteGroupDialog',
inject: ['API', 'userStatusClass', 'userImage', 'adjustDialogZ'],
props: {
dialogData: {
type: Object,
required: true
},
vipFriends: {
type: Array,
required: true
},
onlineFriends: {
type: Array,
required: true
},
activeFriends: {
type: Array,
required: true
},
offlineFriends: {
type: Array,
required: true
}
},
computed: {
inviteGroupDialog: {
get() {
return this.dialogData;
},
set(value) {
this.$emit('update:dialog-data', value);
}
}
},
watch: {
'dialogData.visible'(value) {
if (value) {
this.initDialog();
}
}
},
methods: {
initDialog() {
this.$nextTick(() => this.adjustDialogZ(this.$refs.inviteGroupDialog.$el));
const D = this.inviteGroupDialog;
if (D.groupId) {
this.API.getCachedGroup({
groupId: D.groupId
})
.then((args) => {
D.groupName = args.ref.name;
})
.catch(() => {
D.groupId = '';
});
this.isAllowedToInviteToGroup();
}
const { vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
const { currentUserGroups, inviteGroupDialog } = storeToRefs(useGroupStore());
const { applyGroup } = useGroupStore();
if (D.userId) {
userRequest.getCachedUser({ userId: D.userId }).then((args) => {
D.userObject = args.ref;
D.userIds = [D.userId];
});
}
},
isAllowedToInviteToGroup() {
const D = this.inviteGroupDialog;
const groupId = D.groupId;
if (!groupId) {
return;
}
this.inviteGroupDialog.loading = true;
groupRequest
.getGroup({ groupId })
.then((args) => {
if (hasGroupPermission(args.ref, 'group-invites-manage')) {
return args;
}
// not allowed to invite
this.inviteGroupDialog.groupId = '';
this.$message({
type: 'error',
message: 'You are not allowed to invite to this group'
});
return args;
})
.finally(() => {
this.inviteGroupDialog.loading = false;
});
},
sendGroupInvite() {
this.$confirm('Continue? Invite User(s) To Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
const D = this.inviteGroupDialog;
if (action !== 'confirm' || D.loading === true) {
return;
}
D.loading = true;
const inviteLoop = () => {
if (D.userIds.length === 0) {
D.loading = false;
return;
}
const receiverUserId = D.userIds.shift();
groupRequest
.sendGroupInvite({
groupId: D.groupId,
userId: receiverUserId
})
.then(inviteLoop)
.catch(() => {
D.loading = false;
});
};
inviteLoop();
}
});
const { proxy } = getCurrentInstance();
watch(
() => {
return inviteGroupDialog.value.visible;
},
(value) => {
if (value) {
initDialog();
}
}
};
);
const inviteGroupDialogRef = ref(null);
function initDialog() {
nextTick(() => adjustDialogZ(inviteGroupDialogRef.value.$el));
const D = inviteGroupDialog.value;
if (D.groupId) {
groupRequest
.getCachedGroup({
groupId: D.groupId
})
.then((args) => {
D.groupName = args.ref.name;
})
.catch(() => {
D.groupId = '';
});
isAllowedToInviteToGroup();
}
if (D.userId) {
userRequest.getCachedUser({ userId: D.userId }).then((args) => {
D.userObject = args.ref;
D.userIds = [D.userId];
});
}
}
function isAllowedToInviteToGroup() {
const D = inviteGroupDialog.value;
const groupId = D.groupId;
if (!groupId) {
return;
}
inviteGroupDialog.value.loading = true;
groupRequest
.getGroup({ groupId })
.then((args) => {
const ref = applyGroup(args.json);
if (hasGroupPermission(ref, 'group-invites-manage')) {
return args;
}
// not allowed to invite
inviteGroupDialog.value.groupId = '';
proxy.$message({
type: 'error',
message: 'You are not allowed to invite to this group'
});
return args;
})
.finally(() => {
inviteGroupDialog.value.loading = false;
});
}
function sendGroupInvite() {
proxy.$confirm('Continue? Invite User(s) To Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
const D = inviteGroupDialog.value;
if (action !== 'confirm' || D.loading === true) {
return;
}
D.loading = true;
const inviteLoop = () => {
if (D.userIds.length === 0) {
D.loading = false;
return;
}
const receiverUserId = D.userIds.shift();
groupRequest
.sendGroupInvite({
groupId: D.groupId,
userId: receiverUserId
})
.then(inviteLoop)
.catch(() => {
D.loading = false;
});
};
inviteLoop();
}
});
}
</script>

View File

@@ -1,13 +1,13 @@
<template>
<safe-dialog ref="launchDialog" :visible.sync="isVisible" :title="$t('dialog.launch.header')" width="450px">
<safe-dialog ref="launchDialogRef" :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-form-item :label="t('dialog.launch.url')">
<el-input
v-model="launchDialog.url"
size="mini"
style="width: 260px"
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()" />
<el-tooltip placement="right" :content="$t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
<el-button
size="mini"
icon="el-icon-s-order"
@@ -18,11 +18,8 @@
</el-form-item>
<el-form-item v-if="launchDialog.shortUrl">
<template slot="label">
<span>{{ $t('dialog.launch.short_url') }}</span>
<el-tooltip
placement="top"
style="margin-left: 5px"
:content="$t('dialog.launch.short_url_notice')">
<span>{{ t('dialog.launch.short_url') }}</span>
<el-tooltip placement="top" style="margin-left: 5px" :content="t('dialog.launch.short_url_notice')">
<i class="el-icon-warning" />
</el-tooltip>
</template>
@@ -31,7 +28,7 @@
size="mini"
style="width: 260px"
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()" />
<el-tooltip placement="right" :content="$t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
<el-button
size="mini"
icon="el-icon-s-order"
@@ -40,13 +37,13 @@
@click="copyInstanceMessage(launchDialog.shortUrl)" />
</el-tooltip>
</el-form-item>
<el-form-item :label="$t('dialog.launch.location')">
<el-form-item :label="t('dialog.launch.location')">
<el-input
v-model="launchDialog.location"
size="mini"
style="width: 260px"
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()" />
<el-tooltip placement="right" :content="$t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
<el-button
size="mini"
icon="el-icon-s-order"
@@ -57,226 +54,196 @@
</el-form-item>
</el-form>
<el-checkbox v-model="launchDialog.desktop" style="float: left; margin-top: 5px" @change="saveLaunchDialog">
{{ $t('dialog.launch.start_as_desktop') }}
{{ t('dialog.launch.start_as_desktop') }}
</el-checkbox>
<template slot="footer">
<el-button size="small" @click="showPreviousInstancesInfoDialog(launchDialog.location)">
{{ $t('dialog.launch.info') }}
{{ t('dialog.launch.info') }}
</el-button>
<el-button
size="small"
:disabled="!checkCanInvite(launchDialog.location)"
@click="showInviteDialog(launchDialog.location)">
{{ $t('dialog.launch.invite') }}
{{ t('dialog.launch.invite') }}
</el-button>
<el-button
type="primary"
size="small"
:disabled="!launchDialog.secureOrShortName"
@click="launchGame(launchDialog.location, launchDialog.shortName, launchDialog.desktop)">
{{ $t('dialog.launch.launch') }}
@click="handleLaunchGame(launchDialog.location, launchDialog.shortName, launchDialog.desktop)">
{{ t('dialog.launch.launch') }}
</el-button>
</template>
<InviteDialog
:invite-dialog="inviteDialog"
:vip-friends="vipFriends"
:online-friends="onlineFriends"
:active-friends="activeFriends"
:invite-message-table="inviteMessageTable"
:upload-image="uploadImage"
@closeInviteDialog="closeInviteDialog" />
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
</safe-dialog>
</template>
<script>
<script setup>
import { ref, computed, nextTick, watch, getCurrentInstance, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n-bridge';
import { instanceRequest, worldRequest } from '../../api';
import { isRealInstance, parseLocation } from '../../composables/instance/utils';
import { getLaunchURL } from '../../composables/shared/utils';
import configRepository from '../../service/config';
import { adjustDialogZ, checkCanInvite, getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils';
import {
useAppearanceSettingsStore,
useFriendStore,
useInstanceStore,
useLaunchStore,
useLocationStore
} from '../../stores';
import InviteDialog from './InviteDialog/InviteDialog.vue';
export default {
name: 'LaunchDialog',
components: { InviteDialog },
inject: ['friends', '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() {
return {
launchDialog: {
loading: false,
desktop: false,
tag: '',
location: '',
url: '',
shortName: '',
shortUrl: '',
secureOrShortName: ''
},
inviteDialog: {
visible: false,
loading: false,
worldId: '',
worldName: '',
userIds: [],
friendsInInstance: []
}
};
},
computed: {
isVisible: {
get() {
return this.launchDialogData.visible;
},
set(value) {
this.$emit('update:launch-dialog-data', { ...this.launchDialogData, visible: value });
}
}
},
watch: {
'launchDialogData.loading': {
handler() {
this.getConfig();
this.initLaunchDialog();
}
}
},
const { proxy } = getCurrentInstance();
const { t } = useI18n();
created() {
this.getConfig();
const { friends } = storeToRefs(useFriendStore());
const { hideTooltips } = storeToRefs(useAppearanceSettingsStore());
const { lastLocation } = storeToRefs(useLocationStore());
const { launchGame } = useLaunchStore();
const { launchDialogData } = storeToRefs(useLaunchStore());
const { showPreviousInstancesInfoDialog } = useInstanceStore();
const launchDialogRef = ref(null);
const launchDialog = ref({
loading: false,
desktop: false,
tag: '',
location: '',
url: '',
shortName: '',
shortUrl: '',
secureOrShortName: ''
});
const inviteDialog = ref({
visible: false,
loading: false,
worldId: '',
worldName: '',
userIds: [],
friendsInInstance: []
});
const isVisible = computed({
get() {
return launchDialogData.value.visible;
},
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('launchGame', location, shortName, desktop);
this.isVisible = false;
},
getConfig() {
configRepository.getBool('launchAsDesktop').then((value) => (this.launchDialog.desktop = value));
},
saveLaunchDialog() {
configRepository.setBool('launchAsDesktop', this.launchDialog.desktop);
},
async initLaunchDialog() {
const { tag, shortName } = this.launchDialogData;
if (!isRealInstance(tag)) {
return;
}
this.$nextTick(() => this.adjustDialogZ(this.$refs.launchDialog.$el));
const D = this.launchDialog;
D.tag = tag;
D.secureOrShortName = shortName;
D.shortUrl = '';
D.shortName = shortName;
const L = parseLocation(tag);
L.shortName = shortName;
if (shortName) {
D.shortUrl = `https://vrch.at/${shortName}`;
}
if (L.instanceId) {
D.location = `${L.worldId}:${L.instanceId}`;
} else {
D.location = L.worldId;
}
D.url = getLaunchURL(L);
if (!shortName) {
const res = await instanceRequest.getInstanceShortName({
worldId: L.worldId,
instanceId: L.instanceId
});
// NOTE:
// splitting the 'INSTANCE:SHORTNAME' event and put code here
if (!res.json) {
return;
}
const resLocation = `${res.instance.worldId}:${res.instance.instanceId}`;
if (resLocation === this.launchDialog.tag) {
const resShortName = res.json.shortName;
const secureOrShortName = res.json.shortName || res.json.secureName;
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 = getLaunchURL(parsedL);
}
}
},
async copyInstanceMessage(input) {
try {
await navigator.clipboard.writeText(input);
this.$message({
message: 'Instance copied to clipboard',
type: 'success'
});
} catch (error) {
this.$message({
message: 'Instance copied failed',
type: 'error'
});
console.error(error.message);
}
set(value) {
launchDialogData.value.visible = value;
}
});
watch(
() => launchDialogData.value.loading,
(loading) => {
if (loading) {
getConfig();
initLaunchDialog();
}
}
};
);
getConfig();
function closeInviteDialog() {
inviteDialog.value.visible = false;
}
function showInviteDialog(tag) {
if (!isRealInstance(tag)) {
return;
}
const L = parseLocation(tag);
worldRequest
.getCachedWorld({
worldId: L.worldId
})
.then((args) => {
const D = inviteDialog.value;
D.userIds = [];
D.worldId = L.tag;
D.worldName = args.ref.name;
D.friendsInInstance = [];
const friendsInCurrentInstance = lastLocation.value.friendList;
for (const friend of friendsInCurrentInstance.values()) {
const ctx = friends.value.get(friend.userId);
if (typeof ctx.ref === 'undefined') {
continue;
}
D.friendsInInstance.push(ctx);
}
D.visible = true;
});
}
function handleLaunchGame(location, shortName, desktop) {
launchGame(location, shortName, desktop);
isVisible.value = false;
}
function getConfig() {
configRepository.getBool('launchAsDesktop').then((value) => (launchDialog.value.desktop = value));
}
function saveLaunchDialog() {
configRepository.setBool('launchAsDesktop', launchDialog.value.desktop);
}
async function initLaunchDialog() {
const { tag, shortName } = launchDialogData.value;
if (!isRealInstance(tag)) {
return;
}
nextTick(() => adjustDialogZ(launchDialogRef.value.$el));
const D = launchDialog.value;
D.tag = tag;
D.secureOrShortName = shortName;
D.shortUrl = '';
D.shortName = shortName;
const L = parseLocation(tag);
L.shortName = shortName;
if (shortName) {
D.shortUrl = `https://vrch.at/${shortName}`;
}
if (L.instanceId) {
D.location = `${L.worldId}:${L.instanceId}`;
} else {
D.location = L.worldId;
}
D.url = getLaunchURL(L);
if (!shortName) {
const res = await instanceRequest.getInstanceShortName({
worldId: L.worldId,
instanceId: L.instanceId
});
if (!res.json) {
return;
}
const resLocation = `${res.instance.worldId}:${res.instance.instanceId}`;
if (resLocation === launchDialog.value.tag) {
const resShortName = res.json.shortName;
const secureOrShortName = res.json.shortName || res.json.secureName;
const parsedL = parseLocation(resLocation);
parsedL.shortName = resShortName;
launchDialog.value.shortName = resShortName;
launchDialog.value.secureOrShortName = secureOrShortName;
if (resShortName) {
launchDialog.value.shortUrl = `https://vrch.at/${resShortName}`;
}
launchDialog.value.url = getLaunchURL(parsedL);
}
}
}
async function copyInstanceMessage(input) {
try {
await navigator.clipboard.writeText(input);
proxy.$message({
message: 'Instance copied to clipboard',
type: 'success'
});
} catch (error) {
proxy.$message({
message: 'Instance copied failed',
type: 'error'
});
console.error(error.message);
}
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -26,26 +26,16 @@
</template>
<script setup>
import { inject } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n-bridge';
import { useGalleryStore } from '../../stores';
const { t } = useI18n();
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
defineProps({
previousImagesDialogVisible: {
type: Boolean,
required: true
},
previousImagesTable: {
type: Array,
required: true
}
});
const emit = defineEmits(['update:previousImagesDialogVisible']);
const { previousImagesDialogVisible, previousImagesTable } = storeToRefs(useGalleryStore());
const { showFullscreenImageDialog } = useGalleryStore();
function closeDialog() {
emit('update:previousImagesDialogVisible', false);
previousImagesDialogVisible.value = false;
}
</script>

View File

@@ -1,14 +1,14 @@
<template>
<safe-dialog
ref="dialog"
:visible="visible"
ref="dialogRef"
:visible="previousInstancesInfoDialogVisible"
:title="$t('dialog.previous_instances.info')"
width="800px"
:fullscreen="fullscreen"
destroy-on-close
@close="$emit('update:visible', false)">
@close="closeDialog">
<div style="display: flex; align-items: center; justify-content: space-between">
<location :location="location.tag" style="font-size: 14px"></location>
<Location :location="location.tag" style="font-size: 14px" />
<el-input
v-model="dataTable.filters[0].value"
:placeholder="$t('dialog.previous_instances.search_placeholder')"
@@ -20,9 +20,9 @@
<template slot-scope="scope">
<el-tooltip placement="left">
<template slot="content">
<span>{{ scope.row.created_at | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
</template>
<span>{{ scope.row.created_at | formatDate('short') }}</span>
<span>{{ formatDateFilter(scope.row.created_at, 'short') }}</span>
</el-tooltip>
</template>
</el-table-column>
@@ -57,105 +57,85 @@
</safe-dialog>
</template>
<script>
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';
<script setup>
import { ref, watch, nextTick } from 'vue';
import { storeToRefs } from 'pinia';
import { database } from '../../../service/database';
import {
adjustDialogZ,
compareByCreatedAt,
parseLocation,
timeToText,
formatDateFilter
} from '../../../shared/utils';
import { useGameLogStore, useInstanceStore, useUserStore } from '../../../stores';
export default {
name: 'PreviousInstancesInfoDialog',
components: {
Location
},
inject: ['adjustDialogZ'],
props: {
visible: {
type: Boolean,
default: false
},
instanceId: { type: String, required: true },
gameLogIsFriend: { type: Function, required: true },
gameLogIsFavorite: { type: Function, required: true },
lookupUser: { type: Function, required: true },
isDarkMode: { type: Boolean, required: true }
},
data() {
return {
echarts: null,
echartsInstance: null,
loading: false,
location: {},
currentTab: 'table',
dataTable: {
data: [],
filters: [
{
prop: 'displayName',
value: ''
}
],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: {
prop: 'created_at',
order: 'descending'
}
},
pageSize: 10,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 25, 50, 100]
}
},
fullscreen: false
};
},
computed: {
activityDetailData() {
return this.dataTable.data.map((item) => ({
displayName: item.displayName,
joinTime: dayjs(item.created_at),
leaveTime: dayjs(item.created_at).add(item.time, 'ms'),
time: item.time,
timer: item.timer
}));
const { lookupUser } = useUserStore();
const { previousInstancesInfoDialogVisible, previousInstancesInfoDialogInstanceId } =
storeToRefs(useInstanceStore());
const { gameLogIsFriend, gameLogIsFavorite } = useGameLogStore();
const dialogRef = ref(null);
const loading = ref(false);
const location = ref({});
const dataTable = ref({
data: [],
filters: [
{
prop: 'displayName',
value: ''
}
],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: {
prop: 'created_at',
order: 'descending'
}
},
watch: {
visible(value) {
if (value) {
this.$nextTick(() => {
this.init();
this.refreshPreviousInstancesInfoTable();
});
utils.loadEcharts().then((echarts) => {
this.echarts = echarts;
});
}
}
},
methods: {
init() {
this.adjustDialogZ(this.$refs.dialog.$el);
this.loading = true;
this.location = parseLocation(this.instanceId);
},
refreshPreviousInstancesInfoTable() {
database.getPlayersFromInstance(this.location.tag).then((data) => {
const array = [];
for (const entry of Array.from(data.values())) {
entry.timer = utils.timeToText(entry.time);
array.push(entry);
}
array.sort(utils.compareByCreatedAt);
this.dataTable.data = array;
this.loading = false;
pageSize: 10,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 25, 50, 100]
}
});
const fullscreen = ref(false);
watch(
() => previousInstancesInfoDialogVisible.value,
(value) => {
if (value) {
nextTick(() => {
init();
refreshPreviousInstancesInfoTable();
});
}
}
};
);
function init() {
adjustDialogZ(dialogRef.value.$el);
loading.value = true;
location.value = parseLocation(previousInstancesInfoDialogInstanceId.value);
}
function refreshPreviousInstancesInfoTable() {
database.getPlayersFromInstance(location.value.tag).then((data) => {
const array = [];
for (const entry of Array.from(data.values())) {
entry.timer = timeToText(entry.time);
array.push(entry);
}
array.sort(compareByCreatedAt);
dataTable.value.data = array;
loading.value = false;
});
}
function closeDialog() {
previousInstancesInfoDialogVisible.value = false;
}
</script>

View File

@@ -2,45 +2,44 @@
<safe-dialog
ref="previousInstancesWorldDialog"
:visible.sync="isVisible"
:title="$t('dialog.previous_instances.header')"
:title="t('dialog.previous_instances.header')"
width="1000px"
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
v-model="previousInstancesWorldDialogTable.filters[0].value"
:placeholder="$t('dialog.previous_instances.search_placeholder')"
:placeholder="t('dialog.previous_instances.search_placeholder')"
style="display: block; width: 150px"></el-input>
</div>
<data-tables v-loading="loading" v-bind="previousInstancesWorldDialogTable" style="margin-top: 10px">
<el-table-column :label="$t('table.previous_instances.date')" prop="created_at" sortable width="170">
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
<template slot-scope="scope">
<span>{{ scope.row.created_at | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.instance_name')" prop="name">
<el-table-column :label="t('table.previous_instances.instance_name')" prop="name">
<template slot-scope="scope">
<location-world
<LocationWorld
:locationobject="scope.row.$location"
:grouphint="scope.row.groupName"
:currentuserid="API.currentUser.id"
@show-launch-dialog="showLaunchDialog"></location-world>
:currentuserid="currentUser.id" />
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.instance_creator')" prop="location">
<el-table-column :label="t('table.previous_instances.instance_creator')" prop="location">
<template slot-scope="scope">
<display-name
<DisplayName
:userid="scope.row.$location.userId"
:location="scope.row.$location.tag"
:force-update-key="previousInstancesWorldDialog.forceUpdate"></display-name>
:force-update-key="previousInstancesWorldDialog.forceUpdate" />
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.time')" prop="time" width="100" sortable>
<el-table-column :label="t('table.previous_instances.time')" prop="time" width="100" sortable>
<template slot-scope="scope">
<span v-text="scope.row.timer"></span>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.action')" width="90" align="right">
<el-table-column :label="t('table.previous_instances.action')" width="90" align="right">
<template slot-scope="scope">
<el-button
type="text"
@@ -66,111 +65,106 @@
</safe-dialog>
</template>
<script>
import utils from '../../../classes/utils';
import { parseLocation } from '../../../composables/instance/utils';
import database from '../../../service/database';
<script setup>
import { storeToRefs } from 'pinia';
import { computed, getCurrentInstance, nextTick, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { database } from '../../../service/database';
import {
adjustDialogZ,
compareByCreatedAt,
parseLocation,
removeFromArray,
timeToText,
formatDateFilter
} from '../../../shared/utils';
import { useInstanceStore, useUiStore, useUserStore } from '../../../stores';
export default {
name: 'PreviousInstancesWorldDialog',
inject: ['API', 'showLaunchDialog', 'showPreviousInstancesInfoDialog', 'adjustDialogZ'],
props: {
previousInstancesWorldDialog: {
type: Object,
required: true
},
shiftHeld: Boolean
const { proxy } = getCurrentInstance();
const { t } = useI18n();
const props = defineProps({
previousInstancesWorldDialog: {
type: Object,
required: true
}
});
const emit = defineEmits(['update:previous-instances-world-dialog']);
const { showPreviousInstancesInfoDialog } = useInstanceStore();
const { shiftHeld } = storeToRefs(useUiStore());
const { currentUser } = storeToRefs(useUserStore());
const previousInstancesWorldDialogTable = reactive({
data: [],
filters: [{ prop: 'groupName', value: '' }],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: { prop: 'created_at', order: 'descending' }
},
data() {
return {
previousInstancesWorldDialogTable: {
data: [],
filters: [
{
prop: 'groupName',
value: ''
}
],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: {
prop: 'created_at',
order: 'descending'
}
},
pageSize: 10,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 25, 50, 100]
}
},
loading: false
};
},
computed: {
isVisible: {
get() {
return this.previousInstancesWorldDialog.visible;
},
set(value) {
this.$emit('update:previous-instances-world-dialog', {
...this.previousInstancesWorldDialog,
visible: value
});
pageSize: 10,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 25, 50, 100]
}
});
const loading = ref(false);
const isVisible = computed({
get: () => props.previousInstancesWorldDialog.visible,
set: (value) => {
emit('update:previous-instances-world-dialog', {
...props.previousInstancesWorldDialog,
visible: value
});
}
});
function refreshPreviousInstancesWorldTable() {
loading.value = true;
const D = props.previousInstancesWorldDialog;
database.getPreviousInstancesByWorldId(D.worldRef).then((data) => {
const array = [];
for (const ref of data.values()) {
ref.$location = parseLocation(ref.location);
ref.timer = ref.time > 0 ? timeToText(ref.time) : '';
array.push(ref);
}
array.sort(compareByCreatedAt);
previousInstancesWorldDialogTable.data = array;
loading.value = false;
});
}
function deleteGameLogWorldInstance(row) {
database.deleteGameLogInstanceByInstanceId({ location: row.location });
removeFromArray(previousInstancesWorldDialogTable.data, row);
}
function deleteGameLogWorldInstancePrompt(row) {
proxy.$confirm('Continue? Delete GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
deleteGameLogWorldInstance(row);
}
}
},
watch: {
'previousInstancesWorldDialog.openFlg'() {
if (this.previousInstancesWorldDialog.visible) {
this.$nextTick(() => {
this.adjustDialogZ(this.$refs.previousInstancesWorldDialog.$el);
});
this.refreshPreviousInstancesWorldTable();
}
}
},
methods: {
refreshPreviousInstancesWorldTable() {
this.loading = true;
const D = this.previousInstancesWorldDialog;
database.getpreviousInstancesByWorldId(D.worldRef).then((data) => {
const array = [];
for (const ref of data.values()) {
ref.$location = parseLocation(ref.location);
if (ref.time > 0) {
ref.timer = utils.timeToText(ref.time);
} else {
ref.timer = '';
}
array.push(ref);
}
array.sort(utils.compareByCreatedAt);
this.previousInstancesWorldDialogTable.data = array;
this.loading = false;
});
},
deleteGameLogWorldInstance(row) {
database.deleteGameLogInstanceByInstanceId({
location: row.location
});
utils.removeFromArray(this.previousInstancesWorldDialogTable.data, row);
},
});
}
deleteGameLogWorldInstancePrompt(row) {
this.$confirm('Continue? Delete GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
this.deleteGameLogWorldInstance(row);
}
}
watch(
() => props.previousInstancesWorldDialog.openFlg,
() => {
if (props.previousInstancesWorldDialog.visible) {
nextTick(() => {
adjustDialogZ(proxy.$refs.previousInstancesWorldDialog.$el);
});
refreshPreviousInstancesWorldTable();
}
}
};
);
</script>

View File

@@ -20,7 +20,6 @@
<el-input
v-for="(link, index) in bioDialog.bioLinks"
:key="index"
v-model="bioDialog.bioLinks[index]"
:value="link"
size="small"
style="margin-top: 5px">
@@ -52,7 +51,7 @@
import { getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { userRequest } from '../../../api';
import { getFaviconUrl } from '../../../composables/shared/utils';
import { getFaviconUrl } from '../../../shared/utils';
const { t } = useI18n();
const { $message } = getCurrentInstance().proxy;

View File

@@ -6,7 +6,7 @@
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">
<div v-for="item in currentUser.$languages" :key="item.key" style="margin: 6px 0">
<el-tag
size="small"
type="info"
@@ -23,9 +23,7 @@
</div>
<el-select
value=""
:disabled="
languageDialog.loading || (API.currentUser.$languages && API.currentUser.$languages.length === 3)
"
:disabled="languageDialog.loading || (currentUser.$languages && currentUser.$languages.length === 3)"
:placeholder="t('dialog.language.select_language')"
style="margin-top: 14px"
@change="addUserLanguage">
@@ -46,29 +44,21 @@
</template>
<script setup>
import { inject } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n-bridge';
import { userRequest } from '../../../api';
import { languageClass } from '../../../composables/user/utils';
import { languageClass } from '../../../shared/utils';
import { useUserStore } from '../../../stores';
const { t } = useI18n();
const API = inject('API');
const props = defineProps({
languageDialog: {
type: Object,
required: true
}
});
const { languageDialog, currentUser } = storeToRefs(useUserStore());
function removeUserLanguage(language) {
if (language !== String(language)) {
return;
}
const D = props.languageDialog;
const D = languageDialog.value;
D.loading = true;
userRequest
.removeUserTags({
@@ -83,7 +73,7 @@
if (language !== String(language)) {
return;
}
const D = props.languageDialog;
const D = languageDialog.value;
D.loading = true;
userRequest
.addUserTags({

View File

@@ -15,22 +15,20 @@
<data-tables v-loading="loading" v-bind="previousInstancesUserDialogTable" style="margin-top: 10px">
<el-table-column :label="$t('table.previous_instances.date')" prop="created_at" sortable width="170">
<template slot-scope="scope">
<span>{{ scope.row.created_at | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.world')" prop="name" sortable>
<template slot-scope="scope">
<location
<Location
:location="scope.row.location"
:hint="scope.row.worldName"
:grouphint="scope.row.groupName"></location>
:grouphint="scope.row.groupName" />
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.instance_creator')" prop="location" width="170">
<template slot-scope="scope">
<display-name
:userid="scope.row.$location.userId"
:location="scope.row.$location.tag"></display-name>
<DisplayName :userid="scope.row.$location.userId" :location="scope.row.$location.tag" />
</template>
</el-table-column>
<el-table-column :label="$t('table.previous_instances.time')" prop="time" width="100" sortable>
@@ -69,138 +67,115 @@
</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';
<script setup>
import { storeToRefs } from 'pinia';
import { computed, getCurrentInstance, nextTick, reactive, ref, watch } from 'vue';
import { database } from '../../../service/database';
import {
adjustDialogZ,
compareByCreatedAt,
parseLocation,
removeFromArray,
timeToText,
formatDateFilter
} from '../../../shared/utils';
import { useInstanceStore, useLaunchStore, useUiStore } from '../../../stores';
export default {
name: 'PreviousInstancesUserDialog',
components: {
Location
},
inject: ['adjustDialogZ', 'showLaunchDialog', 'showPreviousInstancesInfoDialog'],
props: {
previousInstancesUserDialog: {
type: Object,
default: () => ({
visible: false,
userRef: {},
loading: false,
forceUpdate: 0,
previousInstances: [],
previousInstancesTable: {
data: [],
filters: [
{
prop: 'displayName',
value: ''
}
],
tableProps: {
stripe: true,
size: 'mini',
height: '400px'
}
}
})
},
shiftHeld: {
type: Boolean,
default: false
}
},
data() {
return {
previousInstancesUserDialogTable: {
const props = defineProps({
previousInstancesUserDialog: {
type: Object,
default: () => ({
visible: false,
userRef: {},
loading: false,
forceUpdate: 0,
previousInstances: [],
previousInstancesTable: {
data: [],
filters: [
{
prop: 'worldName',
value: ''
}
],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: {
prop: 'created_at',
order: 'descending'
}
},
pageSize: 10,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 25, 50, 100]
}
},
loading: false
};
},
computed: {
isVisible: {
get() {
return this.previousInstancesUserDialog.visible;
},
set(value) {
this.$emit('update:previous-instances-user-dialog', {
...this.previousInstancesUserDialog,
visible: value
});
filters: [{ prop: 'displayName', value: '' }],
tableProps: { stripe: true, size: 'mini', height: '400px' }
}
}
})
}
});
const emit = defineEmits(['update:previous-instances-user-dialog']);
const { proxy } = getCurrentInstance();
const loading = ref(false);
const previousInstancesUserDialogTable = reactive({
data: [],
filters: [{ prop: 'worldName', value: '' }],
tableProps: {
stripe: true,
size: 'mini',
defaultSort: { prop: 'created_at', order: 'descending' }
},
watch: {
'previousInstancesUserDialog.openFlg'() {
if (this.previousInstancesUserDialog.visible) {
this.$nextTick(() => {
this.adjustDialogZ(this.$refs.previousInstancesUserDialog.$el);
});
this.refreshPreviousInstancesUserTable();
}
}
},
methods: {
refreshPreviousInstancesUserTable() {
this.loading = true;
database.getpreviousInstancesByUserId(this.previousInstancesUserDialog.userRef).then((data) => {
const array = [];
for (const ref of data.values()) {
ref.$location = parseLocation(ref.location);
if (ref.time > 0) {
ref.timer = utils.timeToText(ref.time);
} else {
ref.timer = '';
}
array.push(ref);
}
array.sort(utils.compareByCreatedAt);
this.previousInstancesUserDialogTable.data = array;
this.loading = false;
});
},
deleteGameLogUserInstance(row) {
database.deleteGameLogInstance({
id: this.previousInstancesUserDialog.userRef.id,
displayName: this.previousInstancesUserDialog.userRef.displayName,
location: row.location
});
utils.removeFromArray(this.previousInstancesUserDialogTable.data, row);
},
deleteGameLogUserInstancePrompt(row) {
this.$confirm('Continue? Delete User From GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
this.deleteGameLogUserInstance(row);
}
}
pageSize: 10,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 25, 50, 100]
}
});
const { showLaunchDialog } = useLaunchStore();
const { showPreviousInstancesInfoDialog } = useInstanceStore();
const { shiftHeld } = storeToRefs(useUiStore());
const isVisible = computed({
get: () => props.previousInstancesUserDialog.visible,
set: (value) => {
emit('update:previous-instances-user-dialog', {
...props.previousInstancesUserDialog,
visible: value
});
}
});
const refreshPreviousInstancesUserTable = async () => {
loading.value = true;
const data = await database.getPreviousInstancesByUserId(props.previousInstancesUserDialog.userRef);
const array = [];
for (const item of data.values()) {
item.$location = parseLocation(item.location);
item.timer = item.time > 0 ? timeToText(item.time) : '';
array.push(item);
}
array.sort(compareByCreatedAt);
previousInstancesUserDialogTable.data = array;
loading.value = false;
};
watch(
() => props.previousInstancesUserDialog.openFlg,
() => {
if (props.previousInstancesUserDialog.visible) {
nextTick(() => {
adjustDialogZ(proxy.$refs.previousInstancesUserDialog.$el);
});
refreshPreviousInstancesUserTable();
}
}
};
);
function deleteGameLogUserInstance(row) {
database.deleteGameLogInstance({
id: props.previousInstancesUserDialog.userRef.id,
displayName: props.previousInstancesUserDialog.userRef.displayName,
location: row.location
});
removeFromArray(previousInstancesUserDialogTable.data, row);
}
function deleteGameLogUserInstancePrompt(row) {
proxy.$confirm('Continue? Delete User From GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') deleteGameLogUserInstance(row);
}
});
}
</script>

View File

@@ -6,7 +6,7 @@
width="800px"
append-to-body
@close="cancelSendInviteRequest">
<template v-if="API.currentUser.$isVRCPlus">
<template v-if="currentUser.$isVRCPlus">
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
</template>
@@ -45,7 +45,7 @@
<el-button type="small" @click="cancelSendInviteRequest">{{
t('dialog.invite_request_message.cancel')
}}</el-button>
<el-button type="small" @click="API.refreshInviteMessageTableData('request')">{{
<el-button type="small" @click="refreshInviteMessageTableData('request')">{{
t('dialog.invite_request_message.refresh')
}}</el-button>
</template>
@@ -53,37 +53,37 @@
: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 { storeToRefs } from 'pinia';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
import EditAndSendInviteDialog from '../InviteDialog/EditAndSendInviteDialog.vue';
import SendInviteConfirmDialog from '../InviteDialog/SendInviteConfirmDialog.vue';
const { t } = useI18n();
const API = inject('API');
const inviteImageUpload = inject('inviteImageUpload');
const inviteStore = useInviteStore();
const { refreshInviteMessageTableData } = inviteStore;
const { inviteRequestMessageTable } = storeToRefs(inviteStore);
const galleryStore = useGalleryStore();
const { inviteImageUpload } = galleryStore;
const { currentUser } = storeToRefs(useUserStore());
const props = defineProps({
sendInviteRequestDialogVisible: {
type: Boolean,
default: false
},
inviteRequestMessageTable: {
type: Object,
default: () => ({})
},
sendInviteDialog: {
type: Object,
default: () => ({})
@@ -92,10 +92,6 @@
type: Object,
require: false,
default: () => ({})
},
uploadImage: {
type: String,
default: ''
}
});
@@ -120,7 +116,6 @@
visible: true
};
}
function cancelSendInviteRequest() {
emit('update:sendInviteRequestDialogVisible', false);
}

View File

@@ -34,7 +34,7 @@
<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">
<el-option v-if="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>
@@ -44,6 +44,7 @@
:placeholder="t('dialog.social_status.status_placeholder')"
maxlength="32"
show-word-limit
clearable
style="display: block; margin-top: 10px"></el-input>
</div>
@@ -56,13 +57,16 @@
</template>
<script setup>
import { inject, getCurrentInstance } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { userRequest } from '../../../api';
import { useUserStore } from '../../../stores';
const { t } = useI18n();
const { $message } = getCurrentInstance().proxy;
const API = inject('API');
const { currentUser } = storeToRefs(useUserStore());
const props = defineProps({
socialStatusDialog: {

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
class="x-dialog"
:visible.sync="VRCXUpdateDialog.visible"
:title="t('dialog.vrcx_updater.header')"
append-to-body
width="400px">
<div v-loading="checkingForVRCXUpdate" style="margin-top: 15px">
<template v-if="updateInProgress">
@@ -17,11 +18,10 @@
<span>{{ t('dialog.vrcx_updater.ready_for_update') }}</span>
</div>
<el-select
v-model="currentBranch"
v-model="branch"
style="display: inline-block; width: 150px; margin-right: 15px"
@change="loadBranchVersions">
<el-option v-for="branch in branches" :key="branch.name" :label="branch.name" :value="branch.name">
</el-option>
<el-option v-for="b in branches" :key="b.name" :label="b.name" :value="b.name"> </el-option>
</el-select>
<el-select v-model="VRCXUpdateDialog.release" style="display: inline-block; width: 150px">
<el-option
@@ -63,72 +63,32 @@
</template>
<script setup>
import { ref, computed, inject, watch, nextTick } from 'vue';
import { storeToRefs } from 'pinia';
import { nextTick, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { branches } from '../../shared/constants';
import { adjustDialogZ } from '../../shared/utils';
import { useVRCXUpdaterStore } from '../../stores';
const VRCXUpdaterStore = useVRCXUpdaterStore();
const {
appVersion,
branch,
checkingForVRCXUpdate,
VRCXUpdateDialog,
pendingVRCXInstall,
updateInProgress,
updateProgress
} = storeToRefs(VRCXUpdaterStore);
const { installVRCXUpdate, loadBranchVersions, restartVRCX, updateProgressText, cancelUpdate } = VRCXUpdaterStore;
const { t } = useI18n();
const adjustDialogZ = inject('adjustDialogZ');
const props = defineProps({
// eslint-disable-next-line vue/prop-name-casing
VRCXUpdateDialog: {
type: Object,
required: true
},
appVersion: {
type: String,
required: true
},
checkingForVRCXUpdate: {
type: Boolean,
default: false
},
updateInProgress: {
type: Boolean,
default: false
},
updateProgress: {
type: Number,
default: 0
},
updateProgressText: {
type: Function,
default: () => ''
},
pendingVRCXInstall: {
type: String,
default: ''
},
branch: {
type: String,
default: ''
},
branches: {
type: Object,
default: () => {}
}
});
const VRCXUpdateDialogRef = ref(null);
const emit = defineEmits([
'loadBranchVersions',
'cancelUpdate',
'installVRCXUpdate',
'restartVRCX',
'update:branch'
]);
const currentBranch = computed({
get: () => props.branch,
set: (value) => {
emit('update:branch', value);
}
});
watch(
() => props.VRCXUpdateDialog,
() => VRCXUpdateDialog,
(newVal) => {
if (newVal.visible) {
nextTick(() => {
@@ -137,20 +97,4 @@
}
}
);
function loadBranchVersions(event) {
emit('loadBranchVersions', event);
}
function cancelUpdate() {
emit('cancelUpdate');
}
function installVRCXUpdate() {
emit('installVRCXUpdate');
}
function restartVRCX(isUpgrade) {
emit('restartVRCX', isUpgrade);
}
</script>

View File

@@ -25,12 +25,9 @@
<!-- 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 v-for="image in previousImagesTable" :key="image.version" style="display: inline-block">
<div
v-if="image.file"
class="x-change-image-item"
style="cursor: pointer"
:class="{ 'current-image': compareCurrentImage(image) }"
@@ -43,35 +40,31 @@
</template>
<script setup>
import { getCurrentInstance, inject, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance, ref } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { imageRequest } from '../../../api';
import { extractFileId } from '../../../composables/shared/utils';
import webApiService from '../../../service/webapi';
import { AppGlobal } from '../../../service/appConfig';
import { $throw } from '../../../service/request';
import { extractFileId } from '../../../shared/utils';
import { useGalleryStore, useWorldStore } from '../../../stores';
const { t } = useI18n();
const API = inject('API');
const instance = getCurrentInstance();
const $message = instance.proxy.$message;
const { worldDialog } = storeToRefs(useWorldStore());
const { previousImagesTable } = storeToRefs(useGalleryStore());
const props = defineProps({
changeWorldImageDialogVisible: {
type: Boolean,
default: false
},
previousImagesTable: {
type: Array,
default: () => []
},
previousImagesFileId: {
type: String,
default: ''
},
worldDialog: {
type: Object,
default: () => ({})
}
});
@@ -126,7 +119,7 @@
}
};
const files = e.target.files || e.dataTransfer.files;
if (!files.length || !props.worldDialog.visible || props.worldDialog.loading) {
if (!files.length || !worldDialog.value.visible || worldDialog.value.loading) {
clearFile();
return;
}
@@ -158,8 +151,8 @@
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 worldId = worldDialog.value.id;
const { imageUrl } = worldDialog.value.ref;
const fileId = extractFileId(imageUrl);
if (!fileId) {
$message({
@@ -204,7 +197,6 @@
}
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 = {
@@ -216,7 +208,6 @@
}
async function worldImageFileStart(args) {
// API.$on('WORLDIMAGE:FILESTART')
const { url } = args.json;
const { fileId, fileVersion } = args.params;
const params = {
@@ -239,9 +230,8 @@
});
if (json.status !== 200) {
// $app.worldDialog.loading = false;
changeWorldImageDialogLoading.value = false;
API.$throw('World image upload failed', json, params.url);
$throw('World image upload failed', json, params.url);
}
const args = {
json,
@@ -251,7 +241,6 @@
}
async function worldImageFileAWS(args) {
// API.$on('WORLDIMAGE:FILEAWS')
const { fileId, fileVersion } = args.params;
const params = {
fileId,
@@ -262,7 +251,6 @@
}
async function worldImageFileFinish(args) {
// API.$on('WORLDIMAGE:FILEFINISH')
const { fileId, fileVersion } = args.params;
const params = {
fileId,
@@ -273,7 +261,6 @@
}
async function worldImageSigStart(args) {
// API.$on('WORLDIMAGE:SIGSTART')
const { url } = args.json;
const { fileId, fileVersion } = args.params;
const params = {
@@ -296,9 +283,8 @@
});
if (json.status !== 200) {
// $app.worldDialog.loading = false;
changeWorldImageDialogLoading.value = false;
API.$throw('World image upload failed', json, params.url);
$throw('World image upload failed', json, params.url);
}
const args = {
json,
@@ -308,7 +294,6 @@
}
async function worldImageSigAWS(args) {
// API.$on('WORLDIMAGE:SIGAWS')
const { fileId, fileVersion } = args.params;
const params = {
fileId,
@@ -318,11 +303,10 @@
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`
imageUrl: `${AppGlobal.endpointDomain}/file/${fileId}/${fileVersion}/file`
};
const res = await imageRequest.setWorldImage(parmas);
return worldImageSet(res);
@@ -337,7 +321,7 @@
});
refresh();
} else {
API.$throw(0, 'World image change failed', args.params.imageUrl);
$throw(0, 'World image change failed', args.params.imageUrl);
}
}
@@ -346,8 +330,8 @@
function setWorldImage(image) {
changeWorldImageDialogLoading.value = true;
const parmas = {
id: props.worldDialog.id,
imageUrl: `${API.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
id: worldDialog.value.id,
imageUrl: `${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
};
imageRequest
.setWorldImage(parmas)
@@ -360,35 +344,11 @@
function compareCurrentImage(image) {
if (
`${API.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
// FIXME: old:avatarDialog -> new:worldDialog, is this correct?
props.worldDialog.ref.imageUrl
`${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
worldDialog.value.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>

View File

@@ -1,22 +1,22 @@
<template>
<safe-dialog
:visible.sync="isVisible"
:title="$t('dialog.set_world_tags.header')"
:title="t('dialog.set_world_tags.header')"
width="400px"
destroy-on-close
append-to-body>
<el-checkbox v-model="setWorldTagsDialog.avatarScalingDisabled">
{{ $t('dialog.set_world_tags.avatar_scaling_disabled') }}
{{ t('dialog.set_world_tags.avatar_scaling_disabled') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.focusViewDisabled">
{{ $t('dialog.set_world_tags.focus_view_disabled') }}
{{ t('dialog.set_world_tags.focus_view_disabled') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.debugAllowed">
{{ $t('dialog.set_world_tags.enable_debugging') }}
{{ t('dialog.set_world_tags.enable_debugging') }}
</el-checkbox>
<div style="font-size: 12px; margin-top: 10px">{{ $t('dialog.set_world_tags.author_tags') }}<br /></div>
<div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.author_tags') }}<br /></div>
<el-input
v-model="setWorldTagsDialog.authorTags"
type="textarea"
@@ -25,281 +25,286 @@
:autosize="{ minRows: 2, maxRows: 5 }"
placeholder=""
style="margin-top: 10px"></el-input>
<div style="font-size: 12px; margin-top: 10px">{{ $t('dialog.set_world_tags.content_tags') }}<br /></div>
<div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.content_tags') }}<br /></div>
<el-checkbox v-model="setWorldTagsDialog.contentHorror">
{{ $t('dialog.set_world_tags.content_horror') }}
{{ t('dialog.set_world_tags.content_horror') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.contentGore">
{{ $t('dialog.set_world_tags.content_gore') }}
{{ t('dialog.set_world_tags.content_gore') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.contentViolence">
{{ $t('dialog.set_world_tags.content_violence') }}
{{ t('dialog.set_world_tags.content_violence') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.contentAdult">
{{ $t('dialog.set_world_tags.content_adult') }}
{{ t('dialog.set_world_tags.content_adult') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.contentSex">
{{ $t('dialog.set_world_tags.content_sex') }}
{{ t('dialog.set_world_tags.content_sex') }}
</el-checkbox>
<div style="font-size: 12px; margin-top: 10px">
{{ $t('dialog.set_world_tags.default_content_settings') }}<br />
{{ t('dialog.set_world_tags.default_content_settings') }}<br />
</div>
<el-checkbox v-model="setWorldTagsDialog.emoji">
{{ $t('dialog.new_instance.content_emoji') }}
{{ t('dialog.new_instance.content_emoji') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.stickers">
{{ $t('dialog.new_instance.content_stickers') }}
{{ t('dialog.new_instance.content_stickers') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.pedestals">
{{ $t('dialog.new_instance.content_pedestals') }}
{{ t('dialog.new_instance.content_pedestals') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.prints">
{{ $t('dialog.new_instance.content_prints') }}
{{ t('dialog.new_instance.content_prints') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.drones">
{{ $t('dialog.new_instance.content_drones') }}
{{ t('dialog.new_instance.content_drones') }}
</el-checkbox>
<br />
<el-checkbox v-model="setWorldTagsDialog.props">
{{ $t('dialog.new_instance.content_items') }}
{{ t('dialog.new_instance.content_items') }}
</el-checkbox>
<template #footer>
<div style="display: flex">
<el-button size="small" @click="setWorldTagsDialog.visible = false">
{{ $t('dialog.set_world_tags.cancel') }}
{{ t('dialog.set_world_tags.cancel') }}
</el-button>
<el-button type="primary" size="small" @click="saveSetWorldTagsDialog">
{{ $t('dialog.set_world_tags.save') }}
{{ t('dialog.set_world_tags.save') }}
</el-button>
</div>
</template>
</safe-dialog>
</template>
<script>
<script setup>
import { ref, computed, watch, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { worldRequest } from '../../../api';
import { useWorldStore } from '../../../stores';
export default {
name: 'SetWorldTagsDialog',
inject: ['showWorldDialog'],
props: {
oldTags: {
type: Array,
default: () => []
},
isSetWorldTagsDialogVisible: {
type: Boolean,
required: true
},
worldId: {
type: String,
required: true
},
isWorldDialogVisible: {
type: Boolean,
required: true
}
const props = defineProps({
oldTags: {
type: Array,
default: () => []
},
data() {
return {
setWorldTagsDialog: {
authorTags: [],
contentTags: [],
debugAllowed: false,
avatarScalingDisabled: false,
focusViewDisabled: false,
contentHorror: false,
contentGore: false,
contentViolence: false,
contentAdult: false,
contentSex: false,
emoji: true,
stickers: true,
pedestals: true,
prints: true,
drones: true,
props: true
}
};
isSetWorldTagsDialogVisible: {
type: Boolean,
required: true
},
computed: {
isVisible: {
get() {
return this.isSetWorldTagsDialogVisible;
},
set(val) {
this.$emit('update:is-set-world-tags-dialog-visible', val);
}
}
worldId: {
type: String,
required: true
},
watch: {
isSetWorldTagsDialogVisible(val) {
if (val) {
this.showSetWorldTagsDialog();
}
}
isWorldDialogVisible: {
type: Boolean,
required: true
}
});
const emit = defineEmits(['update:isSetWorldTagsDialogVisible']);
const { showWorldDialog } = useWorldStore();
const { t } = useI18n();
const { proxy } = getCurrentInstance();
const setWorldTagsDialog = ref({
authorTags: '',
contentTags: '',
debugAllowed: false,
avatarScalingDisabled: false,
focusViewDisabled: false,
contentHorror: false,
contentGore: false,
contentViolence: false,
contentAdult: false,
contentSex: false,
emoji: true,
stickers: true,
pedestals: true,
prints: true,
drones: true,
props: true
});
const isVisible = computed({
get() {
return props.isSetWorldTagsDialogVisible;
},
methods: {
showSetWorldTagsDialog() {
const D = this.setWorldTagsDialog;
D.visible = true;
D.debugAllowed = false;
D.avatarScalingDisabled = false;
D.focusViewDisabled = false;
D.contentHorror = false;
D.contentGore = false;
D.contentViolence = false;
D.contentAdult = false;
D.contentSex = false;
const authorTags = [];
const contentTags = [];
this.oldTags.forEach((tag) => {
if (tag.startsWith('author_tag_')) {
authorTags.unshift(tag.substring(11));
}
if (tag.startsWith('content_')) {
contentTags.unshift(tag.substring(8));
}
switch (tag) {
case 'content_horror':
D.contentHorror = true;
break;
case 'content_gore':
D.contentGore = true;
break;
case 'content_violence':
D.contentViolence = true;
break;
case 'content_adult':
D.contentAdult = true;
break;
case 'content_sex':
D.contentSex = true;
break;
case 'debug_allowed':
D.debugAllowed = true;
break;
case 'feature_avatar_scaling_disabled':
D.avatarScalingDisabled = true;
break;
case 'feature_focus_view_disabled':
D.focusViewDisabled = true;
break;
case 'feature_emoji_disabled':
D.emoji = false;
break;
case 'feature_stickers_disabled':
D.stickers = false;
break;
case 'feature_pedestals_disabled':
D.pedestals = false;
break;
case 'feature_prints_disabled':
D.prints = false;
break;
case 'feature_drones_disabled':
D.drones = false;
break;
case 'feature_props_disabled':
D.props = false;
break;
}
});
D.authorTags = authorTags.toString();
D.contentTags = contentTags.toString();
},
saveSetWorldTagsDialog() {
const D = this.setWorldTagsDialog;
const authorTags = D.authorTags.trim().split(',');
const contentTags = D.contentTags.trim().split(',');
const tags = [];
authorTags.forEach((tag) => {
if (tag) {
tags.unshift(`author_tag_${tag}`);
}
});
// add back custom tags
contentTags.forEach((tag) => {
switch (tag) {
case 'horror':
case 'gore':
case 'violence':
case 'adult':
case 'sex':
case '':
break;
default:
tags.unshift(`content_${tag}`);
break;
}
});
if (D.contentHorror) {
tags.unshift('content_horror');
}
if (D.contentGore) {
tags.unshift('content_gore');
}
if (D.contentViolence) {
tags.unshift('content_violence');
}
if (D.contentAdult) {
tags.unshift('content_adult');
}
if (D.contentSex) {
tags.unshift('content_sex');
}
if (D.debugAllowed) {
tags.unshift('debug_allowed');
}
if (D.avatarScalingDisabled) {
tags.unshift('feature_avatar_scaling_disabled');
}
if (D.focusViewDisabled) {
tags.unshift('feature_focus_view_disabled');
}
if (!D.emoji) {
tags.unshift('feature_emoji_disabled');
}
if (!D.stickers) {
tags.unshift('feature_stickers_disabled');
}
if (!D.pedestals) {
tags.unshift('feature_pedestals_disabled');
}
if (!D.prints) {
tags.unshift('feature_prints_disabled');
}
if (!D.drones) {
tags.unshift('feature_drones_disabled');
}
if (!D.props) {
tags.unshift('feature_props_disabled');
}
worldRequest
.saveWorld({
id: this.worldId,
tags
})
.then((args) => {
this.$message({
message: 'Tags updated',
type: 'success'
});
this.$emit('update:is-set-world-tags-dialog-visible', false);
if (this.isWorldDialogVisible) {
this.showWorldDialog(args.json.id);
}
return args;
});
set(val) {
emit('update:isSetWorldTagsDialogVisible', val);
}
});
watch(
() => props.isSetWorldTagsDialogVisible,
(val) => {
if (val) {
showSetWorldTagsDialog();
}
}
};
);
function showSetWorldTagsDialog() {
const D = setWorldTagsDialog.value;
D.debugAllowed = false;
D.avatarScalingDisabled = false;
D.focusViewDisabled = false;
D.contentHorror = false;
D.contentGore = false;
D.contentViolence = false;
D.contentAdult = false;
D.contentSex = false;
const authorTags = [];
const contentTags = [];
props.oldTags.forEach((tag) => {
if (tag.startsWith('author_tag_')) {
authorTags.unshift(tag.substring(11));
}
if (tag.startsWith('content_')) {
contentTags.unshift(tag.substring(8));
}
switch (tag) {
case 'content_horror':
D.contentHorror = true;
break;
case 'content_gore':
D.contentGore = true;
break;
case 'content_violence':
D.contentViolence = true;
break;
case 'content_adult':
D.contentAdult = true;
break;
case 'content_sex':
D.contentSex = true;
break;
case 'debug_allowed':
D.debugAllowed = true;
break;
case 'feature_avatar_scaling_disabled':
D.avatarScalingDisabled = true;
break;
case 'feature_focus_view_disabled':
D.focusViewDisabled = true;
break;
case 'feature_emoji_disabled':
D.emoji = false;
break;
case 'feature_stickers_disabled':
D.stickers = false;
break;
case 'feature_pedestals_disabled':
D.pedestals = false;
break;
case 'feature_prints_disabled':
D.prints = false;
break;
case 'feature_drones_disabled':
D.drones = false;
break;
case 'feature_props_disabled':
D.props = false;
break;
}
});
D.authorTags = authorTags.toString();
D.contentTags = contentTags.toString();
}
function saveSetWorldTagsDialog() {
const D = setWorldTagsDialog.value;
const authorTags = D.authorTags.trim().split(',');
const contentTags = D.contentTags.trim().split(',');
const tags = [];
authorTags.forEach((tag) => {
if (tag) {
tags.unshift(`author_tag_${tag}`);
}
});
// add back custom tags
contentTags.forEach((tag) => {
switch (tag) {
case 'horror':
case 'gore':
case 'violence':
case 'adult':
case 'sex':
case '':
break;
default:
tags.unshift(`content_${tag}`);
break;
}
});
if (D.contentHorror) {
tags.unshift('content_horror');
}
if (D.contentGore) {
tags.unshift('content_gore');
}
if (D.contentViolence) {
tags.unshift('content_violence');
}
if (D.contentAdult) {
tags.unshift('content_adult');
}
if (D.contentSex) {
tags.unshift('content_sex');
}
if (D.debugAllowed) {
tags.unshift('debug_allowed');
}
if (D.avatarScalingDisabled) {
tags.unshift('feature_avatar_scaling_disabled');
}
if (D.focusViewDisabled) {
tags.unshift('feature_focus_view_disabled');
}
if (!D.emoji) {
tags.unshift('feature_emoji_disabled');
}
if (!D.stickers) {
tags.unshift('feature_stickers_disabled');
}
if (!D.pedestals) {
tags.unshift('feature_pedestals_disabled');
}
if (!D.prints) {
tags.unshift('feature_prints_disabled');
}
if (!D.drones) {
tags.unshift('feature_drones_disabled');
}
if (!D.props) {
tags.unshift('feature_props_disabled');
}
worldRequest
.saveWorld({
id: props.worldId,
tags
})
.then((args) => {
proxy.$message({
message: 'Tags updated',
type: 'success'
});
emit('update:isSetWorldTagsDialogVisible', false);
if (props.isWorldDialogVisible) {
showWorldDialog(args.json.id);
}
return args;
});
}
</script>

View File

@@ -1,7 +1,7 @@
<template>
<safe-dialog
:visible.sync="isVisible"
:title="$t('dialog.allowed_video_player_domains.header')"
:title="t('dialog.allowed_video_player_domains.header')"
width="600px"
destroy-on-close
append-to-body>
@@ -10,13 +10,12 @@
v-for="(domain, index) in urlList"
:key="index"
v-model="urlList[index]"
:value="domain"
size="small"
style="margin-top: 5px">
<el-button slot="append" icon="el-icon-delete" @click="urlList.splice(index, 1)"></el-button>
</el-input>
<el-button size="mini" style="margin-top: 5px" @click="urlList.push('')">
{{ $t('dialog.allowed_video_player_domains.add_domain') }}
{{ t('dialog.allowed_video_player_domains.add_domain') }}
</el-button>
</div>
<template #footer>
@@ -25,65 +24,67 @@
size="small"
:disabled="!worldAllowedDomainsDialog.worldId"
@click="saveWorldAllowedDomains">
{{ $t('dialog.allowed_video_player_domains.save') }}
{{ t('dialog.allowed_video_player_domains.save') }}
</el-button>
</template>
</safe-dialog>
</template>
<script>
<script setup>
import { ref, computed, watch, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { worldRequest } from '../../../api';
export default {
name: 'WorldAllowedDomainsDialog',
props: {
worldAllowedDomainsDialog: {
type: Object,
required: true
}
const props = defineProps({
worldAllowedDomainsDialog: {
type: Object,
required: true
}
});
const emit = defineEmits(['update:worldAllowedDomainsDialog']);
const { proxy } = getCurrentInstance();
const { t } = useI18n();
const urlList = ref([]);
const isVisible = computed({
get() {
return props.worldAllowedDomainsDialog.visible;
},
data() {
return {
urlList: []
};
},
computed: {
isVisible: {
get() {
return this.worldAllowedDomainsDialog.visible;
},
set(val) {
this.$emit('update:world-allowed-domains-dialog', {
...this.worldAllowedDomainsDialog,
visible: val
});
}
}
},
watch: {
'worldAllowedDomainsDialog.visible'(val) {
if (val) {
this.urlList = this.worldAllowedDomainsDialog.urlList;
}
}
},
methods: {
saveWorldAllowedDomains() {
const D = this.worldAllowedDomainsDialog;
worldRequest
.saveWorld({
id: D.worldId,
urlList: D.urlList
})
.then((args) => {
this.$message({
message: 'Allowed Video Player Domains updated',
type: 'success'
});
return args;
});
D.visible = false;
set(val) {
emit('update:worldAllowedDomainsDialog', {
...props.worldAllowedDomainsDialog,
visible: val
});
}
});
watch(
() => props.worldAllowedDomainsDialog.visible,
(val) => {
if (val) {
urlList.value = props.worldAllowedDomainsDialog.urlList;
}
}
};
);
function saveWorldAllowedDomains() {
const D = props.worldAllowedDomainsDialog;
worldRequest
.saveWorld({
id: D.worldId,
urlList: urlList.value
})
.then((args) => {
proxy.$message({
message: 'Allowed Video Player Domains updated',
type: 'success'
});
return args;
});
D.visible = false;
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -1,164 +0,0 @@
import { reactive, ref } from 'vue';
import configRepository from '../../service/config';
function useModerationTable() {
const groupInvitesModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupJoinRequestsModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupBlockedModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupLogsModerationTable = reactive({
data: [],
filters: [{ prop: ['description'], value: '' }],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupBansModerationTable = reactive({
data: [],
filters: [{ prop: ['$displayName'], value: '' }],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupMemberModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
async function initializePageSize() {
try {
const tablePageSize = await configRepository.getInt(
'VRCX_tablePageSize',
15
);
groupMemberModerationTable.pageSize = tablePageSize;
groupBansModerationTable.pageSize = tablePageSize;
groupLogsModerationTable.pageSize = tablePageSize;
groupInvitesModerationTable.pageSize = tablePageSize;
groupJoinRequestsModerationTable.pageSize = tablePageSize;
groupBlockedModerationTable.pageSize = tablePageSize;
} catch (error) {
console.error('Failed to initialize table page size:', error);
}
}
function deselectGroupMember(userId) {
const deselectInTable = (tableData) => {
if (userId) {
const row = tableData.find((item) => item.userId === userId);
if (row) {
row.$selected = false;
}
} else {
tableData.forEach((row) => {
if (row.$selected) {
row.$selected = false;
}
});
}
};
deselectInTable(groupMemberModerationTable.data);
deselectInTable(groupBansModerationTable.data);
deselectInTable(groupInvitesModerationTable.data);
deselectInTable(groupJoinRequestsModerationTable.data);
deselectInTable(groupBlockedModerationTable.data);
}
return {
groupInvitesModerationTable,
groupJoinRequestsModerationTable,
groupBlockedModerationTable,
groupLogsModerationTable,
groupBansModerationTable,
groupMemberModerationTable,
initializePageSize,
deselectGroupMember
};
}
function useSelectedUsers() {
const selectedUsers = reactive({});
// computed not working here hmm
const selectedUsersArray = ref([]);
function groupMemberModerationTableSelectionChange(row) {
if (row.$selected && !selectedUsers[row.userId]) {
setSelectedUsers(row.userId, row);
} else if (!row.$selected && selectedUsers[row.userId]) {
deselectedUsers(row.userId);
}
}
function deselectedUsers(userId, isAll = false) {
if (isAll) {
for (const id in selectedUsers) {
if (Object.prototype.hasOwnProperty.call(selectedUsers, id)) {
delete selectedUsers[id];
}
}
} else {
if (Object.prototype.hasOwnProperty.call(selectedUsers, userId)) {
delete selectedUsers[userId];
}
}
selectedUsersArray.value = Object.values(selectedUsers);
}
function setSelectedUsers(usersId, user) {
if (!user) {
return;
}
selectedUsers[usersId] = user;
selectedUsersArray.value = Object.values(selectedUsers);
}
return {
selectedUsers,
selectedUsersArray,
groupMemberModerationTableSelectionChange,
deselectedUsers,
setSelectedUsers
};
}
export { useModerationTable, useSelectedUsers };

Some files were not shown because too many files have changed in this diff Show More