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
@@ -3,16 +3,13 @@
<div style="display: flex; align-items: center; justify-content: space-between">
<div>
<el-button size="small" @click="showExportDialog">{{ $t('view.favorite.export') }}</el-button>
<el-button size="small" style="margin-left: 5px" @click="$emit('show-world-import-dialog')">{{
<el-button size="small" style="margin-left: 5px" @click="showWorldImportDialog">{{
$t('view.favorite.import')
}}</el-button>
</div>
<div style="display: flex; align-items: center; font-size: 13px; margin-right: 10px">
<span class="name" style="margin-right: 5px; line-height: 10px">{{ $t('view.favorite.sort_by') }}</span>
<el-radio-group
v-model="sortFav"
style="margin-right: 12px"
@change="$emit('save-sort-favorites-option')">
<el-radio-group v-model="sortFav" style="margin-right: 12px" @change="saveSortFavoritesOption">
<el-radio :label="false">{{
$t('view.settings.appearance.appearance.sort_favorite_by_name')
}}</el-radio>
@@ -59,7 +56,7 @@
</div>
<span style="display: block; margin-top: 20px">{{ $t('view.favorite.worlds.vrchat_favorites') }}</span>
<el-collapse style="border: 0">
<el-collapse-item v-for="group in API.favoriteWorldGroups" :key="group.name">
<el-collapse-item v-for="group in favoriteWorldGroups" :key="group.name">
<template slot="title">
<div style="display: flex; align-items: center">
<span
@@ -125,10 +122,8 @@
:favorite="favorite"
:edit-favorites-mode="editFavoritesMode"
:hide-tooltips="hideTooltips"
:shift-held="shiftHeld"
@click="showWorldDialog(favorite.id)"
@handle-select="favorite.$selected = $event"
@new-instance-self-invite="newInstanceSelfInvite" />
@handle-select="favorite.$selected = $event" />
</div>
<div
v-else
@@ -153,7 +148,7 @@
v-if="!refreshingLocalFavorites"
size="small"
style="margin-left: 5px"
@click="$emit('refresh-local-world-favorite')"
@click="refreshLocalWorldFavorite"
>{{ $t('view.favorite.worlds.refresh') }}</el-button
>
<el-button v-else size="small" style="margin-left: 5px" @click="refreshingLocalFavorites = false">
@@ -196,9 +191,7 @@
:favorite="favorite"
:edit-favorites-mode="editFavoritesMode"
:hide-tooltips="hideTooltips"
:shift-held="shiftHeld"
@click="showWorldDialog(favorite.id)"
@new-instance-self-invite="newInstanceSelfInvite"
@remove-local-world-favorite="removeLocalWorldFavorite" />
</div>
<div
@@ -215,236 +208,247 @@
</div>
</el-collapse-item>
</el-collapse>
<WorldExportDialog
:favorite-worlds="favoriteWorlds"
:world-export-dialog-visible.sync="worldExportDialogVisible"
:local-world-favorites="localWorldFavorites"
:local-world-favorite-groups="localWorldFavoriteGroups"
:local-world-favorites-list="localWorldFavoritesList" />
<WorldExportDialog :world-export-dialog-visible.sync="worldExportDialogVisible" />
</div>
</template>
<script>
import FavoritesWorldItem from './FavoritesWorldItem.vue';
import WorldExportDialog from '../dialogs/WorldExportDialog.vue';
<script setup>
import { computed, ref, getCurrentInstance } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n-bridge';
import { favoriteRequest } from '../../../api';
import { useAppearanceSettingsStore, useFavoriteStore, useWorldStore } from '../../../stores';
import WorldExportDialog from '../dialogs/WorldExportDialog.vue';
import FavoritesWorldItem from './FavoritesWorldItem.vue';
export default {
name: 'FavoritesWorldTab',
components: {
FavoritesWorldItem,
WorldExportDialog
defineProps({
editFavoritesMode: {
type: Boolean,
default: false
},
inject: ['API', 'showWorldDialog'],
props: {
sortFavorites: Boolean,
hideTooltips: Boolean,
favoriteWorlds: Array,
editFavoritesMode: Boolean,
shiftHeld: Boolean,
refreshingLocalFavorites: Boolean,
localWorldFavoriteGroups: Array,
localWorldFavorites: Object,
localWorldFavoritesList: Array
},
data() {
return {
worldGroupVisibilityOptions: ['private', 'friends', 'public'],
worldFavoriteSearch: '',
worldExportDialogVisible: false,
worldFavoriteSearchResults: []
};
},
computed: {
groupedByGroupKeyFavoriteWorlds() {
const groupedByGroupKeyFavoriteWorlds = {};
refreshingLocalFavorites: {
type: Boolean,
default: false
}
});
this.favoriteWorlds.forEach((world) => {
if (world.groupKey) {
if (!groupedByGroupKeyFavoriteWorlds[world.groupKey]) {
groupedByGroupKeyFavoriteWorlds[world.groupKey] = [];
}
groupedByGroupKeyFavoriteWorlds[world.groupKey].push(world);
}
});
const emit = defineEmits([
'change-favorite-group-name',
'save-sort-favorites-option',
'refresh-local-world-favorite'
]);
return groupedByGroupKeyFavoriteWorlds;
},
sortFav: {
get() {
return this.sortFavorites;
},
set(value) {
this.$emit('update:sort-favorites', value);
const { proxy } = getCurrentInstance();
const { t } = useI18n();
const { hideTooltips, sortFavorites } = storeToRefs(useAppearanceSettingsStore());
const { setSortFavorites } = useAppearanceSettingsStore();
const { favoriteWorlds, favoriteWorldGroups, localWorldFavorites, localWorldFavoriteGroups } =
storeToRefs(useFavoriteStore());
const {
showWorldImportDialog,
getLocalWorldFavoriteGroupLength,
deleteLocalWorldFavoriteGroup,
renameLocalWorldFavoriteGroup,
removeLocalWorldFavorite,
newLocalWorldFavoriteGroup,
handleFavoriteGroup
} = useFavoriteStore();
const { showWorldDialog } = useWorldStore();
const worldGroupVisibilityOptions = ref(['private', 'friends', 'public']);
const worldExportDialogVisible = ref(false);
const worldFavoriteSearch = ref('');
const worldFavoriteSearchResults = ref([]);
const groupedByGroupKeyFavoriteWorlds = computed(() => {
const groupedByGroupKeyFavoriteWorlds = {};
favoriteWorlds.value.forEach((world) => {
if (world.groupKey) {
if (!groupedByGroupKeyFavoriteWorlds[world.groupKey]) {
groupedByGroupKeyFavoriteWorlds[world.groupKey] = [];
}
groupedByGroupKeyFavoriteWorlds[world.groupKey].push(world);
}
});
return groupedByGroupKeyFavoriteWorlds;
});
const sortFav = computed({
get() {
return sortFavorites.value;
},
set() {
setSortFavorites();
}
});
function showExportDialog() {
worldExportDialogVisible.value = true;
}
function userFavoriteWorldsStatusForFavTab(visibility) {
let style = '';
if (visibility === 'public') {
style = '';
} else if (visibility === 'friends') {
style = 'success';
} else {
style = 'info';
}
return style;
}
function changeWorldGroupVisibility(name, visibility) {
const params = {
type: 'world',
group: name,
visibility
};
favoriteRequest.saveFavoriteGroup(params).then((args) => {
handleFavoriteGroup({
json: args.json,
params: {
favoriteGroupId: args.json.id
}
});
proxy.$message({
message: 'Group visibility changed',
type: 'success'
});
return args;
});
}
function promptNewLocalWorldFavoriteGroup() {
proxy.$prompt(t('prompt.new_local_favorite_group.description'), t('prompt.new_local_favorite_group.header'), {
distinguishCancelAndClose: true,
confirmButtonText: t('prompt.new_local_favorite_group.ok'),
cancelButtonText: t('prompt.new_local_favorite_group.cancel'),
inputPattern: /\S+/,
inputErrorMessage: t('prompt.new_local_favorite_group.input_error'),
callback: (action, instance) => {
if (action === 'confirm' && instance.inputValue) {
newLocalWorldFavoriteGroup(instance.inputValue);
}
}
},
methods: {
showExportDialog() {
this.worldExportDialogVisible = true;
},
});
}
userFavoriteWorldsStatusForFavTab(visibility) {
let style = '';
if (visibility === 'public') {
style = '';
} else if (visibility === 'friends') {
style = 'success';
} else {
style = 'info';
function promptLocalWorldFavoriteGroupRename(group) {
proxy.$prompt(
t('prompt.local_favorite_group_rename.description'),
t('prompt.local_favorite_group_rename.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: t('prompt.local_favorite_group_rename.save'),
cancelButtonText: t('prompt.local_favorite_group_rename.cancel'),
inputPattern: /\S+/,
inputErrorMessage: t('prompt.local_favorite_group_rename.input_error'),
inputValue: group,
callback: (action, instance) => {
if (action === 'confirm' && instance.inputValue) {
renameLocalWorldFavoriteGroup(instance.inputValue, group);
}
}
return style;
},
changeWorldGroupVisibility(name, visibility) {
const params = {
type: 'world',
group: name,
visibility
};
favoriteRequest.saveFavoriteGroup(params).then((args) => {
this.$message({
message: 'Group visibility changed',
type: 'success'
}
);
}
function promptLocalWorldFavoriteGroupDelete(group) {
proxy.$confirm(`Delete Group? ${group}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
deleteLocalWorldFavoriteGroup(group);
}
}
});
}
function clearFavoriteGroup(ctx) {
proxy.$confirm('Continue? Clear Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
favoriteRequest.clearFavoriteGroup({
type: ctx.type,
group: ctx.name
});
return args;
});
},
promptNewLocalWorldFavoriteGroup() {
this.$prompt(
$t('prompt.new_local_favorite_group.description'),
$t('prompt.new_local_favorite_group.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.new_local_favorite_group.ok'),
cancelButtonText: $t('prompt.new_local_favorite_group.cancel'),
inputPattern: /\S+/,
inputErrorMessage: $t('prompt.new_local_favorite_group.input_error'),
callback: (action, instance) => {
if (action === 'confirm' && instance.inputValue) {
this.$emit('new-local-world-favorite-group', instance.inputValue);
}
}
}
);
},
promptLocalWorldFavoriteGroupRename(group) {
this.$prompt(
$t('prompt.local_favorite_group_rename.description'),
$t('prompt.local_favorite_group_rename.header'),
{
distinguishCancelAndClose: true,
confirmButtonText: $t('prompt.local_favorite_group_rename.save'),
cancelButtonText: $t('prompt.local_favorite_group_rename.cancel'),
inputPattern: /\S+/,
inputErrorMessage: $t('prompt.local_favorite_group_rename.input_error'),
inputValue: group,
callback: (action, instance) => {
if (action === 'confirm' && instance.inputValue) {
this.$emit('rename-local-world-favorite-group', instance.inputValue, group);
}
}
}
);
},
promptLocalWorldFavoriteGroupDelete(group) {
this.$confirm(`Delete Group? ${group}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
this.$emit('delete-local-world-favorite-group', group);
}
}
});
},
getLocalWorldFavoriteGroupLength(group) {
const favoriteGroup = this.localWorldFavorites[group];
if (!favoriteGroup) {
return 0;
}
return favoriteGroup.length;
},
}
});
}
clearFavoriteGroup(ctx) {
// FIXME: 메시지 수정
this.$confirm('Continue? Clear Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
favoriteRequest.clearFavoriteGroup({
type: ctx.type,
group: ctx.name
});
}
}
});
},
searchWorldFavorites(worldFavoriteSearch) {
let ref = null;
const search = worldFavoriteSearch.toLowerCase();
if (search.length < 3) {
this.worldFavoriteSearchResults = [];
return;
function searchWorldFavorites(worldFavoriteSearch) {
let ref = null;
const search = worldFavoriteSearch.toLowerCase();
if (search.length < 3) {
worldFavoriteSearchResults.value = [];
return;
}
const results = [];
for (let i = 0; i < localWorldFavoriteGroups.value.length; ++i) {
const group = localWorldFavoriteGroups.value[i];
if (!localWorldFavorites.value[group]) {
continue;
}
for (let j = 0; j < localWorldFavorites.value[group].length; ++j) {
ref = localWorldFavorites.value[group][j];
if (
!ref ||
typeof ref.id === 'undefined' ||
typeof ref.name === 'undefined' ||
typeof ref.authorName === 'undefined'
) {
continue;
}
const results = [];
for (let i = 0; i < this.localWorldFavoriteGroups.length; ++i) {
const group = this.localWorldFavoriteGroups[i];
if (!this.localWorldFavorites[group]) {
continue;
}
for (let j = 0; j < this.localWorldFavorites[group].length; ++j) {
ref = this.localWorldFavorites[group][j];
if (
!ref ||
typeof ref.id === 'undefined' ||
typeof ref.name === 'undefined' ||
typeof ref.authorName === 'undefined'
) {
continue;
}
if (ref.name.toLowerCase().includes(search) || ref.authorName.toLowerCase().includes(search)) {
if (!results.some((r) => r.id === ref.id)) {
results.push(ref);
}
}
if (ref.name.toLowerCase().includes(search) || ref.authorName.toLowerCase().includes(search)) {
if (!results.some((r) => r.id === ref.id)) {
results.push(ref);
}
}
for (let i = 0; i < this.favoriteWorlds.length; ++i) {
ref = this.favoriteWorlds[i].ref;
if (
!ref ||
typeof ref.id === 'undefined' ||
typeof ref.name === 'undefined' ||
typeof ref.authorName === 'undefined'
) {
continue;
}
if (ref.name.toLowerCase().includes(search) || ref.authorName.toLowerCase().includes(search)) {
if (!results.some((r) => r.id === ref.id)) {
results.push(ref);
}
}
}
this.worldFavoriteSearchResults = results;
},
changeFavoriteGroupName(group) {
this.$emit('change-favorite-group-name', group);
},
newInstanceSelfInvite(event) {
this.$emit('new-instance-self-invite', event);
},
removeLocalWorldFavorite(param1, param2) {
this.$emit('remove-local-world-favorite', param1, param2);
}
}
};
for (let i = 0; i < favoriteWorlds.value.length; ++i) {
ref = favoriteWorlds.value[i].ref;
if (
!ref ||
typeof ref.id === 'undefined' ||
typeof ref.name === 'undefined' ||
typeof ref.authorName === 'undefined'
) {
continue;
}
if (ref.name.toLowerCase().includes(search) || ref.authorName.toLowerCase().includes(search)) {
if (!results.some((r) => r.id === ref.id)) {
results.push(ref);
}
}
}
worldFavoriteSearchResults.value = results;
}
function changeFavoriteGroupName(group) {
emit('change-favorite-group-name', group);
}
function refreshLocalWorldFavorite() {
emit('refresh-local-world-favorite');
}
function saveSortFavoritesOption() {
emit('save-sort-favorites-option');
}
</script>
<style scoped></style>