mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-05 14:26:06 +02:00
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:
@@ -0,0 +1,423 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { instanceRequest, userRequest } from '../api';
|
||||
import { groupRequest } from '../api/';
|
||||
import { $app } from '../app';
|
||||
import removeConfusables, { removeWhitespace } from '../service/confusables';
|
||||
import { watchState } from '../service/watchState';
|
||||
import { compareByName, localeIncludes } from '../shared/utils';
|
||||
import { useAvatarStore } from './avatar';
|
||||
import { useFriendStore } from './friend';
|
||||
import { useGroupStore } from './group';
|
||||
import { useAppearanceSettingsStore } from './settings/appearance';
|
||||
import { useUiStore } from './ui';
|
||||
import { useUserStore } from './user';
|
||||
import { useWorldStore } from './world';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
|
||||
export const useSearchStore = defineStore('Search', () => {
|
||||
const userStore = useUserStore();
|
||||
const uiStore = useUiStore();
|
||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||
const friendStore = useFriendStore();
|
||||
const worldStore = useWorldStore();
|
||||
const avatarStore = useAvatarStore();
|
||||
const groupStore = useGroupStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const state = reactive({
|
||||
searchText: '',
|
||||
searchUserResults: [],
|
||||
quickSearchItems: [],
|
||||
friendsListSearch: ''
|
||||
});
|
||||
|
||||
const searchText = computed({
|
||||
get: () => state.searchText,
|
||||
set: (value) => {
|
||||
state.searchText = value;
|
||||
}
|
||||
});
|
||||
|
||||
const searchUserResults = computed({
|
||||
get: () => state.searchUserResults,
|
||||
set: (value) => {
|
||||
state.searchUserResults = value;
|
||||
}
|
||||
});
|
||||
|
||||
const quickSearchItems = computed({
|
||||
get: () => state.quickSearchItems,
|
||||
set: (value) => {
|
||||
state.quickSearchItems = value;
|
||||
}
|
||||
});
|
||||
|
||||
const stringComparer = computed(() =>
|
||||
Intl.Collator(appearanceSettingsStore.appLanguage.replace('_', '-'), {
|
||||
usage: 'search',
|
||||
sensitivity: 'base'
|
||||
})
|
||||
);
|
||||
|
||||
const friendsListSearch = computed({
|
||||
get: () => state.friendsListSearch,
|
||||
set: (value) => {
|
||||
state.friendsListSearch = value;
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => watchState.isLoggedIn,
|
||||
() => {
|
||||
state.searchText = '';
|
||||
state.searchUserResults = [];
|
||||
},
|
||||
{ flush: 'sync' }
|
||||
);
|
||||
|
||||
function clearSearch() {
|
||||
state.searchText = '';
|
||||
state.searchUserResults = [];
|
||||
}
|
||||
|
||||
async function searchUserByDisplayName(displayName) {
|
||||
const params = {
|
||||
n: 10,
|
||||
offset: 0,
|
||||
fuzzy: false,
|
||||
search: displayName
|
||||
};
|
||||
await moreSearchUser(null, params);
|
||||
}
|
||||
|
||||
async function moreSearchUser(go, params) {
|
||||
if (go) {
|
||||
params.offset += params.n * go;
|
||||
if (params.offset < 0) {
|
||||
params.offset = 0;
|
||||
}
|
||||
}
|
||||
await userRequest.getUsers(params).then((args) => {
|
||||
for (const json of args.json) {
|
||||
if (!json.displayName) {
|
||||
console.error('getUsers gave us garbage', json);
|
||||
continue;
|
||||
}
|
||||
userStore.applyUser(json);
|
||||
}
|
||||
|
||||
const map = new Map();
|
||||
for (const json of args.json) {
|
||||
const ref = userStore.cachedUsers.get(json.id);
|
||||
if (typeof ref !== 'undefined') {
|
||||
map.set(ref.id, ref);
|
||||
}
|
||||
}
|
||||
state.searchUserResults = Array.from(map.values());
|
||||
return args;
|
||||
});
|
||||
}
|
||||
|
||||
function quickSearchRemoteMethod(query) {
|
||||
if (!query) {
|
||||
state.quickSearchItems = quickSearchUserHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
const results = [];
|
||||
const cleanQuery = removeWhitespace(query);
|
||||
|
||||
for (const ctx of friendStore.friends.values()) {
|
||||
if (typeof ctx.ref === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const cleanName = removeConfusables(ctx.name);
|
||||
let match = localeIncludes(
|
||||
cleanName,
|
||||
cleanQuery,
|
||||
stringComparer.value
|
||||
);
|
||||
if (!match) {
|
||||
// Also check regular name in case search is with special characters
|
||||
match = localeIncludes(
|
||||
ctx.name,
|
||||
cleanQuery,
|
||||
stringComparer.value
|
||||
);
|
||||
}
|
||||
// Use query with whitespace for notes and memos as people are more
|
||||
// likely to include spaces in memos and notes
|
||||
if (!match && ctx.memo) {
|
||||
match = localeIncludes(ctx.memo, query, stringComparer.value);
|
||||
}
|
||||
if (!match && ctx.ref.note) {
|
||||
match = localeIncludes(
|
||||
ctx.ref.note,
|
||||
query,
|
||||
stringComparer.value
|
||||
);
|
||||
}
|
||||
|
||||
if (match) {
|
||||
results.push({
|
||||
value: ctx.id,
|
||||
label: ctx.name,
|
||||
ref: ctx.ref,
|
||||
name: ctx.name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
results.sort(function (a, b) {
|
||||
const A =
|
||||
stringComparer.value.compare(
|
||||
a.name.substring(0, cleanQuery.length),
|
||||
cleanQuery
|
||||
) === 0;
|
||||
const B =
|
||||
stringComparer.value.compare(
|
||||
b.name.substring(0, cleanQuery.length),
|
||||
cleanQuery
|
||||
) === 0;
|
||||
if (A && !B) {
|
||||
return -1;
|
||||
} else if (B && !A) {
|
||||
return 1;
|
||||
}
|
||||
return compareByName(a, b);
|
||||
});
|
||||
if (results.length > 4) {
|
||||
results.length = 4;
|
||||
}
|
||||
results.push({
|
||||
value: `search:${query}`,
|
||||
label: query
|
||||
});
|
||||
|
||||
state.quickSearchItems = results;
|
||||
}
|
||||
|
||||
function quickSearchChange(value) {
|
||||
if (value) {
|
||||
if (value.startsWith('search:')) {
|
||||
const searchText = value.substr(7);
|
||||
if (state.quickSearchItems.length > 1 && searchText.length) {
|
||||
state.friendsListSearch = searchText;
|
||||
uiStore.menuActiveIndex = 'friendList';
|
||||
} else {
|
||||
uiStore.menuActiveIndex = 'search';
|
||||
state.searchText = searchText;
|
||||
userStore.lookupUser({ displayName: searchText });
|
||||
}
|
||||
} else {
|
||||
userStore.showUserDialog(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function quickSearchUserHistory() {
|
||||
const userHistory = Array.from(userStore.showUserDialogHistory.values())
|
||||
.reverse()
|
||||
.slice(0, 5);
|
||||
const results = [];
|
||||
userHistory.forEach((userId) => {
|
||||
const ref = userStore.cachedUsers.get(userId);
|
||||
if (typeof ref !== 'undefined') {
|
||||
results.push({
|
||||
value: ref.id,
|
||||
label: ref.name,
|
||||
ref
|
||||
});
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
function directAccessPaste() {
|
||||
AppApi.GetClipboard().then((clipboard) => {
|
||||
if (!directAccessParse(clipboard.trim())) {
|
||||
promptOmniDirectDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function directAccessParse(input) {
|
||||
if (!input) {
|
||||
return false;
|
||||
}
|
||||
if (directAccessWorld(input)) {
|
||||
return true;
|
||||
}
|
||||
if (input.startsWith('https://vrchat.')) {
|
||||
const url = new URL(input);
|
||||
const urlPath = url.pathname;
|
||||
const urlPathSplit = urlPath.split('/');
|
||||
if (urlPathSplit.length < 4) {
|
||||
return false;
|
||||
}
|
||||
const type = urlPathSplit[2];
|
||||
if (type === 'user') {
|
||||
const userId = urlPathSplit[3];
|
||||
userStore.showUserDialog(userId);
|
||||
return true;
|
||||
} else if (type === 'avatar') {
|
||||
const avatarId = urlPathSplit[3];
|
||||
avatarStore.showAvatarDialog(avatarId);
|
||||
return true;
|
||||
} else if (type === 'group') {
|
||||
const groupId = urlPathSplit[3];
|
||||
groupStore.showGroupDialog(groupId);
|
||||
return true;
|
||||
}
|
||||
} else if (input.startsWith('https://vrc.group/')) {
|
||||
const shortCode = input.substring(18);
|
||||
showGroupDialogShortCode(shortCode);
|
||||
return true;
|
||||
} else if (/^[A-Za-z0-9]{3,6}\.[0-9]{4}$/g.test(input)) {
|
||||
showGroupDialogShortCode(input);
|
||||
return true;
|
||||
} else if (
|
||||
input.substring(0, 4) === 'usr_' ||
|
||||
/^[A-Za-z0-9]{10}$/g.test(input)
|
||||
) {
|
||||
userStore.showUserDialog(input);
|
||||
return true;
|
||||
} else if (input.substring(0, 5) === 'avtr_') {
|
||||
avatarStore.showAvatarDialog(input);
|
||||
return true;
|
||||
} else if (input.substring(0, 4) === 'grp_') {
|
||||
groupStore.showGroupDialog(input);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function directAccessWorld(textBoxInput) {
|
||||
let worldId;
|
||||
let shortName;
|
||||
let input = textBoxInput;
|
||||
if (input.startsWith('/home/')) {
|
||||
input = `https://vrchat.com${input}`;
|
||||
}
|
||||
if (input.length === 8) {
|
||||
return verifyShortName('', input);
|
||||
} else if (input.startsWith('https://vrch.at/')) {
|
||||
shortName = input.substring(16, 24);
|
||||
return verifyShortName('', shortName);
|
||||
} else if (
|
||||
input.startsWith('https://vrchat.') ||
|
||||
input.startsWith('/home/')
|
||||
) {
|
||||
const url = new URL(input);
|
||||
const urlPath = url.pathname;
|
||||
const urlPathSplit = urlPath.split('/');
|
||||
if (urlPathSplit.length >= 4 && urlPathSplit[2] === 'world') {
|
||||
worldId = urlPathSplit[3];
|
||||
worldStore.showWorldDialog(worldId);
|
||||
return true;
|
||||
} else if (urlPath.substring(5, 12) === '/launch') {
|
||||
const urlParams = new URLSearchParams(url.search);
|
||||
worldId = urlParams.get('worldId');
|
||||
const instanceId = urlParams.get('instanceId');
|
||||
if (instanceId) {
|
||||
shortName = urlParams.get('shortName');
|
||||
const location = `${worldId}:${instanceId}`;
|
||||
if (shortName) {
|
||||
return verifyShortName(location, shortName);
|
||||
}
|
||||
worldStore.showWorldDialog(location);
|
||||
return true;
|
||||
} else if (worldId) {
|
||||
worldStore.showWorldDialog(worldId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (input.substring(0, 5) === 'wrld_') {
|
||||
// a bit hacky, but supports weird malformed inputs cut out from url, why not
|
||||
if (input.indexOf('&instanceId=') >= 0) {
|
||||
input = `https://vrchat.com/home/launch?worldId=${input}`;
|
||||
return directAccessWorld(input);
|
||||
}
|
||||
worldStore.showWorldDialog(input.trim());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function promptOmniDirectDialog() {
|
||||
$app.$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) {
|
||||
const input = instance.inputValue.trim();
|
||||
if (!directAccessParse(input)) {
|
||||
$app.$message({
|
||||
message: t(
|
||||
'prompt.direct_access_omni.message.error'
|
||||
),
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function showGroupDialogShortCode(shortCode) {
|
||||
groupRequest.groupStrictsearch({ query: shortCode }).then((args) => {
|
||||
for (const group of args.json) {
|
||||
if (`${group.shortCode}.${group.discriminator}` === shortCode) {
|
||||
groupStore.showGroupDialog(group.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
});
|
||||
}
|
||||
|
||||
function verifyShortName(location, shortName) {
|
||||
return instanceRequest
|
||||
.getInstanceFromShortName({ shortName })
|
||||
.then((args) => {
|
||||
const newLocation = args.json.location;
|
||||
const newShortName = args.json.shortName;
|
||||
if (newShortName) {
|
||||
worldStore.showWorldDialog(newLocation, newShortName);
|
||||
} else if (newLocation) {
|
||||
worldStore.showWorldDialog(newLocation);
|
||||
} else {
|
||||
worldStore.showWorldDialog(location);
|
||||
}
|
||||
return args;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
searchText,
|
||||
searchUserResults,
|
||||
stringComparer,
|
||||
quickSearchItems,
|
||||
friendsListSearch,
|
||||
clearSearch,
|
||||
searchUserByDisplayName,
|
||||
moreSearchUser,
|
||||
quickSearchUserHistory,
|
||||
quickSearchRemoteMethod,
|
||||
quickSearchChange,
|
||||
directAccessParse,
|
||||
directAccessPaste,
|
||||
directAccessWorld,
|
||||
verifyShortName
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user