mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-17 22:03:50 +02:00
432 lines
14 KiB
JavaScript
432 lines
14 KiB
JavaScript
import { defineStore } from 'pinia';
|
|
import { computed, reactive, watch } from 'vue';
|
|
import { ElMessageBox, ElMessage } from 'element-plus';
|
|
import { instanceRequest, userRequest } from '../api';
|
|
import { groupRequest } from '../api/';
|
|
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';
|
|
|
|
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;
|
|
}
|
|
|
|
if (query.length < 2) {
|
|
state.quickSearchItems = quickSearchUserHistory();
|
|
return;
|
|
}
|
|
|
|
const results = [];
|
|
const cleanQuery = removeWhitespace(query);
|
|
if (!cleanQuery) {
|
|
state.quickSearchItems = quickSearchUserHistory();
|
|
return;
|
|
}
|
|
|
|
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() {
|
|
ElMessageBox.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')
|
|
}
|
|
).then(({ value, action }) => {
|
|
if (action === 'confirm' && value) {
|
|
const input = value.trim();
|
|
if (!directAccessParse(input)) {
|
|
ElMessage({
|
|
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
|
|
};
|
|
});
|