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

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;
});
}