mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-18 14:23:51 +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:
35
src/shared/utils/_utils.js
Normal file
35
src/shared/utils/_utils.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
checkVRChatCache,
|
||||
deleteVRChatCache,
|
||||
displayLocation,
|
||||
extractFileId,
|
||||
extractFileVersion,
|
||||
extractVariantVersion,
|
||||
getAvailablePlatforms,
|
||||
getBundleLocation,
|
||||
getLaunchURL,
|
||||
getPrintFileName,
|
||||
getPrintLocalDate,
|
||||
isFriendOnline,
|
||||
isRealInstance,
|
||||
parseLocation
|
||||
} from './index';
|
||||
|
||||
const utils = {
|
||||
getAvailablePlatforms,
|
||||
deleteVRChatCache,
|
||||
checkVRChatCache,
|
||||
getLaunchURL,
|
||||
extractFileId,
|
||||
extractFileVersion,
|
||||
extractVariantVersion,
|
||||
isRealInstance,
|
||||
displayLocation,
|
||||
parseLocation,
|
||||
getPrintFileName,
|
||||
getPrintLocalDate,
|
||||
isFriendOnline,
|
||||
getBundleLocation
|
||||
};
|
||||
|
||||
export { utils };
|
||||
148
src/shared/utils/avatar.js
Normal file
148
src/shared/utils/avatar.js
Normal file
@@ -0,0 +1,148 @@
|
||||
import { useAuthStore } from '../../stores';
|
||||
import { replaceBioSymbols } from './common';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} args
|
||||
* @param {Map} cachedAvatarNames
|
||||
* @returns
|
||||
*/
|
||||
function storeAvatarImage(args, cachedAvatarNames) {
|
||||
const refCreatedAt = args.json.versions[0];
|
||||
const fileCreatedAt = refCreatedAt.created_at;
|
||||
const fileId = args.params.fileId;
|
||||
let avatarName = '';
|
||||
const imageName = args.json.name;
|
||||
const avatarNameRegex = /Avatar - (.*) - Image -/gi.exec(imageName);
|
||||
if (avatarNameRegex) {
|
||||
avatarName = replaceBioSymbols(avatarNameRegex[1]);
|
||||
}
|
||||
const ownerId = args.json.ownerId;
|
||||
const avatarInfo = {
|
||||
ownerId,
|
||||
avatarName,
|
||||
fileCreatedAt
|
||||
};
|
||||
cachedAvatarNames.set(fileId, avatarInfo);
|
||||
return avatarInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} avatar
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function parseAvatarUrl(avatar) {
|
||||
const url = new URL(avatar);
|
||||
const urlPath = url.pathname;
|
||||
if (urlPath.substring(5, 13) === '/avatar/') {
|
||||
const avatarId = urlPath.substring(13);
|
||||
return avatarId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} unityPackages
|
||||
* @returns
|
||||
*/
|
||||
function getPlatformInfo(unityPackages) {
|
||||
let pc = {};
|
||||
let android = {};
|
||||
let ios = {};
|
||||
if (typeof unityPackages === 'object') {
|
||||
for (const unityPackage of unityPackages) {
|
||||
if (
|
||||
unityPackage.variant &&
|
||||
unityPackage.variant !== 'standard' &&
|
||||
unityPackage.variant !== 'security'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (unityPackage.platform === 'standalonewindows') {
|
||||
if (
|
||||
unityPackage.performanceRating === 'None' &&
|
||||
pc.performanceRating
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
pc = unityPackage;
|
||||
} else if (unityPackage.platform === 'android') {
|
||||
if (
|
||||
unityPackage.performanceRating === 'None' &&
|
||||
android.performanceRating
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
android = unityPackage;
|
||||
} else if (unityPackage.platform === 'ios') {
|
||||
if (
|
||||
unityPackage.performanceRating === 'None' &&
|
||||
ios.performanceRating
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
ios = unityPackage;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { pc, android, ios };
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} unitySortNumber
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function compareUnityVersion(unitySortNumber) {
|
||||
const authStore = useAuthStore();
|
||||
if (!authStore.cachedConfig.sdkUnityVersion) {
|
||||
console.error('No cachedConfig.sdkUnityVersion');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2022.3.6f1 2022 03 06 000
|
||||
// 2019.4.31f1 2019 04 31 000
|
||||
// 5.3.4p1 5 03 04 010
|
||||
// 2019.4.31f1c1 is a thing
|
||||
const array = authStore.cachedConfig.sdkUnityVersion.split('.');
|
||||
if (array.length < 3) {
|
||||
console.error('Invalid cachedConfig.sdkUnityVersion');
|
||||
return false;
|
||||
}
|
||||
let currentUnityVersion = array[0];
|
||||
currentUnityVersion += array[1].padStart(2, '0');
|
||||
const indexFirstLetter = array[2].search(/[a-zA-Z]/);
|
||||
if (indexFirstLetter > -1) {
|
||||
currentUnityVersion += array[2]
|
||||
.substr(0, indexFirstLetter)
|
||||
.padStart(2, '0');
|
||||
currentUnityVersion += '0';
|
||||
const letter = array[2].substr(indexFirstLetter, 1);
|
||||
if (letter === 'p') {
|
||||
currentUnityVersion += '1';
|
||||
} else {
|
||||
// f
|
||||
currentUnityVersion += '0';
|
||||
}
|
||||
currentUnityVersion += '0';
|
||||
} else {
|
||||
// just in case
|
||||
currentUnityVersion += '000';
|
||||
}
|
||||
// just in case
|
||||
currentUnityVersion = currentUnityVersion.replace(/\D/g, '');
|
||||
|
||||
if (parseInt(unitySortNumber, 10) <= parseInt(currentUnityVersion, 10)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export {
|
||||
storeAvatarImage,
|
||||
parseAvatarUrl,
|
||||
getPlatformInfo,
|
||||
compareUnityVersion
|
||||
};
|
||||
59
src/shared/utils/base/array.js
Normal file
59
src/shared/utils/base/array.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
*
|
||||
* @param {array} array
|
||||
* @param {*} item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function removeFromArray(array, item) {
|
||||
const { length } = array;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
if (array[i] === item) {
|
||||
array.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {array} a
|
||||
* @param {array} b
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function 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])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {array} array
|
||||
* @param {number} fromIndex
|
||||
* @param {number} toIndex
|
||||
* @returns {void}
|
||||
*/
|
||||
function 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);
|
||||
}
|
||||
|
||||
export { removeFromArray, arraysMatch, moveArrayItem };
|
||||
80
src/shared/utils/base/date.js
Normal file
80
src/shared/utils/base/date.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useAppearanceSettingsStore } from '../../../stores';
|
||||
|
||||
/**
|
||||
* @param {string} dateStr
|
||||
* @param {'long'|'short'} format
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatDateFilter(dateStr, format) {
|
||||
const appearance = useAppearanceSettingsStore();
|
||||
const {
|
||||
dtIsoFormat: isoFormat,
|
||||
dtHour12: hour12,
|
||||
currentCulture
|
||||
} = appearance;
|
||||
|
||||
if (!dateStr) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const dt = new Date(dateStr);
|
||||
if (isNaN(dt.getTime())) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
function padZero(num) {
|
||||
return String(num).padStart(2, '0');
|
||||
}
|
||||
|
||||
function toIsoLong(date) {
|
||||
const y = date.getFullYear();
|
||||
const m = padZero(date.getMonth() + 1);
|
||||
const d = padZero(date.getDate());
|
||||
const hh = padZero(date.getHours());
|
||||
const mm = padZero(date.getMinutes());
|
||||
const ss = padZero(date.getSeconds());
|
||||
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
|
||||
}
|
||||
|
||||
function toLocalShort(date) {
|
||||
return date
|
||||
.toLocaleDateString(isoFormat ? 'en-nz' : currentCulture, {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hourCycle: hour12 ? 'h12' : 'h23'
|
||||
})
|
||||
.replace(' AM', 'am')
|
||||
.replace(' PM', 'pm')
|
||||
.replace(',', '');
|
||||
}
|
||||
|
||||
if (isoFormat) {
|
||||
if (format === 'long') {
|
||||
return toIsoLong(dt);
|
||||
}
|
||||
if (format === 'short') {
|
||||
return toLocalShort(dt);
|
||||
}
|
||||
} else {
|
||||
if (format === 'long') {
|
||||
return dt.toLocaleDateString(currentCulture, {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
hourCycle: hour12 ? 'h12' : 'h23'
|
||||
});
|
||||
}
|
||||
if (format === 'short') {
|
||||
return toLocalShort(dt);
|
||||
}
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
|
||||
export { formatDateFilter };
|
||||
102
src/shared/utils/base/devtool.js
Normal file
102
src/shared/utils/base/devtool.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useAvatarStore, useWorldStore } from '../../../stores';
|
||||
import { compareUnityVersion } from '../avatar';
|
||||
import {
|
||||
extractFileId,
|
||||
extractFileVersion,
|
||||
extractVariantVersion
|
||||
} from '../common';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {Promise<string|null>}
|
||||
*/
|
||||
async function getBundleLocation(input) {
|
||||
const worldStore = useWorldStore();
|
||||
const avatarStore = useAvatarStore();
|
||||
let unityPackage;
|
||||
let unityPackages;
|
||||
let assetUrl = input;
|
||||
let variant = '';
|
||||
if (assetUrl) {
|
||||
// continue
|
||||
} else if (
|
||||
avatarStore.avatarDialog.visible &&
|
||||
avatarStore.avatarDialog.ref.unityPackages.length > 0
|
||||
) {
|
||||
unityPackages = avatarStore.avatarDialog.ref.unityPackages;
|
||||
for (let i = unityPackages.length - 1; i > -1; i--) {
|
||||
unityPackage = unityPackages[i];
|
||||
if (
|
||||
unityPackage.variant &&
|
||||
unityPackage.variant !== 'standard' &&
|
||||
unityPackage.variant !== 'security'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
compareUnityVersion(unityPackage.unitySortNumber)
|
||||
) {
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
if (unityPackage.variant !== 'standard') {
|
||||
variant = unityPackage.variant;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
avatarStore.avatarDialog.visible &&
|
||||
avatarStore.avatarDialog.ref.assetUrl
|
||||
) {
|
||||
assetUrl = avatarStore.avatarDialog.ref.assetUrl;
|
||||
} else if (
|
||||
worldStore.worldDialog.visible &&
|
||||
worldStore.worldDialog.ref.unityPackages.length > 0
|
||||
) {
|
||||
unityPackages = worldStore.worldDialog.ref.unityPackages;
|
||||
for (let i = unityPackages.length - 1; i > -1; i--) {
|
||||
unityPackage = unityPackages[i];
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
compareUnityVersion(unityPackage.unitySortNumber)
|
||||
) {
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
worldStore.worldDialog.visible &&
|
||||
worldStore.worldDialog.ref.assetUrl
|
||||
) {
|
||||
assetUrl = worldStore.worldDialog.ref.assetUrl;
|
||||
}
|
||||
if (!assetUrl) {
|
||||
return null;
|
||||
}
|
||||
const fileId = extractFileId(assetUrl);
|
||||
const fileVersion = parseInt(extractFileVersion(assetUrl), 10);
|
||||
const variantVersion = parseInt(extractVariantVersion(assetUrl), 10);
|
||||
const assetLocation = await AssetBundleManager.GetVRChatCacheFullLocation(
|
||||
fileId,
|
||||
fileVersion,
|
||||
variant,
|
||||
variantVersion
|
||||
);
|
||||
const cacheInfo = await AssetBundleManager.CheckVRChatCache(
|
||||
fileId,
|
||||
fileVersion,
|
||||
variant,
|
||||
variantVersion
|
||||
);
|
||||
let inCache = false;
|
||||
if (cacheInfo.Item1 > 0) {
|
||||
inCache = true;
|
||||
}
|
||||
console.log(`InCache: ${inCache}`);
|
||||
const fullAssetLocation = `${assetLocation}\\__data`;
|
||||
console.log(fullAssetLocation);
|
||||
return fullAssetLocation;
|
||||
}
|
||||
|
||||
export { getBundleLocation };
|
||||
97
src/shared/utils/base/format.js
Normal file
97
src/shared/utils/base/format.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import { escapeTag } from './string';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} sec
|
||||
* @param {boolean} isNeedSeconds
|
||||
* @returns {string}
|
||||
*/
|
||||
function timeToText(sec, isNeedSeconds = false) {
|
||||
let n = Number(sec);
|
||||
if (isNaN(n)) {
|
||||
return 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(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} duration
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatSeconds(duration) {
|
||||
const pad = function (num, size) {
|
||||
return `000${num}`.slice(size * -1);
|
||||
},
|
||||
time = parseFloat(duration).toFixed(3),
|
||||
hours = Math.floor(time / 60 / 60),
|
||||
minutes = Math.floor(time / 60) % 60,
|
||||
seconds = Math.floor(time - minutes * 60);
|
||||
let hoursOut = '';
|
||||
if (hours > '0') {
|
||||
hoursOut = `${pad(hours, 2)}:`;
|
||||
}
|
||||
return `${hoursOut + pad(minutes, 2)}:${pad(seconds, 2)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} duration
|
||||
* @returns {number}
|
||||
*/
|
||||
function convertYoutubeTime(duration) {
|
||||
let a = duration.match(/\d+/g);
|
||||
if (
|
||||
duration.indexOf('M') >= 0 &&
|
||||
duration.indexOf('H') === -1 &&
|
||||
duration.indexOf('S') === -1
|
||||
) {
|
||||
a = [0, a[0], 0];
|
||||
}
|
||||
if (duration.indexOf('H') >= 0 && duration.indexOf('M') === -1) {
|
||||
a = [a[0], 0, a[1]];
|
||||
}
|
||||
if (
|
||||
duration.indexOf('H') >= 0 &&
|
||||
duration.indexOf('M') === -1 &&
|
||||
duration.indexOf('S') === -1
|
||||
) {
|
||||
a = [a[0], 0, 0];
|
||||
}
|
||||
let length = 0;
|
||||
if (a.length === 3) {
|
||||
length += parseInt(a[0], 10) * 3600;
|
||||
length += parseInt(a[1], 10) * 60;
|
||||
length += parseInt(a[2], 10);
|
||||
}
|
||||
if (a.length === 2) {
|
||||
length += parseInt(a[0], 10) * 60;
|
||||
length += parseInt(a[1], 10);
|
||||
}
|
||||
if (a.length === 1) {
|
||||
length += parseInt(a[0], 10);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
export { timeToText, formatSeconds, convertYoutubeTime };
|
||||
105
src/shared/utils/base/string.js
Normal file
105
src/shared/utils/base/string.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
*
|
||||
* @param {string} tag
|
||||
* @returns {string}
|
||||
*/
|
||||
function escapeTag(tag) {
|
||||
const s = String(tag);
|
||||
return s.replace(/["&'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} obj
|
||||
* @returns {object}
|
||||
*/
|
||||
function escapeTagRecursive(obj) {
|
||||
if (typeof obj === 'string') {
|
||||
return escapeTag(obj);
|
||||
}
|
||||
if (typeof obj === 'object') {
|
||||
for (const key in obj) {
|
||||
obj[key] = escapeTagRecursive(obj[key]);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
function textToHex(text) {
|
||||
const s = String(text);
|
||||
return s
|
||||
.split('')
|
||||
.map((c) => c.charCodeAt(0).toString(16))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} num
|
||||
* @returns {string}
|
||||
*/
|
||||
function commaNumber(num) {
|
||||
if (!num) {
|
||||
return '0';
|
||||
}
|
||||
const s = String(Number(num));
|
||||
return s.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {string} search
|
||||
* @param {object} comparer
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function 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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
function changeLogRemoveLinks(text) {
|
||||
return text.replace(/([^!])\[[^\]]+\]\([^)]+\)/g, '$1');
|
||||
}
|
||||
|
||||
export {
|
||||
escapeTag,
|
||||
escapeTagRecursive,
|
||||
textToHex,
|
||||
commaNumber,
|
||||
localeIncludes,
|
||||
changeLogRemoveLinks
|
||||
};
|
||||
284
src/shared/utils/base/ui.js
Normal file
284
src/shared/utils/base/ui.js
Normal file
@@ -0,0 +1,284 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useAppearanceSettingsStore } from '../../../stores';
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function systemIsDarkMode() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {boolean}isDark
|
||||
*/
|
||||
function changeAppDarkStyle(isDark) {
|
||||
if (isDark) {
|
||||
AppApi.ChangeTheme(1);
|
||||
} else {
|
||||
AppApi.ChangeTheme(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} themeMode
|
||||
* @returns
|
||||
*/
|
||||
function changeAppThemeStyle(themeMode) {
|
||||
const themeStyle = {};
|
||||
switch (themeMode) {
|
||||
case 'light':
|
||||
themeStyle.href = '';
|
||||
break;
|
||||
case 'dark':
|
||||
themeStyle.href = '';
|
||||
break;
|
||||
case 'darkvanillaold':
|
||||
themeStyle.href = 'theme.darkvanillaold.css';
|
||||
break;
|
||||
case 'darkvanilla':
|
||||
themeStyle.href = 'theme.darkvanilla.css';
|
||||
break;
|
||||
case 'pink':
|
||||
themeStyle.href = 'theme.pink.css';
|
||||
break;
|
||||
case 'material3':
|
||||
themeStyle.href = 'theme.material3.css';
|
||||
break;
|
||||
case 'system':
|
||||
themeStyle.href = '';
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* prevents flickering
|
||||
* giving absolute paths does prevent flickering
|
||||
* when switching from another dark theme to 'dark' theme
|
||||
* <del>works on my machine</del>
|
||||
*/
|
||||
let filePathPrefix = 'file://vrcx/';
|
||||
if (LINUX) {
|
||||
filePathPrefix = './';
|
||||
}
|
||||
|
||||
let $appThemeStyle = document.getElementById('app-theme-style');
|
||||
if (!$appThemeStyle) {
|
||||
$appThemeStyle = document.createElement('link');
|
||||
$appThemeStyle.setAttribute('id', 'app-theme-style');
|
||||
$appThemeStyle.rel = 'stylesheet';
|
||||
document.head.appendChild($appThemeStyle);
|
||||
}
|
||||
$appThemeStyle.href = themeStyle.href
|
||||
? `${filePathPrefix}${themeStyle.href}`
|
||||
: '';
|
||||
|
||||
let $appThemeDarkStyle = document.getElementById('app-theme-dark-style');
|
||||
|
||||
const darkThemeCssPath = `${filePathPrefix}theme.dark.css`;
|
||||
|
||||
if (!$appThemeDarkStyle && themeMode !== 'light') {
|
||||
if (themeMode === 'system' && !systemIsDarkMode()) {
|
||||
return;
|
||||
}
|
||||
$appThemeDarkStyle = document.createElement('link');
|
||||
$appThemeDarkStyle.setAttribute('id', 'app-theme-dark-style');
|
||||
$appThemeDarkStyle.rel = 'stylesheet';
|
||||
$appThemeDarkStyle.href = darkThemeCssPath;
|
||||
document.head.insertBefore($appThemeDarkStyle, $appThemeStyle);
|
||||
} else {
|
||||
if (themeMode === 'system' && systemIsDarkMode()) {
|
||||
if ($appThemeDarkStyle.href === darkThemeCssPath) {
|
||||
return;
|
||||
}
|
||||
$appThemeDarkStyle.href = darkThemeCssPath;
|
||||
} else if (themeMode !== 'light' && themeMode !== 'system') {
|
||||
if ($appThemeDarkStyle.href === darkThemeCssPath) {
|
||||
return;
|
||||
}
|
||||
$appThemeDarkStyle.href = darkThemeCssPath;
|
||||
} else {
|
||||
$appThemeDarkStyle && $appThemeDarkStyle.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CJK character in Japanese, Korean, Chinese are different
|
||||
* so change font-family order when users change language to display CJK character correctly
|
||||
* @param {string} lang
|
||||
*/
|
||||
function changeCJKFontsOrder(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}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} trustColor
|
||||
*/
|
||||
function updateTrustColorClasses(trustColor) {
|
||||
if (document.getElementById('trustColor') !== null) {
|
||||
document.getElementById('trustColor').outerHTML = '';
|
||||
}
|
||||
const style = document.createElement('style');
|
||||
style.id = 'trustColor';
|
||||
style.type = 'text/css';
|
||||
let newCSS = '';
|
||||
for (const rank in trustColor) {
|
||||
newCSS += `.x-tag-${rank} { color: ${trustColor[rank]} !important; border-color: ${trustColor[rank]} !important; } `;
|
||||
}
|
||||
style.innerHTML = newCSS;
|
||||
document.getElementsByTagName('head')[0].appendChild(style);
|
||||
}
|
||||
|
||||
function refreshCustomCss() {
|
||||
if (document.contains(document.getElementById('app-custom-style'))) {
|
||||
document.getElementById('app-custom-style').remove();
|
||||
}
|
||||
AppApi.CustomCssPath().then((customCss) => {
|
||||
const head = document.head;
|
||||
if (customCss) {
|
||||
const $appCustomStyle = document.createElement('link');
|
||||
$appCustomStyle.setAttribute('id', 'app-custom-style');
|
||||
$appCustomStyle.rel = 'stylesheet';
|
||||
$appCustomStyle.href = `file://${customCss}?_=${Date.now()}`;
|
||||
head.appendChild($appCustomStyle);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refreshCustomScript() {
|
||||
if (document.contains(document.getElementById('app-custom-script'))) {
|
||||
document.getElementById('app-custom-script').remove();
|
||||
}
|
||||
AppApi.CustomScriptPath().then((customScript) => {
|
||||
const head = document.head;
|
||||
if (customScript) {
|
||||
const $appCustomScript = document.createElement('script');
|
||||
$appCustomScript.setAttribute('id', 'app-custom-script');
|
||||
$appCustomScript.src = `file://${customScript}?_=${Date.now()}`;
|
||||
head.appendChild($appCustomScript);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} hue
|
||||
* @returns {string}
|
||||
*/
|
||||
function HueToHex(hue) {
|
||||
const appSettingsStore = useAppearanceSettingsStore();
|
||||
const { isDarkMode } = storeToRefs(appSettingsStore);
|
||||
// this.HSVtoRGB(hue / 65535, .8, .8);
|
||||
if (isDarkMode.value) {
|
||||
return HSVtoRGB(hue / 65535, 0.6, 1);
|
||||
}
|
||||
return HSVtoRGB(hue / 65535, 1, 0.7);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} h
|
||||
* @param {number} s
|
||||
* @param {number} v
|
||||
* @returns {string}
|
||||
*/
|
||||
function HSVtoRGB(h, s, v) {
|
||||
let r = 0;
|
||||
let g = 0;
|
||||
let b = 0;
|
||||
if (arguments.length === 1) {
|
||||
s = h.s;
|
||||
v = h.v;
|
||||
h = h.h;
|
||||
}
|
||||
const i = Math.floor(h * 6);
|
||||
const f = h * 6 - i;
|
||||
const p = v * (1 - s);
|
||||
const q = v * (1 - f * s);
|
||||
const t = v * (1 - (1 - f) * s);
|
||||
switch (i % 6) {
|
||||
case 0:
|
||||
r = v;
|
||||
g = t;
|
||||
b = p;
|
||||
break;
|
||||
case 1:
|
||||
r = q;
|
||||
g = v;
|
||||
b = p;
|
||||
break;
|
||||
case 2:
|
||||
r = p;
|
||||
g = v;
|
||||
b = t;
|
||||
break;
|
||||
case 3:
|
||||
r = p;
|
||||
g = q;
|
||||
b = v;
|
||||
break;
|
||||
case 4:
|
||||
r = t;
|
||||
g = p;
|
||||
b = v;
|
||||
break;
|
||||
case 5:
|
||||
r = v;
|
||||
g = p;
|
||||
b = q;
|
||||
break;
|
||||
}
|
||||
const red = Math.round(r * 255);
|
||||
const green = Math.round(g * 255);
|
||||
const blue = Math.round(b * 255);
|
||||
const decColor = 0x1000000 + blue + 0x100 * green + 0x10000 * red;
|
||||
return `#${decColor.toString(16).substr(1)}`;
|
||||
}
|
||||
|
||||
function adjustDialogZ(el) {
|
||||
let z = 0;
|
||||
document.querySelectorAll('.v-modal,.el-dialog__wrapper').forEach((v) => {
|
||||
const _z = Number(v.style.zIndex) || 0;
|
||||
if (_z && _z > z && v !== el) {
|
||||
z = _z;
|
||||
}
|
||||
});
|
||||
if (z) {
|
||||
el.style.zIndex = z + 1;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
systemIsDarkMode,
|
||||
changeAppDarkStyle,
|
||||
changeAppThemeStyle,
|
||||
changeCJKFontsOrder,
|
||||
updateTrustColorClasses,
|
||||
refreshCustomCss,
|
||||
refreshCustomScript,
|
||||
HueToHex,
|
||||
HSVtoRGB,
|
||||
adjustDialogZ
|
||||
};
|
||||
13
src/shared/utils/chart.js
Normal file
13
src/shared/utils/chart.js
Normal file
@@ -0,0 +1,13 @@
|
||||
let echarts = null;
|
||||
|
||||
// lazy load echarts
|
||||
function loadEcharts() {
|
||||
if (echarts) {
|
||||
return Promise.resolve(echarts);
|
||||
}
|
||||
return import('echarts').then((module) => {
|
||||
echarts = module;
|
||||
return echarts;
|
||||
});
|
||||
}
|
||||
export { loadEcharts };
|
||||
541
src/shared/utils/common.js
Normal file
541
src/shared/utils/common.js
Normal file
@@ -0,0 +1,541 @@
|
||||
import Noty from 'noty';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { miscRequest } from '../../api';
|
||||
import { $app } from '../../app';
|
||||
import {
|
||||
useAvatarStore,
|
||||
useInstanceStore,
|
||||
useWorldStore,
|
||||
useSearchStore
|
||||
} from '../../stores';
|
||||
import { compareUnityVersion } from './avatar';
|
||||
import { escapeTag } from './base/string';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} unityPackages
|
||||
* @returns
|
||||
*/
|
||||
function getAvailablePlatforms(unityPackages) {
|
||||
let isPC = false;
|
||||
let isQuest = false;
|
||||
let isIos = false;
|
||||
if (typeof unityPackages === 'object') {
|
||||
for (const unityPackage of unityPackages) {
|
||||
if (
|
||||
unityPackage.variant &&
|
||||
unityPackage.variant !== 'standard' &&
|
||||
unityPackage.variant !== 'security'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (unityPackage.platform === 'standalonewindows') {
|
||||
isPC = true;
|
||||
} else if (unityPackage.platform === 'android') {
|
||||
isQuest = true;
|
||||
} else if (unityPackage.platform === 'ios') {
|
||||
isIos = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { isPC, isQuest, isIos };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} fileName
|
||||
* @param {*} data
|
||||
*/
|
||||
function downloadAndSaveJson(fileName, data) {
|
||||
if (!fileName || !data) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute(
|
||||
'href',
|
||||
`data:application/json;charset=utf-8,${encodeURIComponent(
|
||||
JSON.stringify(data, null, 2)
|
||||
)}`
|
||||
);
|
||||
link.setAttribute('download', `${fileName}.json`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} catch {
|
||||
new Noty({
|
||||
type: 'error',
|
||||
text: escapeTag('Failed to download JSON.')
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteVRChatCache(ref) {
|
||||
let assetUrl = '';
|
||||
let variant = '';
|
||||
for (let i = ref.unityPackages.length - 1; i > -1; i--) {
|
||||
const unityPackage = ref.unityPackages[i];
|
||||
if (
|
||||
unityPackage.variant &&
|
||||
unityPackage.variant !== 'standard' &&
|
||||
unityPackage.variant !== 'security'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
compareUnityVersion(unityPackage.unitySortNumber)
|
||||
) {
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
if (unityPackage.variant !== 'standard') {
|
||||
variant = unityPackage.variant;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
const id = extractFileId(assetUrl);
|
||||
const version = parseInt(extractFileVersion(assetUrl), 10);
|
||||
const variantVersion = parseInt(extractVariantVersion(assetUrl), 10);
|
||||
await AssetBundleManager.DeleteCache(id, version, variant, variantVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} ref
|
||||
* @returns
|
||||
*/
|
||||
async function checkVRChatCache(ref) {
|
||||
if (!ref.unityPackages) {
|
||||
return { Item1: -1, Item2: false, Item3: '' };
|
||||
}
|
||||
let assetUrl = '';
|
||||
let variant = '';
|
||||
for (let i = ref.unityPackages.length - 1; i > -1; i--) {
|
||||
const unityPackage = ref.unityPackages[i];
|
||||
if (unityPackage.variant && unityPackage.variant !== 'security') {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
compareUnityVersion(unityPackage.unitySortNumber)
|
||||
) {
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
if (unityPackage.variant !== 'standard') {
|
||||
variant = unityPackage.variant;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!assetUrl) {
|
||||
assetUrl = ref.assetUrl;
|
||||
}
|
||||
const id = extractFileId(assetUrl);
|
||||
const version = parseInt(extractFileVersion(assetUrl), 10);
|
||||
const variantVersion = parseInt(extractVariantVersion(assetUrl), 10);
|
||||
if (!id || !version) {
|
||||
return { Item1: -1, Item2: false, Item3: '' };
|
||||
}
|
||||
|
||||
return AssetBundleManager.CheckVRChatCache(
|
||||
id,
|
||||
version,
|
||||
variant,
|
||||
variantVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @param {string} message
|
||||
*/
|
||||
function copyToClipboard(text, message = 'Copied successfully!') {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
$app.$message({
|
||||
message: message,
|
||||
type: 'success'
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Copy failed:', err);
|
||||
$app.$message.error('Copy failed!');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} resource
|
||||
* @returns {string}
|
||||
*/
|
||||
function getFaviconUrl(resource) {
|
||||
if (!resource) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const url = new URL(resource);
|
||||
return `https://icons.duckduckgo.com/ip2/${url.host}.ico`;
|
||||
} catch (err) {
|
||||
console.error('Invalid URL:', resource, err);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {number} resolution
|
||||
* @returns {string}
|
||||
*/
|
||||
function convertFileUrlToImageUrl(url, resolution = 128) {
|
||||
if (!url) {
|
||||
return '';
|
||||
}
|
||||
/**
|
||||
* possible patterns?
|
||||
* /file/file_fileId/version
|
||||
* /file/file_fileId/version/
|
||||
* /file/file_fileId/version/file
|
||||
* /file/file_fileId/version/file/
|
||||
*/
|
||||
const pattern = /file\/file_([a-f0-9-]+)\/(\d+)(\/file)?\/?$/;
|
||||
const match = url.match(pattern);
|
||||
|
||||
if (match) {
|
||||
const fileId = match[1];
|
||||
const version = match[2];
|
||||
return `https://api.vrchat.cloud/api/1/image/file_${fileId}/${version}/${resolution}`;
|
||||
}
|
||||
// no match return origin url
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
function replaceVrcPackageUrl(url) {
|
||||
if (!url) {
|
||||
return '';
|
||||
}
|
||||
return url.replace('https://api.vrchat.cloud/', 'https://vrchat.com/');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} s
|
||||
* @returns {string}
|
||||
*/
|
||||
function extractFileId(s) {
|
||||
const match = String(s).match(/file_[0-9A-Za-z-]+/);
|
||||
return match ? match[0] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} s
|
||||
* @returns {string}
|
||||
*/
|
||||
function extractFileVersion(s) {
|
||||
const match = /(?:\/file_[0-9A-Za-z-]+\/)([0-9]+)/gi.exec(s);
|
||||
return match ? match[1] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
function extractVariantVersion(url) {
|
||||
if (!url) {
|
||||
return '0';
|
||||
}
|
||||
try {
|
||||
const params = new URLSearchParams(new URL(url).search);
|
||||
const version = params.get('v');
|
||||
if (version) {
|
||||
return version;
|
||||
}
|
||||
return '0';
|
||||
} catch {
|
||||
return '0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} json
|
||||
* @returns {Array}
|
||||
*/
|
||||
function buildTreeData(json) {
|
||||
const node = [];
|
||||
for (const key in json) {
|
||||
if (key[0] === '$') {
|
||||
continue;
|
||||
}
|
||||
const 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: buildTreeData(val),
|
||||
key: idx
|
||||
};
|
||||
}
|
||||
return {
|
||||
key: idx,
|
||||
value: val
|
||||
};
|
||||
}),
|
||||
key
|
||||
});
|
||||
} else if (value === Object(value)) {
|
||||
node.push({
|
||||
children: buildTreeData(value),
|
||||
key
|
||||
});
|
||||
} else {
|
||||
node.push({
|
||||
key,
|
||||
value: String(value)
|
||||
});
|
||||
}
|
||||
}
|
||||
node.sort(function (a, b) {
|
||||
const A = String(a.key).toUpperCase();
|
||||
const B = String(b.key).toUpperCase();
|
||||
if (A < B) {
|
||||
return -1;
|
||||
}
|
||||
if (A > B) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
function replaceBioSymbols(text) {
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
const symbolList = {
|
||||
'@': '@',
|
||||
'#': '#',
|
||||
$: '$',
|
||||
'%': '%',
|
||||
'&': '&',
|
||||
'=': '=',
|
||||
'+': '+',
|
||||
'/': '⁄',
|
||||
'\\': '\',
|
||||
';': ';',
|
||||
':': '˸',
|
||||
',': '‚',
|
||||
'?': '?',
|
||||
'!': 'ǃ',
|
||||
'"': '"',
|
||||
'<': '≺',
|
||||
'>': '≻',
|
||||
'.': '․',
|
||||
'^': '^',
|
||||
'{': '{',
|
||||
'}': '}',
|
||||
'[': '[',
|
||||
']': ']',
|
||||
'(': '(',
|
||||
')': ')',
|
||||
'|': '|',
|
||||
'*': '∗'
|
||||
};
|
||||
let newText = text;
|
||||
for (const key in symbolList) {
|
||||
const regex = new RegExp(symbolList[key], 'g');
|
||||
newText = newText.replace(regex, key);
|
||||
}
|
||||
return newText.replace(/ {1,}/g, ' ').trimRight();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} link
|
||||
*/
|
||||
function openExternalLink(link) {
|
||||
const searchStore = useSearchStore();
|
||||
if (searchStore.directAccessParse(link)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$app.$confirm(`${link}`, 'Open External Link', {
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: 'Open',
|
||||
cancelButtonText: 'Copy',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
AppApi.OpenLink(link);
|
||||
} else if (action === 'cancel') {
|
||||
copyLink(link);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
*/
|
||||
function copyLink(text) {
|
||||
$app.$message({
|
||||
message: 'Link copied to clipboard',
|
||||
type: 'success'
|
||||
});
|
||||
copyToClipboard(text);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} ref
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function getBundleDateSize(ref) {
|
||||
const avatarStore = useAvatarStore();
|
||||
const { avatarDialog } = storeToRefs(avatarStore);
|
||||
const worldStore = useWorldStore();
|
||||
const { worldDialog } = storeToRefs(worldStore);
|
||||
const instanceStore = useInstanceStore();
|
||||
const { currentInstanceWorld, currentInstanceLocation } =
|
||||
storeToRefs(instanceStore);
|
||||
const bundleSizes = [];
|
||||
for (let i = ref.unityPackages.length - 1; i > -1; i--) {
|
||||
const unityPackage = ref.unityPackages[i];
|
||||
if (
|
||||
unityPackage.variant &&
|
||||
unityPackage.variant !== 'standard' &&
|
||||
unityPackage.variant !== 'security'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (!compareUnityVersion(unityPackage.unitySortNumber)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const platform = unityPackage.platform;
|
||||
if (bundleSizes[platform]) {
|
||||
continue;
|
||||
}
|
||||
const assetUrl = unityPackage.assetUrl;
|
||||
const fileId = extractFileId(assetUrl);
|
||||
const fileVersion = parseInt(extractFileVersion(assetUrl), 10);
|
||||
if (!fileId) {
|
||||
continue;
|
||||
}
|
||||
const args = await miscRequest.getBundles(fileId);
|
||||
if (!args?.json?.versions) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let { versions } = args.json;
|
||||
for (let j = versions.length - 1; j > -1; j--) {
|
||||
const version = versions[j];
|
||||
if (version.version === fileVersion) {
|
||||
const createdAt = version.created_at;
|
||||
const fileSize = `${(
|
||||
version.file.sizeInBytes / 1048576
|
||||
).toFixed(2)} MB`;
|
||||
bundleSizes[platform] = {
|
||||
createdAt,
|
||||
fileSize
|
||||
};
|
||||
|
||||
// update avatar dialog
|
||||
if (avatarDialog.value.id === ref.id) {
|
||||
avatarDialog.value.bundleSizes[platform] =
|
||||
bundleSizes[platform];
|
||||
if (avatarDialog.value.lastUpdated < version.created_at) {
|
||||
avatarDialog.value.lastUpdated = version.created_at;
|
||||
}
|
||||
}
|
||||
// update world dialog
|
||||
if (worldDialog.value.id === ref.id) {
|
||||
worldDialog.value.bundleSizes[platform] =
|
||||
bundleSizes[platform];
|
||||
if (worldDialog.value.lastUpdated < version.created_at) {
|
||||
worldDialog.value.lastUpdated = version.created_at;
|
||||
}
|
||||
}
|
||||
// update player list
|
||||
if (currentInstanceLocation.value.worldId === ref.id) {
|
||||
currentInstanceWorld.value.bundleSizes[platform] =
|
||||
bundleSizes[platform];
|
||||
|
||||
if (
|
||||
currentInstanceWorld.value.lastUpdated <
|
||||
version.created_at
|
||||
) {
|
||||
currentInstanceWorld.value.lastUpdated =
|
||||
version.created_at;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bundleSizes;
|
||||
}
|
||||
|
||||
// #region | App: Random unsorted app methods, data structs, API functions, and an API feedback/file analysis event
|
||||
|
||||
function openFolderGeneric(path) {
|
||||
AppApi.OpenFolderAndSelectItem(path, true);
|
||||
}
|
||||
|
||||
function debounce(func, delay) {
|
||||
let timer = null;
|
||||
return function (...args) {
|
||||
const context = this;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func.apply(context, args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
getAvailablePlatforms,
|
||||
downloadAndSaveJson,
|
||||
deleteVRChatCache,
|
||||
checkVRChatCache,
|
||||
copyToClipboard,
|
||||
getFaviconUrl,
|
||||
convertFileUrlToImageUrl,
|
||||
replaceVrcPackageUrl,
|
||||
extractFileId,
|
||||
extractFileVersion,
|
||||
extractVariantVersion,
|
||||
buildTreeData,
|
||||
replaceBioSymbols,
|
||||
openExternalLink,
|
||||
copyLink,
|
||||
getBundleDateSize,
|
||||
openFolderGeneric,
|
||||
debounce
|
||||
};
|
||||
262
src/shared/utils/compare.js
Normal file
262
src/shared/utils/compare.js
Normal file
@@ -0,0 +1,262 @@
|
||||
import { sortStatus } from './friend';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByName(a, b) {
|
||||
if (typeof a.name !== 'string' || typeof b.name !== 'string') {
|
||||
return 0;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* descending
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByCreatedAt(a, b) {
|
||||
if (typeof a.created_at !== 'string' || typeof b.created_at !== 'string') {
|
||||
return 0;
|
||||
}
|
||||
const A = a.created_at.toUpperCase();
|
||||
const B = b.created_at.toUpperCase();
|
||||
if (A < B) {
|
||||
return 1;
|
||||
}
|
||||
if (A > B) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ascending
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByCreatedAtAscending(a, b) {
|
||||
const A = a.created_at;
|
||||
const B = b.created_at;
|
||||
if (A < B) {
|
||||
return -1;
|
||||
}
|
||||
if (A > B) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* descending
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByUpdatedAt(a, b) {
|
||||
if (typeof a.updated_at !== 'string' || typeof b.updated_at !== 'string') {
|
||||
return 0;
|
||||
}
|
||||
const A = a.updated_at.toUpperCase();
|
||||
const B = b.updated_at.toUpperCase();
|
||||
if (A < B) {
|
||||
return 1;
|
||||
}
|
||||
if (A > B) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ascending
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByDisplayName(a, b) {
|
||||
if (
|
||||
typeof a.displayName !== 'string' ||
|
||||
typeof b.displayName !== 'string'
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
return a.displayName.localeCompare(b.displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByMemberCount(a, b) {
|
||||
if (
|
||||
typeof a.memberCount !== 'number' ||
|
||||
typeof b.memberCount !== 'number'
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
return a.memberCount - b.memberCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* private
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByPrivate(a, b) {
|
||||
if (typeof a.ref === 'undefined' || typeof b.ref === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
if (a.ref.location === 'private' && b.ref.location === 'private') {
|
||||
return 0;
|
||||
} else if (a.ref.location === 'private') {
|
||||
return 1;
|
||||
} else if (b.ref.location === 'private') {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByStatus(a, b) {
|
||||
if (typeof a.ref === 'undefined' || typeof b.ref === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
if (a.ref.status === b.ref.status) {
|
||||
return 0;
|
||||
}
|
||||
if (a.ref.state === 'offline') {
|
||||
return 1;
|
||||
}
|
||||
return sortStatus(a.ref.status, b.ref.status);
|
||||
}
|
||||
|
||||
/**
|
||||
* last active
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByLastActive(a, b) {
|
||||
if (a.state === 'online' && b.state === 'online') {
|
||||
if (
|
||||
a.ref?.$online_for &&
|
||||
b.ref?.$online_for &&
|
||||
a.ref.$online_for === b.ref.$online_for
|
||||
) {
|
||||
compareByActivityField(a, b, 'last_login');
|
||||
}
|
||||
return compareByActivityField(a, b, '$online_for');
|
||||
}
|
||||
|
||||
return compareByActivityField(a, b, 'last_activity');
|
||||
}
|
||||
|
||||
/**
|
||||
* last seen
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByLastSeen(a, b) {
|
||||
return compareByActivityField(a, b, '$lastSeen');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @param {string} field
|
||||
* @returns
|
||||
*/
|
||||
function compareByActivityField(a, b, field) {
|
||||
if (typeof a.ref === 'undefined' || typeof b.ref === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// When the field is just and empty string, it means they've been
|
||||
// in whatever active state for the longest
|
||||
if (
|
||||
a.ref[field] < b.ref[field] ||
|
||||
(a.ref[field] !== '' && b.ref[field] === '')
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
if (
|
||||
a.ref[field] > b.ref[field] ||
|
||||
(a.ref[field] === '' && b.ref[field] !== '')
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* location at
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByLocationAt(a, b) {
|
||||
if (a.location === 'traveling' && b.location === 'traveling') {
|
||||
return 0;
|
||||
}
|
||||
if (a.location === 'traveling') {
|
||||
return 1;
|
||||
}
|
||||
if (b.location === 'traveling') {
|
||||
return -1;
|
||||
}
|
||||
if (a.$location_at < b.$location_at) {
|
||||
return -1;
|
||||
}
|
||||
if (a.$location_at > b.$location_at) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* location at but for the sidebar
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns
|
||||
*/
|
||||
function compareByLocation(a, b) {
|
||||
if (typeof a.ref === 'undefined' || typeof b.ref === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
if (a.state !== 'online' || b.state !== 'online') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.ref.location.localeCompare(b.ref.location);
|
||||
}
|
||||
|
||||
export {
|
||||
compareByName,
|
||||
compareByCreatedAt,
|
||||
compareByCreatedAtAscending,
|
||||
compareByUpdatedAt,
|
||||
compareByDisplayName,
|
||||
compareByMemberCount,
|
||||
compareByPrivate,
|
||||
compareByStatus,
|
||||
compareByLastActive,
|
||||
compareByLastSeen,
|
||||
compareByLocationAt,
|
||||
compareByLocation
|
||||
};
|
||||
147
src/shared/utils/friend.js
Normal file
147
src/shared/utils/friend.js
Normal file
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
compareByLastActive,
|
||||
compareByLastSeen,
|
||||
compareByLocation,
|
||||
compareByLocationAt,
|
||||
compareByName,
|
||||
compareByPrivate,
|
||||
compareByStatus
|
||||
} from './compare';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string[]} sortMethods
|
||||
* @returns
|
||||
*/
|
||||
function getFriendsSortFunction(sortMethods) {
|
||||
const sorts = [];
|
||||
for (const sortMethod of sortMethods) {
|
||||
switch (sortMethod) {
|
||||
case 'Sort Alphabetically':
|
||||
sorts.push(compareByName);
|
||||
break;
|
||||
case 'Sort Private to Bottom':
|
||||
sorts.push(compareByPrivate);
|
||||
break;
|
||||
case 'Sort by Status':
|
||||
sorts.push(compareByStatus);
|
||||
break;
|
||||
case 'Sort by Last Active':
|
||||
sorts.push(compareByLastActive);
|
||||
break;
|
||||
case 'Sort by Last Seen':
|
||||
sorts.push(compareByLastSeen);
|
||||
break;
|
||||
case 'Sort by Time in Instance':
|
||||
sorts.push((a, b) => {
|
||||
if (
|
||||
typeof a.ref === 'undefined' ||
|
||||
typeof b.ref === 'undefined'
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
if (a.state !== 'online' || b.state !== 'online') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return compareByLocationAt(b.ref, a.ref);
|
||||
});
|
||||
break;
|
||||
case 'Sort by Location':
|
||||
sorts.push(compareByLocation);
|
||||
break;
|
||||
case 'None':
|
||||
sorts.push(() => 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns {number}
|
||||
*/
|
||||
return (a, b) => {
|
||||
let res;
|
||||
for (const sort of sorts) {
|
||||
res = sort(a, b);
|
||||
if (res !== 0) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} a
|
||||
* @param {string} b
|
||||
* @returns {number}
|
||||
*/
|
||||
function sortStatus(a, b) {
|
||||
switch (b) {
|
||||
case 'join me':
|
||||
switch (a) {
|
||||
case 'active':
|
||||
return 1;
|
||||
case 'ask me':
|
||||
return 1;
|
||||
case 'busy':
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'active':
|
||||
switch (a) {
|
||||
case 'join me':
|
||||
return -1;
|
||||
case 'ask me':
|
||||
return 1;
|
||||
case 'busy':
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'ask me':
|
||||
switch (a) {
|
||||
case 'join me':
|
||||
return -1;
|
||||
case 'active':
|
||||
return -1;
|
||||
case 'busy':
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'busy':
|
||||
switch (a) {
|
||||
case 'join me':
|
||||
return -1;
|
||||
case 'active':
|
||||
return -1;
|
||||
case 'ask me':
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} friend
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isFriendOnline(friend) {
|
||||
if (typeof friend === 'undefined' || typeof friend.ref === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
if (friend.state === 'online') {
|
||||
return true;
|
||||
}
|
||||
if (friend.state !== 'online' && friend.ref.location !== 'private') {
|
||||
// wat
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export { getFriendsSortFunction, sortStatus, isFriendOnline };
|
||||
57
src/shared/utils/gallery.js
Normal file
57
src/shared/utils/gallery.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
*
|
||||
* @param {object} print
|
||||
* @returns
|
||||
*/
|
||||
function getPrintFileName(print) {
|
||||
const authorName = print.authorName;
|
||||
// fileDate format: 2024-11-03_16-14-25.757
|
||||
const createdAt = getPrintLocalDate(print);
|
||||
const fileNameDate = createdAt
|
||||
.toISOString()
|
||||
.replace(/:/g, '-')
|
||||
.replace(/T/g, '_')
|
||||
.replace(/Z/g, '');
|
||||
const fileName = `${authorName}_${fileNameDate}_${print.id}.png`;
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} print
|
||||
* @returns
|
||||
*/
|
||||
function getPrintLocalDate(print) {
|
||||
if (print.createdAt) {
|
||||
const createdAt = new Date(print.createdAt);
|
||||
// cursed convert to local time
|
||||
createdAt.setMinutes(
|
||||
createdAt.getMinutes() - createdAt.getTimezoneOffset()
|
||||
);
|
||||
return createdAt;
|
||||
}
|
||||
if (print.timestamp) {
|
||||
return new Date(print.timestamp);
|
||||
}
|
||||
|
||||
const createdAt = new Date();
|
||||
// cursed convert to local time
|
||||
createdAt.setMinutes(
|
||||
createdAt.getMinutes() - createdAt.getTimezoneOffset()
|
||||
);
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} emoji
|
||||
*/
|
||||
function getEmojiFileName(emoji) {
|
||||
if (emoji.frames) {
|
||||
const loopStyle = emoji.loopStyle || 'linear';
|
||||
return `${emoji.name}_${emoji.animationStyle}animationStyle_${emoji.frames}frames_${emoji.framesOverTime}fps_${loopStyle}loopStyle.png`;
|
||||
} else {
|
||||
return `${emoji.name}_${emoji.animationStyle}animationStyle.png`;
|
||||
}
|
||||
}
|
||||
|
||||
export { getPrintLocalDate, getPrintFileName, getEmojiFileName };
|
||||
52
src/shared/utils/group.js
Normal file
52
src/shared/utils/group.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import groupRequest from '../../api/group';
|
||||
import { parseLocation } from './location';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} ref
|
||||
* @param {string} permission
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasGroupPermission(ref, permission) {
|
||||
if (
|
||||
ref &&
|
||||
ref.myMember &&
|
||||
ref.myMember.permissions &&
|
||||
(ref.myMember.permissions.includes('*') ||
|
||||
ref.myMember.permissions.includes(permission))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} data
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function getGroupName(data) {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
let groupName = '';
|
||||
let groupId = data;
|
||||
if (!data.startsWith('grp_')) {
|
||||
const L = parseLocation(data);
|
||||
groupId = L.groupId;
|
||||
if (!L.groupId) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
try {
|
||||
const args = await groupRequest.getCachedGroup({
|
||||
groupId
|
||||
});
|
||||
groupName = args.ref.name;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return groupName;
|
||||
}
|
||||
|
||||
export { hasGroupPermission, getGroupName };
|
||||
20
src/shared/utils/index.js
Normal file
20
src/shared/utils/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
export * from './base/array';
|
||||
export * from './base/devtool';
|
||||
export * from './base/format';
|
||||
export * from './base/date';
|
||||
export * from './base/string';
|
||||
export * from './base/ui';
|
||||
export * from './avatar';
|
||||
export * from './chart';
|
||||
export * from './common';
|
||||
export * from './compare';
|
||||
export * from './friend';
|
||||
export * from './group';
|
||||
export * from './instance';
|
||||
export * from './setting';
|
||||
export * from './user';
|
||||
export * from './gallery';
|
||||
export * from './location';
|
||||
export * from './invite';
|
||||
export * from './world';
|
||||
export * from './memos';
|
||||
65
src/shared/utils/instance.js
Normal file
65
src/shared/utils/instance.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { instanceRequest } from '../../api';
|
||||
import { parseLocation } from './location';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} instance
|
||||
*/
|
||||
function refreshInstancePlayerCount(instance) {
|
||||
const L = parseLocation(instance);
|
||||
if (L.isRealInstance) {
|
||||
instanceRequest.getInstance({
|
||||
worldId: L.worldId,
|
||||
instanceId: L.instanceId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} instanceId
|
||||
* @returns
|
||||
*/
|
||||
function isRealInstance(instanceId) {
|
||||
if (!instanceId) {
|
||||
return false;
|
||||
}
|
||||
switch (instanceId) {
|
||||
case ':':
|
||||
case 'offline':
|
||||
case 'offline:offline':
|
||||
case 'private':
|
||||
case 'private:private':
|
||||
case 'traveling':
|
||||
case 'traveling:traveling':
|
||||
case instanceId.startsWith('local'):
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} instance
|
||||
* @returns {string}
|
||||
*/
|
||||
function getLaunchURL(instance) {
|
||||
const L = instance;
|
||||
if (L.instanceId) {
|
||||
if (L.shortName) {
|
||||
return `https://vrchat.com/home/launch?worldId=${encodeURIComponent(
|
||||
L.worldId
|
||||
)}&instanceId=${encodeURIComponent(
|
||||
L.instanceId
|
||||
)}&shortName=${encodeURIComponent(L.shortName)}`;
|
||||
}
|
||||
return `https://vrchat.com/home/launch?worldId=${encodeURIComponent(
|
||||
L.worldId
|
||||
)}&instanceId=${encodeURIComponent(L.instanceId)}`;
|
||||
}
|
||||
return `https://vrchat.com/home/launch?worldId=${encodeURIComponent(
|
||||
L.worldId
|
||||
)}`;
|
||||
}
|
||||
|
||||
export { refreshInstancePlayerCount, isRealInstance, getLaunchURL };
|
||||
62
src/shared/utils/invite.js
Normal file
62
src/shared/utils/invite.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
useFriendStore,
|
||||
useInstanceStore,
|
||||
useLocationStore,
|
||||
useUserStore
|
||||
} from '../../stores';
|
||||
import { parseLocation } from './location';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} location
|
||||
* @returns
|
||||
*/
|
||||
function checkCanInvite(location) {
|
||||
const userStore = useUserStore();
|
||||
const locationStore = useLocationStore();
|
||||
const instanceStore = useInstanceStore();
|
||||
const L = parseLocation(location);
|
||||
const instance = instanceStore.cachedInstances.get(location);
|
||||
if (instance?.closedAt) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
L.accessType === 'public' ||
|
||||
L.accessType === 'group' ||
|
||||
L.userId === userStore.currentUser.id
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (L.accessType === 'invite' || L.accessType === 'friends') {
|
||||
return false;
|
||||
}
|
||||
if (locationStore.lastLocation.location === location) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} location
|
||||
* @returns
|
||||
*/
|
||||
function checkCanInviteSelf(location) {
|
||||
const userStore = useUserStore();
|
||||
const instanceStore = useInstanceStore();
|
||||
const friendStore = useFriendStore();
|
||||
const L = parseLocation(location);
|
||||
const instance = instanceStore.cachedInstances.get(location);
|
||||
if (instance?.closedAt) {
|
||||
return false;
|
||||
}
|
||||
if (L.userId === userStore.currentUser.id) {
|
||||
return true;
|
||||
}
|
||||
if (L.accessType === 'friends' && !friendStore.friends.has(L.userId)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export { checkCanInvite, checkCanInviteSelf };
|
||||
144
src/shared/utils/location.js
Normal file
144
src/shared/utils/location.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
*
|
||||
* @param {string} location
|
||||
* @param {string} worldName
|
||||
* @param {string} groupName
|
||||
* @returns {string}
|
||||
*/
|
||||
function displayLocation(location, worldName, groupName) {
|
||||
let text = worldName;
|
||||
const L = parseLocation(location);
|
||||
if (L.isOffline) {
|
||||
text = 'Offline';
|
||||
} else if (L.isPrivate) {
|
||||
text = 'Private';
|
||||
} else if (L.isTraveling) {
|
||||
text = 'Traveling';
|
||||
} else if (L.worldId) {
|
||||
if (groupName) {
|
||||
text = `${worldName} ${L.accessTypeName}(${groupName})`;
|
||||
} else if (L.instanceId) {
|
||||
text = `${worldName} ${L.accessTypeName}`;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} tag
|
||||
* @returns {object}
|
||||
*/
|
||||
function parseLocation(tag) {
|
||||
let _tag = String(tag || '');
|
||||
const ctx = {
|
||||
tag: _tag,
|
||||
isOffline: false,
|
||||
isPrivate: false,
|
||||
isTraveling: false,
|
||||
isRealInstance: false,
|
||||
worldId: '',
|
||||
instanceId: '',
|
||||
instanceName: '',
|
||||
accessType: '',
|
||||
accessTypeName: '',
|
||||
region: '',
|
||||
shortName: '',
|
||||
userId: null,
|
||||
hiddenId: null,
|
||||
privateId: null,
|
||||
friendsId: null,
|
||||
groupId: null,
|
||||
groupAccessType: null,
|
||||
canRequestInvite: false,
|
||||
strict: false,
|
||||
ageGate: false
|
||||
};
|
||||
if (_tag === 'offline' || _tag === 'offline:offline') {
|
||||
ctx.isOffline = true;
|
||||
} else if (_tag === 'private' || _tag === 'private:private') {
|
||||
ctx.isPrivate = true;
|
||||
} else if (_tag === 'traveling' || _tag === 'traveling:traveling') {
|
||||
ctx.isTraveling = true;
|
||||
} else if (!_tag.startsWith('local')) {
|
||||
ctx.isRealInstance = true;
|
||||
const sep = _tag.indexOf(':');
|
||||
// technically not part of instance id, but might be there when coping id from url so why not support it
|
||||
const shortNameQualifier = '&shortName=';
|
||||
const shortNameIndex = _tag.indexOf(shortNameQualifier);
|
||||
if (shortNameIndex >= 0) {
|
||||
ctx.shortName = _tag.substr(
|
||||
shortNameIndex + shortNameQualifier.length
|
||||
);
|
||||
_tag = _tag.substr(0, shortNameIndex);
|
||||
}
|
||||
if (sep >= 0) {
|
||||
ctx.worldId = _tag.substr(0, sep);
|
||||
ctx.instanceId = _tag.substr(sep + 1);
|
||||
ctx.instanceId.split('~').forEach((s, i) => {
|
||||
if (i) {
|
||||
const A = s.indexOf('(');
|
||||
const Z = A >= 0 ? s.lastIndexOf(')') : -1;
|
||||
const key = Z >= 0 ? s.substr(0, A) : s;
|
||||
const value = A < Z ? s.substr(A + 1, Z - A - 1) : '';
|
||||
if (key === 'hidden') {
|
||||
ctx.hiddenId = value;
|
||||
} else if (key === 'private') {
|
||||
ctx.privateId = value;
|
||||
} else if (key === 'friends') {
|
||||
ctx.friendsId = value;
|
||||
} else if (key === 'canRequestInvite') {
|
||||
ctx.canRequestInvite = true;
|
||||
} else if (key === 'region') {
|
||||
ctx.region = value;
|
||||
} else if (key === 'group') {
|
||||
ctx.groupId = value;
|
||||
} else if (key === 'groupAccessType') {
|
||||
ctx.groupAccessType = value;
|
||||
} else if (key === 'strict') {
|
||||
ctx.strict = true;
|
||||
} else if (key === 'ageGate') {
|
||||
ctx.ageGate = true;
|
||||
}
|
||||
} else {
|
||||
ctx.instanceName = s;
|
||||
}
|
||||
});
|
||||
ctx.accessType = 'public';
|
||||
if (ctx.privateId !== null) {
|
||||
if (ctx.canRequestInvite) {
|
||||
// InvitePlus
|
||||
ctx.accessType = 'invite+';
|
||||
} else {
|
||||
// InviteOnly
|
||||
ctx.accessType = 'invite';
|
||||
}
|
||||
ctx.userId = ctx.privateId;
|
||||
} else if (ctx.friendsId !== null) {
|
||||
// FriendsOnly
|
||||
ctx.accessType = 'friends';
|
||||
ctx.userId = ctx.friendsId;
|
||||
} else if (ctx.hiddenId !== null) {
|
||||
// FriendsOfGuests
|
||||
ctx.accessType = 'friends+';
|
||||
ctx.userId = ctx.hiddenId;
|
||||
} else if (ctx.groupId !== null) {
|
||||
// Group
|
||||
ctx.accessType = 'group';
|
||||
}
|
||||
ctx.accessTypeName = ctx.accessType;
|
||||
if (ctx.groupAccessType !== null) {
|
||||
if (ctx.groupAccessType === 'public') {
|
||||
ctx.accessTypeName = 'groupPublic';
|
||||
} else if (ctx.groupAccessType === 'plus') {
|
||||
ctx.accessTypeName = 'groupPlus';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.worldId = _tag;
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export { parseLocation, displayLocation };
|
||||
127
src/shared/utils/memos.js
Normal file
127
src/shared/utils/memos.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { database } from '../../service/database.js';
|
||||
import { useFriendStore } from '../../stores';
|
||||
|
||||
/**
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function 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 saveUserMemo(userId, memo);
|
||||
VRCXStorage.Remove(`memo_${userId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @returns
|
||||
*/
|
||||
async function getUserMemo(userId) {
|
||||
try {
|
||||
return await database.getUserMemo(userId);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return {
|
||||
userId: '',
|
||||
editedAt: '',
|
||||
memo: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {string} memo
|
||||
*/
|
||||
async function saveUserMemo(id, memo) {
|
||||
const friendStore = useFriendStore();
|
||||
const { friends } = storeToRefs(friendStore);
|
||||
if (memo) {
|
||||
await database.setUserMemo({
|
||||
userId: id,
|
||||
editedAt: new Date().toJSON(),
|
||||
memo
|
||||
});
|
||||
} else {
|
||||
await database.deleteUserMemo(id);
|
||||
}
|
||||
var ref = friends.value.get(id);
|
||||
if (ref) {
|
||||
ref.memo = String(memo || '');
|
||||
if (memo) {
|
||||
var array = memo.split('\n');
|
||||
ref.$nickName = array[0];
|
||||
} else {
|
||||
ref.$nickName = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function getAllUserMemos() {
|
||||
const friendStore = useFriendStore();
|
||||
const { friends } = storeToRefs(friendStore);
|
||||
var memos = await database.getAllUserMemos();
|
||||
memos.forEach((memo) => {
|
||||
var ref = friends.value.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];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} worldId
|
||||
* @returns
|
||||
*/
|
||||
async function getWorldMemo(worldId) {
|
||||
try {
|
||||
return await database.getWorldMemo(worldId);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return {
|
||||
worldId: '',
|
||||
editedAt: '',
|
||||
memo: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// async function getAvatarMemo(avatarId) {
|
||||
// try {
|
||||
// return await database.getAvatarMemoDB(avatarId);
|
||||
// } catch (err) {
|
||||
// console.error(err);
|
||||
// return {
|
||||
// avatarId: '',
|
||||
// editedAt: '',
|
||||
// memo: ''
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
export {
|
||||
migrateMemos,
|
||||
getUserMemo,
|
||||
saveUserMemo,
|
||||
getAllUserMemos,
|
||||
getWorldMemo
|
||||
// getAvatarMemo
|
||||
};
|
||||
22
src/shared/utils/setting.js
Normal file
22
src/shared/utils/setting.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
*
|
||||
* @param {string} res
|
||||
* @returns
|
||||
*/
|
||||
function getVRChatResolution(res) {
|
||||
switch (res) {
|
||||
case '1280x720':
|
||||
return '1280x720 (720p)';
|
||||
case '1920x1080':
|
||||
return '1920x1080 (1080p)';
|
||||
case '2560x1440':
|
||||
return '2560x1440 (1440p)';
|
||||
case '3840x2160':
|
||||
return '3840x2160 (4K)';
|
||||
case '7680x4320':
|
||||
return '7680x4320 (8K)';
|
||||
}
|
||||
return `${res} (Custom)`;
|
||||
}
|
||||
|
||||
export { getVRChatResolution };
|
||||
294
src/shared/utils/user.js
Normal file
294
src/shared/utils/user.js
Normal file
@@ -0,0 +1,294 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useAppearanceSettingsStore, useUserStore } from '../../stores';
|
||||
import { languageMappings } from '../constants';
|
||||
import { timeToText } from './base/format';
|
||||
import { HueToHex } from './base/ui';
|
||||
import { convertFileUrlToImageUrl } from './common';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} ctx
|
||||
* @returns {number}
|
||||
*/
|
||||
function userOnlineForTimestamp(ctx) {
|
||||
if (ctx.ref.state === 'online' && ctx.ref.$online_for) {
|
||||
return ctx.ref.$online_for;
|
||||
} else if (ctx.ref.state === 'active' && ctx.ref.$active_for) {
|
||||
return ctx.ref.$active_for;
|
||||
} else if (ctx.ref.$offline_for) {
|
||||
return ctx.ref.$offline_for;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} language
|
||||
* @returns
|
||||
*/
|
||||
function languageClass(language) {
|
||||
const style = {};
|
||||
const mapping = languageMappings[language];
|
||||
if (typeof mapping !== 'undefined') {
|
||||
style[mapping] = true;
|
||||
} else {
|
||||
style.unknown = true;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @returns
|
||||
*/
|
||||
async function getNameColour(userId) {
|
||||
const hue = await AppApi.GetColourFromUserID(userId);
|
||||
return HueToHex(hue);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns
|
||||
*/
|
||||
function removeEmojis(text) {
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
return text
|
||||
.replace(
|
||||
/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
|
||||
''
|
||||
)
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} user
|
||||
* @param {boolean} pendingOffline
|
||||
* @returns
|
||||
*/
|
||||
function userStatusClass(user, pendingOffline = false) {
|
||||
const userStore = useUserStore();
|
||||
const style = {};
|
||||
if (typeof user === 'undefined') {
|
||||
return style;
|
||||
}
|
||||
let id = '';
|
||||
if (user.id) {
|
||||
id = user.id;
|
||||
} else if (user.userId) {
|
||||
id = user.userId;
|
||||
}
|
||||
if (id === userStore.currentUser.id) {
|
||||
return statusClass(user.status);
|
||||
}
|
||||
if (!user.isFriend) {
|
||||
return style;
|
||||
}
|
||||
if (pendingOffline) {
|
||||
// Pending offline
|
||||
style.offline = true;
|
||||
} else if (
|
||||
user.status !== 'active' &&
|
||||
user.location === 'private' &&
|
||||
user.state === '' &&
|
||||
id &&
|
||||
!userStore.currentUser.onlineFriends.includes(id)
|
||||
) {
|
||||
// temp fix
|
||||
if (userStore.currentUser.activeFriends.includes(id)) {
|
||||
// Active
|
||||
style.active = true;
|
||||
} else {
|
||||
// Offline
|
||||
style.offline = true;
|
||||
}
|
||||
} else if (user.state === 'active') {
|
||||
// Active
|
||||
style.active = true;
|
||||
} else if (user.location === 'offline') {
|
||||
// Offline
|
||||
style.offline = true;
|
||||
} else if (user.status === 'active') {
|
||||
// Online
|
||||
style.online = true;
|
||||
} else if (user.status === 'join me') {
|
||||
// Join Me
|
||||
style.joinme = true;
|
||||
} else if (user.status === 'ask me') {
|
||||
// Ask Me
|
||||
style.askme = true;
|
||||
} else if (user.status === 'busy') {
|
||||
// Do Not Disturb
|
||||
style.busy = true;
|
||||
}
|
||||
if (
|
||||
user.platform &&
|
||||
user.platform !== 'standalonewindows' &&
|
||||
user.platform !== 'web'
|
||||
) {
|
||||
style.mobile = true;
|
||||
}
|
||||
if (
|
||||
user.last_platform &&
|
||||
user.last_platform !== 'standalonewindows' &&
|
||||
user.platform === 'web'
|
||||
) {
|
||||
style.mobile = true;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} status
|
||||
* @returns {object}
|
||||
*/
|
||||
function statusClass(status) {
|
||||
const style = {};
|
||||
if (typeof status !== 'undefined') {
|
||||
if (status === 'active') {
|
||||
// Online
|
||||
style.online = true;
|
||||
} else if (status === 'join me') {
|
||||
// Join Me
|
||||
style.joinme = true;
|
||||
} else if (status === 'ask me') {
|
||||
// Ask Me
|
||||
style.askme = true;
|
||||
} else if (status === 'busy') {
|
||||
// Do Not Disturb
|
||||
style.busy = true;
|
||||
}
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} user - User Ref Object
|
||||
* @param {boolean} isIcon - is use for icon (about 40x40)
|
||||
* @param {string} resolution - requested icon resolution (default 128),
|
||||
* @param {boolean} isUserDialogIcon - is use for user dialog icon
|
||||
* @returns {string} - img url
|
||||
*/
|
||||
function userImage(
|
||||
user,
|
||||
isIcon = false,
|
||||
resolution = '128',
|
||||
isUserDialogIcon = false
|
||||
) {
|
||||
const appAppearanceSettingsStore = useAppearanceSettingsStore();
|
||||
const { displayVRCPlusIconsAsAvatar } = storeToRefs(
|
||||
appAppearanceSettingsStore
|
||||
);
|
||||
if (!user) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
(isUserDialogIcon && user.userIcon) ||
|
||||
(displayVRCPlusIconsAsAvatar.value && user.userIcon)
|
||||
) {
|
||||
if (isIcon) {
|
||||
return convertFileUrlToImageUrl(user.userIcon);
|
||||
}
|
||||
return user.userIcon;
|
||||
}
|
||||
|
||||
if (user.profilePicOverrideThumbnail) {
|
||||
if (isIcon) {
|
||||
return user.profilePicOverrideThumbnail.replace(
|
||||
'/256',
|
||||
`/${resolution}`
|
||||
);
|
||||
}
|
||||
return user.profilePicOverrideThumbnail;
|
||||
}
|
||||
if (user.profilePicOverride) {
|
||||
return user.profilePicOverride;
|
||||
}
|
||||
if (user.thumbnailUrl) {
|
||||
return user.thumbnailUrl;
|
||||
}
|
||||
if (user.currentAvatarThumbnailImageUrl) {
|
||||
if (isIcon) {
|
||||
return user.currentAvatarThumbnailImageUrl.replace(
|
||||
'/256',
|
||||
`/${resolution}`
|
||||
);
|
||||
}
|
||||
return user.currentAvatarThumbnailImageUrl;
|
||||
}
|
||||
if (user.currentAvatarImageUrl) {
|
||||
if (isIcon) {
|
||||
return convertFileUrlToImageUrl(user.currentAvatarImageUrl);
|
||||
}
|
||||
return user.currentAvatarImageUrl;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} user
|
||||
* @returns {string|*}
|
||||
*/
|
||||
function userImageFull(user) {
|
||||
const appAppearanceSettingsStore = useAppearanceSettingsStore();
|
||||
const { displayVRCPlusIconsAsAvatar } = storeToRefs(
|
||||
appAppearanceSettingsStore
|
||||
);
|
||||
if (displayVRCPlusIconsAsAvatar.value && user.userIcon) {
|
||||
return user.userIcon;
|
||||
}
|
||||
if (user.profilePicOverride) {
|
||||
return user.profilePicOverride;
|
||||
}
|
||||
return user.currentAvatarImageUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} user
|
||||
* @returns {*|string}
|
||||
*/
|
||||
function parseUserUrl(user) {
|
||||
const url = new URL(user);
|
||||
const urlPath = url.pathname;
|
||||
if (urlPath.substring(5, 11) === '/user/') {
|
||||
const userId = urlPath.substring(11);
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} ctx
|
||||
* @returns {string}
|
||||
*/
|
||||
function userOnlineFor(ctx) {
|
||||
if (ctx.ref.state === 'online' && ctx.ref.$online_for) {
|
||||
return timeToText(Date.now() - ctx.ref.$online_for);
|
||||
} else if (ctx.ref.state === 'active' && ctx.ref.$active_for) {
|
||||
return timeToText(Date.now() - ctx.ref.$active_for);
|
||||
} else if (ctx.ref.$offline_for) {
|
||||
return timeToText(Date.now() - ctx.ref.$offline_for);
|
||||
}
|
||||
return '-';
|
||||
}
|
||||
|
||||
export {
|
||||
userOnlineForTimestamp,
|
||||
languageClass,
|
||||
getNameColour,
|
||||
removeEmojis,
|
||||
userStatusClass,
|
||||
statusClass,
|
||||
userImage,
|
||||
userImageFull,
|
||||
parseUserUrl,
|
||||
userOnlineFor
|
||||
};
|
||||
36
src/shared/utils/world.js
Normal file
36
src/shared/utils/world.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { worldRequest } from '../../api';
|
||||
import { parseLocation } from './location';
|
||||
import { rpcWorlds } from '../constants';
|
||||
/**
|
||||
*
|
||||
* @param {string} location
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function getWorldName(location) {
|
||||
let worldName = '';
|
||||
|
||||
const L = parseLocation(location);
|
||||
if (L.isRealInstance && L.worldId) {
|
||||
const args = await worldRequest.getCachedWorld({
|
||||
worldId: L.worldId
|
||||
});
|
||||
worldName = args.ref.name;
|
||||
}
|
||||
|
||||
return worldName;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} location
|
||||
* @returns
|
||||
*/
|
||||
function isRpcWorld(location) {
|
||||
const L = parseLocation(location);
|
||||
if (rpcWorlds.includes(L.worldId)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export { getWorldName, isRpcWorld };
|
||||
Reference in New Issue
Block a user