mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-10 18:33:51 +02:00
6748 lines
145 KiB
JavaScript
6748 lines
145 KiB
JavaScript
// Copyright(c) 2019 pypy. All rights reserved.
|
|
//
|
|
// This work is licensed under the terms of the MIT license.
|
|
// For a copy, see <https://opensource.org/licenses/MIT>.
|
|
|
|
if (window.CefSharp) {
|
|
Promise.all([
|
|
CefSharp.BindObjectAsync('VRCX'),
|
|
CefSharp.BindObjectAsync('VRCXStorage'),
|
|
CefSharp.BindObjectAsync('LogWatcher'),
|
|
CefSharp.BindObjectAsync('Discord')
|
|
]).catch(() => {
|
|
location = 'https://github.com/pypy-vrc/vrcx';
|
|
}).then(() => {
|
|
|
|
document.addEventListener('keyup', (e) => {
|
|
if (e.ctrlKey) {
|
|
if (e.shiftKey && e.code === 'KeyI') {
|
|
VRCX.ShowDevTools();
|
|
} else if (e.code === 'KeyR') {
|
|
location.reload();
|
|
}
|
|
}
|
|
});
|
|
|
|
VRCXStorage.GetBool = function (key) {
|
|
return this.Get(key) === 'true';
|
|
};
|
|
|
|
VRCXStorage.SetBool = function (key, value) {
|
|
this.Set(key, value
|
|
? 'true'
|
|
: 'false');
|
|
};
|
|
|
|
VRCXStorage.GetInt = function (key) {
|
|
return parseInt(this.Get(key), 10) || 0;
|
|
};
|
|
|
|
VRCXStorage.SetInt = function (key, value) {
|
|
this.Set(key, String(value));
|
|
};
|
|
|
|
VRCXStorage.GetFloat = function (key) {
|
|
return parseFloat(this.Get(key), 10) || 0.0;
|
|
};
|
|
|
|
VRCXStorage.SetFloat = function (key, value) {
|
|
this.Set(key, String(value));
|
|
};
|
|
|
|
VRCXStorage.GetArray = function (key) {
|
|
try {
|
|
var json = this.Get(key);
|
|
if (json) {
|
|
var array = JSON.parse(json);
|
|
if (Array.isArray(array)) {
|
|
return array;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
return [];
|
|
};
|
|
|
|
VRCXStorage.SetArray = function (key, value) {
|
|
this.Set(key, JSON.stringify(value));
|
|
};
|
|
|
|
VRCXStorage.GetObject = function (key) {
|
|
try {
|
|
var json = this.Get(key);
|
|
if (json) {
|
|
return JSON.parse(json);
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
return {};
|
|
};
|
|
|
|
VRCXStorage.SetObject = function (key, value) {
|
|
this.Set(key, JSON.stringify(value));
|
|
};
|
|
|
|
setInterval(() => VRCXStorage.Flush(), 5 * 60 * 1000);
|
|
|
|
Noty.overrideDefaults({
|
|
animation: {
|
|
open: 'animated bounceInLeft',
|
|
close: 'animated bounceOutLeft'
|
|
},
|
|
layout: 'bottomLeft',
|
|
theme: 'mint',
|
|
timeout: 6000
|
|
});
|
|
|
|
var removeFromArray = (array, item) => {
|
|
var { length } = array;
|
|
for (var i = 0; i < length; ++i) {
|
|
if (array[i] === item) {
|
|
array.splice(i, 1);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
var escapeTag = (s) => String(s).replace(/["&'<>]/gu, (c) => `&#${c.charCodeAt(0)};`);
|
|
Vue.filter('escapeTag', escapeTag);
|
|
|
|
var commaNumber = (n) => String(Number(n) || 0).replace(/(\d)(?=(\d{3})+(?!\d))/gu, '$1,');
|
|
Vue.filter('commaNumber', commaNumber);
|
|
|
|
var formatDate = (s, format) => {
|
|
var ctx = new Date(s);
|
|
if (isNaN(ctx)) {
|
|
return escapeTag(s);
|
|
}
|
|
var hours = ctx.getHours();
|
|
var map = {
|
|
'YYYY': String(10000 + ctx.getFullYear()).substr(-4),
|
|
'MM': String(101 + ctx.getMonth()).substr(-2),
|
|
'DD': String(100 + ctx.getDate()).substr(-2),
|
|
'HH24': String(100 + hours).substr(-2),
|
|
'HH': String(100 + (hours > 12
|
|
? hours - 12
|
|
: hours)).substr(-2),
|
|
'MI': String(100 + ctx.getMinutes()).substr(-2),
|
|
'SS': String(100 + ctx.getSeconds()).substr(-2),
|
|
'AMPM': hours >= 12
|
|
? 'PM'
|
|
: 'AM'
|
|
};
|
|
return format.replace(/YYYY|MM|DD|HH24|HH|MI|SS|AMPM/gu, (c) => map[c] || c);
|
|
};
|
|
Vue.filter('formatDate', formatDate);
|
|
|
|
var textToHex = (s) => String(s).split('').map((c) => c.charCodeAt(0).toString(16)).join(' ');
|
|
Vue.filter('textToHex', textToHex);
|
|
|
|
var timeToText = (t) => {
|
|
var sec = Number(t);
|
|
if (isNaN(sec)) {
|
|
return escapeTag(t);
|
|
}
|
|
sec = Math.floor(sec / 1000);
|
|
var arr = [];
|
|
if (sec < 0) {
|
|
sec = -sec;
|
|
}
|
|
if (sec >= 86400) {
|
|
arr.push(`${Math.floor(sec / 86400)}d`);
|
|
sec %= 86400;
|
|
}
|
|
if (sec >= 3600) {
|
|
arr.push(`${Math.floor(sec / 3600)}h`);
|
|
sec %= 3600;
|
|
}
|
|
if (sec >= 60) {
|
|
arr.push(`${Math.floor(sec / 60)}m`);
|
|
sec %= 60;
|
|
}
|
|
if (sec ||
|
|
!arr.length) {
|
|
arr.push(`${sec}s`);
|
|
}
|
|
return arr.join(' ');
|
|
};
|
|
Vue.filter('timeToText', timeToText);
|
|
|
|
Vue.use(VueLazyload, {
|
|
preLoad: 1,
|
|
observer: true,
|
|
observerOptions: {
|
|
rootMargin: '0px',
|
|
threshold: 0.1
|
|
}
|
|
});
|
|
|
|
Vue.use(DataTables.DataTables);
|
|
|
|
ELEMENT.locale(ELEMENT.lang.en);
|
|
|
|
//
|
|
// API
|
|
//
|
|
|
|
var API = {};
|
|
|
|
API.$handler = {};
|
|
|
|
API.$emit = function (event, ...args) {
|
|
try {
|
|
// console.log(event, ...args);
|
|
var h = this.$handler[event];
|
|
if (h) {
|
|
h.forEach((f) => f(...args));
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
};
|
|
|
|
API.$on = function (event, callback) {
|
|
var h = this.$handler[event];
|
|
if (h) {
|
|
h.push(callback);
|
|
} else {
|
|
this.$handler[event] = [callback];
|
|
}
|
|
};
|
|
|
|
API.$off = function (event, callback) {
|
|
var h = this.$handler[event];
|
|
if (h) {
|
|
h.find((val, idx, arr) => {
|
|
if (val !== callback) {
|
|
return false;
|
|
}
|
|
if (arr.length > 1) {
|
|
arr.splice(idx, 1);
|
|
} else {
|
|
delete this.$handler[event];
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
};
|
|
|
|
API.$fetch = {};
|
|
|
|
API.call = function (endpoint, options) {
|
|
var input = `https://api.vrchat.cloud/api/1/${endpoint}`;
|
|
var init = {
|
|
method: 'GET',
|
|
mode: 'cors',
|
|
credentials: 'include',
|
|
cache: 'no-cache',
|
|
referrerPolicy: 'no-referrer',
|
|
...options
|
|
};
|
|
if (init.method === 'GET') {
|
|
if (init.body) {
|
|
var url = new URL(input);
|
|
for (var key in init.body) {
|
|
url.searchParams.set(key, init.body[key]);
|
|
}
|
|
input = url.toString();
|
|
init.body = null;
|
|
}
|
|
// merge requests
|
|
if (this.$fetch[input]) {
|
|
return this.$fetch[input];
|
|
}
|
|
} else {
|
|
init.headers = {
|
|
'Content-Type': 'application/json;charset=utf-8',
|
|
...init.headers
|
|
};
|
|
init.body = init.body
|
|
? JSON.stringify(init.body)
|
|
: '{}';
|
|
}
|
|
var req = fetch(input, init).catch((err) => {
|
|
this.$throw(0, err);
|
|
}).then((res) => res.json().catch(() => {
|
|
if (!res.ok) {
|
|
this.$throw(res.status);
|
|
}
|
|
this.$throw(0, 'Invalid JSON');
|
|
}).then((json) => {
|
|
if (res.ok) {
|
|
if (json.success) {
|
|
new Noty({
|
|
type: 'success',
|
|
text: escapeTag(json.success.message)
|
|
}).show();
|
|
}
|
|
} else if (typeof json.error === 'object') {
|
|
this.$throw(
|
|
json.error.status_code || res.status,
|
|
json.error.message,
|
|
json.error.data
|
|
);
|
|
} else if (typeof json.error === 'string') {
|
|
this.$throw(
|
|
json.status_code || res.status,
|
|
json.error
|
|
);
|
|
} else {
|
|
this.$throw(res.status, json);
|
|
}
|
|
return json;
|
|
}));
|
|
if (init.method === 'GET') {
|
|
this.$fetch[input] = req.finally(() => {
|
|
delete this.$fetch[input];
|
|
});
|
|
}
|
|
return req;
|
|
};
|
|
|
|
API.$status = {
|
|
100: 'Continue',
|
|
101: 'Switching Protocols',
|
|
102: 'Processing',
|
|
103: 'Early Hints',
|
|
200: 'OK',
|
|
201: 'Created',
|
|
202: 'Accepted',
|
|
203: 'Non-Authoritative Information',
|
|
204: 'No Content',
|
|
205: 'Reset Content',
|
|
206: 'Partial Content',
|
|
207: 'Multi-Status',
|
|
208: 'Already Reported',
|
|
226: 'IM Used',
|
|
300: 'Multiple Choices',
|
|
301: 'Moved Permanently',
|
|
302: 'Found',
|
|
303: 'See Other',
|
|
304: 'Not Modified',
|
|
305: 'Use Proxy',
|
|
306: 'Switch Proxy',
|
|
307: 'Temporary Redirect',
|
|
308: 'Permanent Redirect',
|
|
400: 'Bad Request',
|
|
401: 'Unauthorized',
|
|
402: 'Payment Required',
|
|
403: 'Forbidden',
|
|
404: 'Not Found',
|
|
405: 'Method Not Allowed',
|
|
406: 'Not Acceptable',
|
|
407: 'Proxy Authentication Required',
|
|
408: 'Request Timeout',
|
|
409: 'Conflict',
|
|
410: 'Gone',
|
|
411: 'Length Required',
|
|
412: 'Precondition Failed',
|
|
413: 'Payload Too Large',
|
|
414: 'URI Too Long',
|
|
415: 'Unsupported Media Type',
|
|
416: 'Range Not Satisfiable',
|
|
417: 'Expectation Failed',
|
|
418: "I'm a teapot",
|
|
421: 'Misdirected Request',
|
|
422: 'Unprocessable Entity',
|
|
423: 'Locked',
|
|
424: 'Failed Dependency',
|
|
425: 'Too Early',
|
|
426: 'Upgrade Required',
|
|
428: 'Precondition Required',
|
|
429: 'Too Many Requests',
|
|
431: 'Request Header Fields Too Large',
|
|
451: 'Unavailable For Legal Reasons',
|
|
500: 'Internal Server Error',
|
|
501: 'Not Implemented',
|
|
502: 'Bad Gateway',
|
|
503: 'Service Unavailable',
|
|
504: 'Gateway Timeout',
|
|
505: 'HTTP Version Not Supported',
|
|
506: 'Variant Also Negotiates',
|
|
507: 'Insufficient Storage',
|
|
508: 'Loop Detected',
|
|
510: 'Not Extended',
|
|
511: 'Network Authentication Required',
|
|
// CloudFlare Error
|
|
520: 'Web server returns an unknown error',
|
|
521: 'Web server is down',
|
|
522: 'Connection timed out',
|
|
523: 'Origin is unreachable',
|
|
524: 'A timeout occurred',
|
|
525: 'SSL handshake failed',
|
|
526: 'Invalid SSL certificate',
|
|
527: 'Railgun Listener to origin error'
|
|
};
|
|
|
|
API.$throw = function (code, error, extra) {
|
|
var text = [];
|
|
if (code) {
|
|
var status = this.$status[code];
|
|
if (status) {
|
|
text.push(`${code} ${status}`);
|
|
} else {
|
|
text.push(`${code}`);
|
|
}
|
|
}
|
|
if (error !== undefined) {
|
|
text.push(error);
|
|
}
|
|
if (extra !== undefined) {
|
|
text.push(extra);
|
|
}
|
|
if (text.length) {
|
|
new Noty({
|
|
type: 'error',
|
|
text: text.map((s) => escapeTag(s)).join('<br>')
|
|
}).show();
|
|
}
|
|
throw {
|
|
'status_code': code,
|
|
error
|
|
};
|
|
};
|
|
|
|
API.bulk = function (options) {
|
|
var handle = (args) => {
|
|
if (typeof options.handle === 'function') {
|
|
options.handle(args, options);
|
|
}
|
|
if (args.json.length &&
|
|
(options.param.offset += args.json.length,
|
|
options.N > 0
|
|
? options.N > options.param.offset
|
|
: options.N < 0
|
|
? args.json.length
|
|
: options.param.n === args.json.length)) {
|
|
this[options.fn](options.param).catch((err) => {
|
|
if (typeof options.done === 'function') {
|
|
options.done(false, options);
|
|
}
|
|
throw err;
|
|
}).then(handle);
|
|
} else if (typeof options.done === 'function') {
|
|
options.done(true, options);
|
|
}
|
|
return args;
|
|
};
|
|
this[options.fn](options.param).catch((err) => {
|
|
if (typeof options.done === 'function') {
|
|
options.done(false, options);
|
|
}
|
|
throw err;
|
|
}).then(handle);
|
|
};
|
|
|
|
// API: Config
|
|
|
|
API.config = {};
|
|
|
|
API.$on('CONFIG', (args) => {
|
|
args.ref = API.updateConfig(args.json);
|
|
});
|
|
|
|
API.getConfig = function () {
|
|
return this.call('config', {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
json
|
|
};
|
|
this.$emit('CONFIG', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.updateConfig = function (ref) {
|
|
var ctx = {
|
|
clientApiKey: '',
|
|
...ref
|
|
};
|
|
this.config = ctx;
|
|
return ctx;
|
|
};
|
|
|
|
// API: Location
|
|
|
|
API.parseLocation = function (tag) {
|
|
var L = {
|
|
tag: String(tag || ''),
|
|
isOffline: false,
|
|
isPrivate: false,
|
|
worldId: '',
|
|
instanceId: '',
|
|
instanceName: '',
|
|
accessType: '',
|
|
userId: null,
|
|
hiddenId: null,
|
|
privateId: null,
|
|
friendsId: null,
|
|
canRequestInvite: false
|
|
};
|
|
if (L.tag === 'offline') {
|
|
L.isOffline = true;
|
|
} else if (L.tag === 'private') {
|
|
L.isPrivate = true;
|
|
} else if (!L.tag.startsWith('local')) {
|
|
var sep = L.tag.indexOf(':');
|
|
if (sep >= 0) {
|
|
L.worldId = L.tag.substr(0, sep);
|
|
L.instanceId = L.tag.substr(sep + 1);
|
|
L.instanceId.split('~').forEach((s, i) => {
|
|
if (i) {
|
|
var A = s.indexOf('(');
|
|
var Z = A >= 0
|
|
? s.lastIndexOf(')')
|
|
: -1;
|
|
var key = Z >= 0
|
|
? s.substr(0, A)
|
|
: s;
|
|
var value = A < Z
|
|
? s.substr(A + 1, Z - A - 1)
|
|
: '';
|
|
if (key === 'hidden') {
|
|
L.hiddenId = value;
|
|
} else if (key === 'private') {
|
|
L.privateId = value;
|
|
} else if (key === 'friends') {
|
|
L.friendsId = value;
|
|
} else if (key === 'canRequestInvite') {
|
|
L.canRequestInvite = true;
|
|
}
|
|
} else {
|
|
L.instanceName = s;
|
|
}
|
|
});
|
|
L.accessType = 'public';
|
|
if (L.privateId !== null) {
|
|
if (L.canRequestInvite) {
|
|
// InvitePlus
|
|
L.accessType = 'invite+';
|
|
} else {
|
|
// InviteOnly
|
|
L.accessType = 'invite';
|
|
}
|
|
L.userId = L.privateId;
|
|
} else if (L.friendsId !== null) {
|
|
// FriendsOnly
|
|
L.accessType = 'friends';
|
|
L.userId = L.friendsId;
|
|
} else if (L.hiddenId !== null) {
|
|
// FriendsOfGuests
|
|
L.accessType = 'friends+';
|
|
L.userId = L.hiddenId;
|
|
}
|
|
} else {
|
|
L.worldId = L.tag;
|
|
}
|
|
}
|
|
return L;
|
|
};
|
|
|
|
Vue.component('launch', {
|
|
template: '<el-button @click="confirm" size="mini" icon="el-icon-link" circle></el-button>',
|
|
props: {
|
|
location: String
|
|
},
|
|
methods: {
|
|
parse() {
|
|
var L = API.parseLocation(this.location);
|
|
this.$el.style.display = L.isOffline || L.isPrivate
|
|
? 'none'
|
|
: '';
|
|
},
|
|
confirm() {
|
|
API.$emit('SHOW_LAUNCH_DIALOG', this.location);
|
|
}
|
|
},
|
|
watch: {
|
|
location() {
|
|
this.parse();
|
|
}
|
|
},
|
|
mounted() {
|
|
this.parse();
|
|
}
|
|
});
|
|
|
|
Vue.component('location', {
|
|
template: '<span @click="showWorldDialog" :class="{ \'x-link\': link }">{{ text }}<slot></slot></span>',
|
|
props: {
|
|
location: String,
|
|
link: {
|
|
type: Boolean,
|
|
default: true
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
text: this.location
|
|
};
|
|
},
|
|
methods: {
|
|
parse() {
|
|
var L = API.parseLocation(this.location);
|
|
if (L.isOffline) {
|
|
this.text = 'Offline';
|
|
} else if (L.isPrivate) {
|
|
this.text = 'Private';
|
|
} else if (L.worldId) {
|
|
var ref = API.world[L.worldId];
|
|
if (ref) {
|
|
if (L.instanceId) {
|
|
this.text = `${ref.name} #${L.instanceName} ${L.accessType}`;
|
|
} else {
|
|
this.text = ref.name;
|
|
}
|
|
} else {
|
|
API.getWorld({
|
|
worldId: L.worldId
|
|
}).then((args) => {
|
|
if (L.tag === this.location) {
|
|
if (L.instanceId) {
|
|
this.text = `${args.ref.name} #${L.instanceName} ${L.accessType}`;
|
|
} else {
|
|
this.text = args.ref.name;
|
|
}
|
|
}
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
},
|
|
showWorldDialog() {
|
|
if (this.link) {
|
|
API.$emit('SHOW_WORLD_DIALOG', this.location);
|
|
}
|
|
}
|
|
},
|
|
watch: {
|
|
location() {
|
|
this.parse();
|
|
}
|
|
},
|
|
created() {
|
|
this.parse();
|
|
}
|
|
});
|
|
|
|
// API: User
|
|
|
|
// changeUserName: PUT users/${userId} {displayName: string, currentPassword: string}
|
|
// changeUserEmail: PUT users/${userId} {email: string, currentPassword: string}
|
|
// changePassword: PUT users/${userId} {password: string, currentPassword: string}
|
|
// updateTOSAggreement: PUT users/${userId} {acceptedTOSVersion: number}
|
|
|
|
// 2FA
|
|
// removeTwoFactorAuth: DELETE auth/twofactorauth
|
|
// getTwoFactorAuthpendingSecret: POST auth/twofactorauth/totp/pending -> { qrCodeDataUrl: string, secret: string }
|
|
// verifyTwoFactorAuthPendingSecret: POST auth/twofactorauth/totp/pending/verify { code: string } -> { verified: bool, enabled: bool }
|
|
// cancelVerifyTwoFactorAuthPendingSecret: DELETE auth/twofactorauth/totp/pending
|
|
// getTwoFactorAuthOneTimePasswords: GET auth/user/twofactorauth/otp -> { otp: [ { code: string, used: bool } ] }
|
|
|
|
// Account Link
|
|
// merge: PUT auth/user/merge {mergeToken: string}
|
|
// 링크됐다면 CurrentUser에 steamId, oculusId 값이 생기는듯
|
|
// 스팀 계정으로 로그인해도 steamId, steamDetails에 값이 생김
|
|
|
|
// Password Recovery
|
|
// sendLink: PUT auth/password {email: string}
|
|
// setNewPassword: PUT auth/password {emailToken: string, id: string, password: string}
|
|
|
|
API.isLoggedIn = false;
|
|
API.currentUser = {};
|
|
API.user = {};
|
|
|
|
API.$on('LOGOUT', () => {
|
|
API.isLoggedIn = false;
|
|
VRCX.DeleteAllCookies();
|
|
});
|
|
|
|
API.$on('USER:CURRENT', (args) => {
|
|
args.ref = API.updateCurrentUser(args.json);
|
|
});
|
|
|
|
API.$on('USER:CURRENT:SAVE', (args) => {
|
|
API.$emit('USER:CURRENT', args);
|
|
});
|
|
|
|
API.$on('USER', (args) => {
|
|
args.ref = API.updateUser(args.json);
|
|
});
|
|
|
|
API.$on('USER:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('USER', {
|
|
param: {
|
|
userId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
API.logout = function () {
|
|
return this.call('logout', {
|
|
method: 'PUT'
|
|
}).finally(() => {
|
|
this.$emit('LOGOUT');
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
username: string,
|
|
password: string
|
|
}
|
|
*/
|
|
API.login = function (param) {
|
|
return this.call(`auth/user?apiKey=${this.config.clientApiKey}`, {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Basic ${btoa(encodeURIComponent(`${param.username}:${param.password}`).replace(/%([0-9A-F]{2})/gu, (_, s) => String.fromCharCode(parseInt(s, 16))))}`
|
|
}
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json,
|
|
origin: true
|
|
};
|
|
if (json.requiresTwoFactorAuth) {
|
|
this.$emit('USER:2FA', args);
|
|
} else {
|
|
this.$emit('USER:CURRENT', args);
|
|
}
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
steamTicket: string
|
|
}
|
|
*/
|
|
API.loginWithSteam = function (param) {
|
|
return this.call(`auth/steam?apiKey=${this.config.clientApiKey}`, {
|
|
method: 'POST',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json,
|
|
origin: true
|
|
};
|
|
if (json.requiresTwoFactorAuth) {
|
|
this.$emit('USER:2FA', args);
|
|
} else {
|
|
this.$emit('USER:CURRENT', args);
|
|
}
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
code: string
|
|
}
|
|
*/
|
|
API.verifyOTP = function (param) {
|
|
return this.call('auth/twofactorauth/otp/verify', {
|
|
method: 'POST',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('OTP', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
code: string
|
|
}
|
|
*/
|
|
API.verifyTOTP = function (param) {
|
|
return this.call('auth/twofactorauth/totp/verify', {
|
|
method: 'POST',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('TOTP', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.getCurrentUser = function () {
|
|
return this.call(`auth/user?apiKey=${this.config.clientApiKey}`, {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
json,
|
|
origin: true
|
|
};
|
|
if (json.requiresTwoFactorAuth) {
|
|
this.$emit('USER:2FA', args);
|
|
} else {
|
|
this.$emit('USER:CURRENT', args);
|
|
}
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.updateCurrentUser = function (ref) {
|
|
var ctx;
|
|
if (this.isLoggedIn) {
|
|
ctx = this.currentUser;
|
|
Object.assign(ctx, ref);
|
|
if (ctx.homeLocation_.tag !== ctx.homeLocation) {
|
|
ctx.homeLocation_ = this.parseLocation(ctx.homeLocation);
|
|
}
|
|
} else {
|
|
this.isLoggedIn = true;
|
|
ctx = {
|
|
id: ref.id,
|
|
username: '',
|
|
displayName: '',
|
|
pastDisplayNames: [],
|
|
friends: [],
|
|
currentAvatarImageUrl: '',
|
|
currentAvatarThumbnailImageUrl: '',
|
|
currentAvatar: '',
|
|
homeLocation: '',
|
|
twoFactorAuthEnabled: false,
|
|
status: '',
|
|
statusDescription: '',
|
|
state: '',
|
|
tags: [],
|
|
developerType: '',
|
|
last_login: '',
|
|
last_platform: '',
|
|
allowAvatarCopying: false,
|
|
friendKey: '',
|
|
onlineFriends: [],
|
|
activeFriends: [],
|
|
offlineFriends: [],
|
|
// custom
|
|
homeLocation_: {},
|
|
//
|
|
...ref
|
|
};
|
|
ctx.homeLocation_ = this.parseLocation(ctx.homeLocation);
|
|
this.currentUser = ctx;
|
|
this.$emit('LOGIN', {
|
|
json: ref,
|
|
ref: ctx
|
|
});
|
|
}
|
|
return ctx;
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
userId: string
|
|
}
|
|
*/
|
|
API.getUser = function (param) {
|
|
return this.call(`users/${param.userId}`, {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('USER', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
var userUpdateQueue = [];
|
|
var userUpdateTimer = false;
|
|
var queueUserUpdate = (args) => {
|
|
userUpdateQueue.push(args);
|
|
if (userUpdateTimer === false) {
|
|
userUpdateTimer = setTimeout(() => {
|
|
userUpdateTimer = false;
|
|
userUpdateQueue.forEach((v) => API.$emit('USER:UPDATE', v));
|
|
userUpdateQueue.length = 0;
|
|
}, 1);
|
|
}
|
|
};
|
|
|
|
API.updateUser = function (ref) {
|
|
var ctx = this.user[ref.id];
|
|
if (ctx) {
|
|
var prop = {};
|
|
var key;
|
|
for (key in ctx) {
|
|
if (typeof ctx[key] !== 'object') {
|
|
prop[key] = true;
|
|
}
|
|
}
|
|
var _ctx = { ...ctx };
|
|
Object.assign(ctx, ref);
|
|
if (ctx.location_.tag !== ctx.location) {
|
|
ctx.location_ = this.parseLocation(ctx.location);
|
|
}
|
|
for (key in ctx) {
|
|
if (typeof ctx[key] !== 'object') {
|
|
prop[key] = true;
|
|
}
|
|
}
|
|
var has = false;
|
|
for (key in prop) {
|
|
if (ctx[key] === _ctx[key]) {
|
|
delete prop[key];
|
|
} else {
|
|
has = true;
|
|
prop[key] = [
|
|
ctx[key],
|
|
_ctx[key]
|
|
];
|
|
}
|
|
}
|
|
if (has) {
|
|
if (prop.location) {
|
|
var now = Date.now();
|
|
prop.location.push(now - ctx.location_at_);
|
|
ctx.location_at_ = now;
|
|
}
|
|
queueUserUpdate({
|
|
ref: ctx,
|
|
prop
|
|
});
|
|
}
|
|
} else {
|
|
ctx = {
|
|
id: ref.id,
|
|
username: '',
|
|
displayName: '',
|
|
currentAvatarImageUrl: '',
|
|
currentAvatarThumbnailImageUrl: '',
|
|
status: '',
|
|
statusDescription: '',
|
|
state: '',
|
|
tags: [],
|
|
developerType: '',
|
|
last_login: '',
|
|
last_platform: '',
|
|
allowAvatarCopying: false,
|
|
isFriend: false,
|
|
friendKey: '',
|
|
location: '',
|
|
worldId: '',
|
|
instanceId: '',
|
|
// custom
|
|
location_: {},
|
|
location_at_: Date.now(),
|
|
admin_: false,
|
|
troll_: false,
|
|
trustLevel_: 'Visitor',
|
|
//
|
|
...ref
|
|
};
|
|
ctx.location_ = this.parseLocation(ctx.location);
|
|
this.user[ctx.id] = ctx;
|
|
}
|
|
ctx.admin_ = ctx.developerType &&
|
|
ctx.developerType !== 'none';
|
|
if (ctx.tags) {
|
|
ctx.admin_ = ctx.admin_ || ctx.tags.includes('admin_moderator');
|
|
ctx.troll_ = ctx.tags.includes('system_probable_troll') ||
|
|
ctx.tags.includes('system_troll');
|
|
if (ctx.tags.includes('system_legend')) {
|
|
ctx.trustLevel_ = 'Legendary User';
|
|
} else if (ctx.tags.includes('system_trust_legend')) {
|
|
ctx.trustLevel_ = 'Veteran User';
|
|
} else if (ctx.tags.includes('system_trust_veteran')) {
|
|
ctx.trustLevel_ = 'Trusted User';
|
|
} else if (ctx.tags.includes('system_trust_trusted')) {
|
|
ctx.trustLevel_ = 'Known User';
|
|
} else if (ctx.tags.includes('system_trust_known')) {
|
|
ctx.trustLevel_ = 'User';
|
|
} else if (ctx.tags.includes('system_trust_basic')) {
|
|
ctx.trustLevel_ = 'New User';
|
|
} else {
|
|
ctx.trustLevel_ = 'Visitor';
|
|
}
|
|
}
|
|
return ctx;
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
userId: string
|
|
}
|
|
*/
|
|
API.getCachedUser = function (param) {
|
|
return new Promise((resolve, reject) => {
|
|
var ctx = this.user[param.userId];
|
|
if (ctx) {
|
|
resolve({
|
|
cache: true,
|
|
ref: ctx,
|
|
param
|
|
});
|
|
} else {
|
|
this.getUser(param).catch(reject).then(resolve);
|
|
}
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number,
|
|
search: string,
|
|
sort: string ('nuisanceFactor', 'created', '_created_at', 'last_login'),
|
|
order: string ('ascending', 'descending')
|
|
}
|
|
*/
|
|
API.getUsers = function (param) {
|
|
return this.call('users', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('USER:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
status: string ('active', 'join me', 'busy', 'offline'),
|
|
statusDescription: string
|
|
}
|
|
*/
|
|
API.saveCurrentUser = function (param) {
|
|
return this.call(`users/${this.currentUser.id}`, {
|
|
method: 'PUT',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
// FIXME: handle this
|
|
this.$emit('USER:CURRENT:SAVE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: World
|
|
|
|
API.world = {};
|
|
|
|
API.$on('WORLD', (args) => {
|
|
args.ref = API.updateWorld(args.json);
|
|
});
|
|
|
|
API.$on('WORLD:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('WORLD', {
|
|
param: {
|
|
worldId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
/*
|
|
param: {
|
|
worldId: string
|
|
}
|
|
*/
|
|
API.getWorld = function (param) {
|
|
return this.call(`worlds/${param.worldId}`, {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('WORLD', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.updateWorld = function (ref) {
|
|
var ctx = this.world[ref.id];
|
|
if (ctx) {
|
|
Object.assign(ctx, ref);
|
|
} else {
|
|
ctx = {
|
|
id: ref.id,
|
|
name: '',
|
|
description: '',
|
|
authorId: '',
|
|
authorName: '',
|
|
capacity: 0,
|
|
tags: [],
|
|
releaseStatus: '',
|
|
imageUrl: '',
|
|
thumbnailImageUrl: '',
|
|
assetUrl: '',
|
|
assetUrlObject: {},
|
|
pluginUrl: '',
|
|
pluginUrlObject: {},
|
|
unityPackageUrl: '',
|
|
unityPackageUrlObject: {},
|
|
unityPackages: [],
|
|
version: 0,
|
|
previewYoutubeId: '',
|
|
favorites: 0,
|
|
created_at: '',
|
|
updated_at: '',
|
|
publicationDate: '',
|
|
labsPublicationDate: '',
|
|
visits: 0,
|
|
popularity: 0,
|
|
heat: 0,
|
|
publicOccupants: 0,
|
|
privateOccupants: 0,
|
|
occupants: 0,
|
|
instances: [],
|
|
// custom
|
|
labs_: false,
|
|
//
|
|
...ref
|
|
};
|
|
this.world[ctx.id] = ctx;
|
|
}
|
|
if (ctx.tags) {
|
|
ctx.labs_ = ctx.tags.includes('system_labs');
|
|
}
|
|
return ctx;
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
worldId: string
|
|
}
|
|
*/
|
|
API.getCachedWorld = function (param) {
|
|
return new Promise((resolve, reject) => {
|
|
var ctx = this.world[param.worldId];
|
|
if (ctx) {
|
|
resolve({
|
|
cache: true,
|
|
ref: ctx,
|
|
param
|
|
});
|
|
} else {
|
|
this.getWorld(param).catch(reject).then(resolve);
|
|
}
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number,
|
|
search: string,
|
|
userId: string,
|
|
user: string ('me','friend')
|
|
sort: string ('popularity','heat','trust','shuffle','favorites','reportScore','reportCount','publicationDate','labsPublicationDate','created','_created_at','updated','_updated_at','order'),
|
|
order: string ('ascending','descending'),
|
|
releaseStatus: string ('public','private','hidden','all'),
|
|
featured: boolean
|
|
}
|
|
*/
|
|
API.getWorlds = function (param, option) {
|
|
var endpoint = 'worlds';
|
|
if (option) {
|
|
endpoint = `worlds/${option}`;
|
|
}
|
|
return this.call(endpoint, {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('WORLD:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: Friend
|
|
|
|
API.friend404 = {};
|
|
API.isFriendLoading = false;
|
|
|
|
API.$on('LOGIN', () => {
|
|
API.friend404 = {};
|
|
API.isFriendLoading = false;
|
|
});
|
|
|
|
API.$on('FRIEND:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('USER', {
|
|
param: {
|
|
userId: json.id
|
|
},
|
|
json
|
|
});
|
|
API.user[json.id].friend_ = true;
|
|
delete API.friend404[json.id];
|
|
});
|
|
});
|
|
|
|
API.checkFriends = function (mark) {
|
|
if (!mark) {
|
|
return this.currentUser.friends.every((id) => {
|
|
var ctx = this.user[id];
|
|
if (ctx &&
|
|
ctx.friend_) {
|
|
return true;
|
|
}
|
|
// NOTE: NaN이면 false라서 괜찮음
|
|
return this.friend404[id] >= 2;
|
|
});
|
|
}
|
|
this.currentUser.friends.forEach((id) => {
|
|
var ctx = this.user[id];
|
|
if (!(ctx &&
|
|
ctx.friend_)) {
|
|
var hit = Number(this.friend404[id]) || 0;
|
|
if (hit < 2) {
|
|
this.friend404[id] = hit + 1;
|
|
}
|
|
}
|
|
});
|
|
return true;
|
|
};
|
|
|
|
API.refreshFriend = function () {
|
|
var param = {
|
|
n: 100,
|
|
offset: 0,
|
|
offline: false
|
|
};
|
|
var N = this.currentUser.onlineFriends.length;
|
|
if (!N) {
|
|
N = this.currentUser.friends.length;
|
|
if (!N ||
|
|
this.checkFriends(false)) {
|
|
return;
|
|
}
|
|
param.offline = true;
|
|
}
|
|
if (!this.isFriendLoading) {
|
|
this.isFriendLoading = true;
|
|
this.bulk({
|
|
fn: 'getFriends',
|
|
N,
|
|
param,
|
|
done: (ok, options) => {
|
|
if (this.checkFriends(param.offline)) {
|
|
this.isFriendLoading = false;
|
|
} else {
|
|
N = this.currentUser.friends.length - param.offset;
|
|
if (N <= 0) {
|
|
N = this.currentUser.friends.length;
|
|
}
|
|
options.N = N;
|
|
param.offset = 0;
|
|
param.offline = true;
|
|
this.bulk(options);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number,
|
|
offline: boolean
|
|
}
|
|
*/
|
|
API.getFriends = function (param) {
|
|
return this.call('auth/user/friends', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FRIEND:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
userId: string
|
|
}
|
|
*/
|
|
API.sendFriendRequest = function (param) {
|
|
return this.call(`user/${param.userId}/friendRequest`, {
|
|
method: 'POST'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FRIEND:REQUEST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
userId: string
|
|
}
|
|
*/
|
|
API.cancelFriendRequest = function (param) {
|
|
return this.call(`user/${param.userId}/friendRequest`, {
|
|
method: 'DELETE'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FRIEND:REQUEST:CANCEL', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
userId: string
|
|
}
|
|
*/
|
|
API.deleteFriend = function (param) {
|
|
return this.call(`auth/user/friends/${param.userId}`, {
|
|
method: 'DELETE'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FRIEND:DELETE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
userId: string
|
|
}
|
|
*/
|
|
API.getFriendStatus = function (param) {
|
|
return this.call(`user/${param.userId}/friendStatus`, {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FRIEND:STATUS', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: Avatar
|
|
|
|
API.avatar = {};
|
|
|
|
API.$on('AVATAR', (args) => {
|
|
args.ref = API.updateAvatar(args.json);
|
|
});
|
|
|
|
API.$on('AVATAR:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('AVATAR', {
|
|
param: {
|
|
avatarId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
API.$on('AVATAR:SELECT', (args) => {
|
|
API.$emit('USER:CURRENT', args);
|
|
});
|
|
|
|
/*
|
|
param: {
|
|
avatarId: string
|
|
}
|
|
*/
|
|
API.getAvatar = function (param) {
|
|
return this.call(`avatars/${param.avatarId}`, {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('AVATAR', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.updateAvatar = function (ref) {
|
|
var ctx = this.avatar[ref.id];
|
|
if (ctx) {
|
|
Object.assign(ctx, ref);
|
|
} else {
|
|
ctx = {
|
|
id: ref.id,
|
|
name: '',
|
|
description: '',
|
|
authorId: '',
|
|
authorName: '',
|
|
tags: [],
|
|
assetUrl: '',
|
|
assetUrlObject: {},
|
|
imageUrl: '',
|
|
thumbnailImageUrl: '',
|
|
releaseStatus: '',
|
|
version: 0,
|
|
unityPackages: [],
|
|
unityPackageUrl: '',
|
|
unityPackageUrlObject: {},
|
|
created_at: '',
|
|
updated_at: '',
|
|
...ref
|
|
};
|
|
this.avatar[ctx.id] = ctx;
|
|
}
|
|
return ctx;
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
avatarId: string
|
|
}
|
|
*/
|
|
API.getCachedAvatar = function (param) {
|
|
return new Promise((resolve, reject) => {
|
|
var ctx = this.avatar[param.avatarId];
|
|
if (ctx) {
|
|
resolve({
|
|
cache: true,
|
|
ref: ctx,
|
|
param
|
|
});
|
|
} else {
|
|
this.getAvatar(param).catch(reject).then(resolve);
|
|
}
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number,
|
|
search: string,
|
|
userId: string,
|
|
user: string ('me','friends')
|
|
sort: string ('created','updated','order','_created_at','_updated_at'),
|
|
order: string ('ascending','descending'),
|
|
releaseStatus: string ('public','private','hidden','all'),
|
|
featured: boolean
|
|
}
|
|
*/
|
|
API.getAvatars = function (param) {
|
|
return this.call('avatars', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('AVATAR:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
avatarId: string
|
|
}
|
|
*/
|
|
API.selectAvatar = function (param) {
|
|
return this.call(`avatars/${param.avatarId}/select`, {
|
|
method: 'PUT',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('AVATAR:SELECT', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: Notification
|
|
|
|
API.notification = {};
|
|
API.isNotificationLoading = false;
|
|
|
|
API.$on('LOGIN', () => {
|
|
API.notification = {};
|
|
API.isNotificationLoading = false;
|
|
});
|
|
|
|
API.$on('NOTIFICATION', (args) => {
|
|
args.ref = API.updateNotification(args.json);
|
|
});
|
|
|
|
API.$on('NOTIFICATION:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('NOTIFICATION', {
|
|
param: {
|
|
notificationId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
API.$on('NOTIFICATION:ACCEPT', (args) => {
|
|
var ctx = API.notification[args.param.notificationId];
|
|
if (ctx &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
args.ref = ctx;
|
|
API.$emit('NOTIFICATION:@DELETE', {
|
|
param: {
|
|
notificationId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
API.$emit('FRIEND:ADD', {
|
|
param: {
|
|
userId: ctx.senderUserId
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
API.$on('NOTIFICATION:HIDE', (args) => {
|
|
var ctx = API.notification[args.param.notificationId];
|
|
if (ctx &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
args.ref = ctx;
|
|
API.$emit('NOTIFICATION:@DELETE', {
|
|
param: {
|
|
notificationId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
});
|
|
|
|
API.markAllNotificationsAsExpired = function () {
|
|
for (var key in this.notification) {
|
|
var ctx = this.notification[key];
|
|
if (!ctx.hide_) {
|
|
ctx.expired_ = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
API.checkExpiredNotifcations = function () {
|
|
for (var key in this.notification) {
|
|
var ctx = this.notification[key];
|
|
if (ctx.expired_ &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
this.$emit('NOTIFICATION:@DELETE', {
|
|
param: {
|
|
notificationId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
API.refreshNotification = function () {
|
|
// NOTE : 캐시 때문에 after=~ 로는 갱신이 안됨. 그래서 첨부터 불러옴
|
|
if (!this.isNotificationLoading) {
|
|
this.isNotificationLoading = true;
|
|
this.markAllNotificationsAsExpired();
|
|
this.bulk({
|
|
fn: 'getNotifications',
|
|
N: -1,
|
|
param: {
|
|
n: 100,
|
|
offset: 0
|
|
},
|
|
done: (ok) => {
|
|
if (ok) {
|
|
this.checkExpiredNotifcations();
|
|
}
|
|
this.isNotificationLoading = false;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
API.updateNotification = function (ref) {
|
|
var ctx = this.notification[ref.id];
|
|
if (ctx) {
|
|
Object.assign(ctx, ref);
|
|
} else {
|
|
ctx = {
|
|
id: ref.id,
|
|
senderUserId: '',
|
|
senderUsername: '',
|
|
type: '',
|
|
message: '',
|
|
details: {},
|
|
seen: false,
|
|
created_at: '',
|
|
...ref
|
|
};
|
|
this.notification[ctx.id] = ctx;
|
|
}
|
|
if (typeof ctx.details !== 'object') {
|
|
var details = {};
|
|
try {
|
|
var json = JSON.parse(ctx.details);
|
|
if (typeof json === 'object') {
|
|
details = json;
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
ctx.details = details;
|
|
}
|
|
ctx.expired_ = false;
|
|
return ctx;
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number,
|
|
sent: boolean,
|
|
type: string,
|
|
after: string (ISO8601 or 'five_minutes_ago')
|
|
}
|
|
*/
|
|
API.getNotifications = function (param) {
|
|
return this.call('auth/user/notifications', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('NOTIFICATION:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.clearNotification = function () {
|
|
return this.call('auth/user/notifications/clear', {
|
|
method: 'PUT'
|
|
}).then((json) => {
|
|
var args = {
|
|
json
|
|
};
|
|
// FIXME: 고쳐줘
|
|
this.$emit('NOTIFICATION:CLEAR', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
receiverUserId: string,
|
|
type: string,
|
|
message: string,
|
|
seen: boolean,
|
|
details: json-string
|
|
}
|
|
*/
|
|
API.sendNotification = function (param) {
|
|
return this.call(`user/${param.receiverUserId}/notification`, {
|
|
method: 'POST',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('NOTIFICATION:SEND', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
notificationId: string
|
|
}
|
|
*/
|
|
API.acceptNotification = function (param) {
|
|
return this.call(`auth/user/notifications/${param.notificationId}/accept`, {
|
|
method: 'PUT'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('NOTIFICATION:ACCEPT', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
notificationId: string
|
|
}
|
|
*/
|
|
API.hideNotification = function (param) {
|
|
return this.call(`auth/user/notifications/${param.notificationId}/hide`, {
|
|
method: 'PUT'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('NOTIFICATION:HIDE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.getFriendRequest = function (userId) {
|
|
for (var key in this.notification) {
|
|
var ctx = this.notification[key];
|
|
if (ctx.type === 'friendRequest' &&
|
|
ctx.senderUserId === userId &&
|
|
!ctx.hide_) {
|
|
return key;
|
|
}
|
|
}
|
|
return '';
|
|
};
|
|
|
|
// API: PlayerModeration
|
|
|
|
API.playerModeration = {};
|
|
API.isPlayerModerationLoading = false;
|
|
|
|
API.$on('LOGIN', () => {
|
|
API.playerModeration = {};
|
|
API.isPlayerModerationLoading = false;
|
|
});
|
|
|
|
API.$on('PLAYER-MODERATION', (args) => {
|
|
args.ref = API.updatePlayerModeration(args.json);
|
|
});
|
|
|
|
API.$on('PLAYER-MODERATION:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('PLAYER-MODERATION', {
|
|
param: {
|
|
playerModerationId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
API.$on('PLAYER-MODERATION:SEND', (args) => {
|
|
API.$emit('PLAYER-MODERATION', {
|
|
param: {
|
|
playerModerationId: args.json.id
|
|
},
|
|
json: args.json
|
|
});
|
|
});
|
|
|
|
API.$on('PLAYER-MODERATION:DELETE', (args) => {
|
|
API.handleDeletePlayerModeration(args.param.type, args.param.moderated);
|
|
});
|
|
|
|
API.markAllPlayerModerationsAsExpired = function () {
|
|
for (var key in this.playerModeration) {
|
|
var ctx = this.playerModeration[key];
|
|
if (!ctx.hide_) {
|
|
ctx.expired_ = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
API.checkExpiredPlayerModerations = function () {
|
|
for (var key in this.playerModeration) {
|
|
var ctx = this.playerModeration[key];
|
|
if (ctx.expired_ &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
this.$emit('PLAYER-MODERATION:@DELETE', {
|
|
param: {
|
|
playerModerationId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
API.refreshPlayerModeration = function () {
|
|
if (!this.isPlayerModerationLoading) {
|
|
this.isPlayerModerationLoading = true;
|
|
this.markAllPlayerModerationsAsExpired();
|
|
Promise.all([
|
|
this.getPlayerModerations(),
|
|
this.getPlayerModerationsAgainstMe()
|
|
]).finally(() => {
|
|
this.isPlayerModerationLoading = false;
|
|
}).then(() => {
|
|
this.checkExpiredPlayerModerations();
|
|
});
|
|
}
|
|
};
|
|
|
|
API.handleDeletePlayerModeration = function (type, moderated) {
|
|
var cuid = this.currentUser.id;
|
|
for (var key in this.playerModeration) {
|
|
var ctx = this.playerModeration[key];
|
|
if (ctx.type === type &&
|
|
ctx.targetUserId === moderated &&
|
|
ctx.sourceUserId === cuid &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
this.$emit('PLAYER-MODERATION:@DELETE', {
|
|
param: {
|
|
playerModerationId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
API.updatePlayerModeration = function (ref) {
|
|
var ctx = this.playerModeration[ref.id];
|
|
if (ctx) {
|
|
Object.assign(ctx, ref);
|
|
} else {
|
|
ctx = {
|
|
id: ref.id,
|
|
type: '',
|
|
sourceUserId: '',
|
|
sourceDisplayName: '',
|
|
targetUserId: '',
|
|
targetDisplayName: '',
|
|
created: '',
|
|
...ref
|
|
};
|
|
this.playerModeration[ctx.id] = ctx;
|
|
}
|
|
ctx.expired_ = false;
|
|
return ctx;
|
|
};
|
|
|
|
API.getPlayerModerations = function () {
|
|
return this.call('auth/user/playermoderations', {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
json
|
|
};
|
|
this.$emit('PLAYER-MODERATION:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.getPlayerModerationsAgainstMe = function () {
|
|
return this.call('auth/user/playermoderated', {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
json
|
|
};
|
|
this.$emit('PLAYER-MODERATION:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
moderated: string,
|
|
type: string
|
|
}
|
|
*/
|
|
// old-way: POST auth/user/blocks {blocked:userId}
|
|
API.sendPlayerModeration = function (param) {
|
|
return this.call('auth/user/playermoderations', {
|
|
method: 'POST',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('PLAYER-MODERATION:SEND', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
moderated: string,
|
|
type: string
|
|
}
|
|
*/
|
|
// old-way: PUT auth/user/unblocks {blocked:userId}
|
|
API.deletePlayerModeration = function (param) {
|
|
return this.call('auth/user/unplayermoderate', {
|
|
method: 'PUT',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('PLAYER-MODERATION:DELETE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: Favorite
|
|
|
|
API.favorite = {};
|
|
API.favoriteGroup = {};
|
|
API.favoriteObject = {};
|
|
API.favoriteFriendGroups = [];
|
|
API.favoriteWorldGroups = [];
|
|
API.favoriteAvatarGroups = [];
|
|
API.isFavoriteLoading = false;
|
|
API.isFavoriteGroupLoading = false;
|
|
|
|
API.$on('LOGIN', () => {
|
|
API.favorite = {};
|
|
API.favoriteGroup = {};
|
|
API.favoriteObject = {};
|
|
API.favoriteFriendGroups = [];
|
|
API.favoriteWorldGroups = [];
|
|
API.favoriteAvatarGroups = [];
|
|
API.isFavoriteLoading = false;
|
|
API.isFavoriteGroupLoading = false;
|
|
API.refreshFavorite();
|
|
});
|
|
|
|
API.$on('FAVORITE', (args) => {
|
|
var ref = API.updateFavorite(args.json);
|
|
args.ref = ref;
|
|
if (!ref.hide_ &&
|
|
API.favoriteObject[ref.favoriteId] !== ref) {
|
|
API.favoriteObject[ref.favoriteId] = ref;
|
|
if (ref.type === 'friend') {
|
|
API.favoriteFriendGroups.find((ctx) => {
|
|
if (ctx.name === ref.group_) {
|
|
++ctx.count;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
} else if (ref.type === 'world') {
|
|
API.favoriteWorldGroups.find((ctx) => {
|
|
if (ctx.name === ref.group_) {
|
|
++ctx.count;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
} else if (ref.type === 'avatar') {
|
|
API.favoriteAvatarGroups.find((ctx) => {
|
|
if (ctx.name === ref.group_) {
|
|
++ctx.count;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
API.$on('FAVORITE:@DELETE', (args) => {
|
|
var { ref } = args;
|
|
if (API.favoriteObject[ref.favoriteId]) {
|
|
delete API.favoriteObject[ref.favoriteId];
|
|
if (ref.type === 'friend') {
|
|
API.favoriteFriendGroups.find((ctx) => {
|
|
if (ctx.name === ref.group_) {
|
|
--ctx.count;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
} else if (ref.type === 'world') {
|
|
API.favoriteWorldGroups.find((ctx) => {
|
|
if (ctx.name === ref.group_) {
|
|
--ctx.count;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
} else if (ref.type === 'avatar') {
|
|
API.favoriteAvatarGroups.find((ctx) => {
|
|
if (ctx.name === ref.group_) {
|
|
--ctx.count;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
API.$on('FAVORITE:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('FAVORITE', {
|
|
param: {
|
|
favoriteId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
API.$on('FAVORITE:ADD', (args) => {
|
|
API.$emit('FAVORITE', {
|
|
param: {
|
|
favoriteId: args.json.id
|
|
},
|
|
json: args.json
|
|
});
|
|
});
|
|
|
|
API.$on('FAVORITE:DELETE', (args) => {
|
|
API.handleDeleteFavorite(args.param.objectId);
|
|
});
|
|
|
|
API.$on('FAVORITE:GROUP', (args) => {
|
|
var ref = API.updateFavoriteGroup(args.json);
|
|
args.ref = ref;
|
|
if (!ref.hide_) {
|
|
if (ref.type === 'friend') {
|
|
API.favoriteFriendGroups.find((ctx) => {
|
|
if (ctx.name === ref.name) {
|
|
ctx.displayName = ref.displayName;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
} else if (ref.type === 'world') {
|
|
API.favoriteWorldGroups.find((ctx) => {
|
|
if (ctx.name === ref.name) {
|
|
ctx.displayName = ref.displayName;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
} else if (ref.type === 'avatar') {
|
|
API.favoriteAvatarGroups.find((ctx) => {
|
|
if (ctx.name === ref.name) {
|
|
ctx.displayName = ref.displayName;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
API.$on('FAVORITE:GROUP:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('FAVORITE:GROUP', {
|
|
param: {
|
|
favoriteGroupId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
API.$on('FAVORITE:GROUP:SAVE', (args) => {
|
|
API.$emit('FAVORITE:GROUP', {
|
|
param: {
|
|
favoriteGroupId: args.json.id
|
|
},
|
|
json: args.json
|
|
});
|
|
});
|
|
|
|
API.$on('FAVORITE:GROUP:CLEAR', (args) => {
|
|
API.handleClearFavoriteGroup(args.param.group);
|
|
});
|
|
|
|
API.$on('FAVORITE:FRIEND:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('USER', {
|
|
param: {
|
|
userId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
API.$on('FAVORITE:WORLD:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
if (json.id !== '???') {
|
|
// FIXME
|
|
// json.favoriteId로 따로 불러와야 하나?
|
|
// 근데 ???가 많으면 과다 요청이 될듯
|
|
API.$emit('WORLD', {
|
|
param: {
|
|
worldId: json.id
|
|
},
|
|
json
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
API.$on('FAVORITE:AVATAR:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
if (json.releaseStatus !== 'hidden') {
|
|
// NOTE: 얘는 또 더미 데이터로 옴
|
|
API.$emit('AVATAR', {
|
|
param: {
|
|
avatarId: json.id
|
|
},
|
|
json
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
API.markAllFavoritesAsExpired = function () {
|
|
for (var key in this.favorite) {
|
|
var ctx = this.favorite[key];
|
|
if (!ctx.hide_) {
|
|
ctx.expired_ = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
API.checkExpiredFavorites = function () {
|
|
for (var key in this.favorite) {
|
|
var ctx = this.favorite[key];
|
|
if (ctx.expired_ &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
this.$emit('FAVORITE:@DELETE', {
|
|
param: {
|
|
favoriteId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
API.refreshFavorite = function () {
|
|
if (!this.isFavoriteLoading) {
|
|
this.isFavoriteLoading = true;
|
|
this.markAllFavoritesAsExpired();
|
|
this.bulk({
|
|
fn: 'getFavorites',
|
|
N: -1,
|
|
param: {
|
|
n: 100,
|
|
offset: 0
|
|
},
|
|
done: (ok) => {
|
|
if (ok) {
|
|
this.checkExpiredFavorites();
|
|
}
|
|
this.refreshFavoriteGroup();
|
|
this.refreshFavoriteFriends();
|
|
this.refreshFavoriteWorlds();
|
|
this.refreshFavoriteAvatars();
|
|
this.isFavoriteLoading = false;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
API.refreshFavoriteFriends = function () {
|
|
var N = 0;
|
|
for (var key in this.favorite) {
|
|
var ctx = this.favorite[key];
|
|
if (ctx.type === 'friend' &&
|
|
!ctx.hide_) {
|
|
++N;
|
|
}
|
|
}
|
|
if (N) {
|
|
this.bulk({
|
|
fn: 'getFavoriteFriends',
|
|
N,
|
|
param: {
|
|
n: 100,
|
|
offset: 0
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
API.refreshFavoriteWorlds = function () {
|
|
var N = 0;
|
|
for (var key in this.favorite) {
|
|
var ctx = this.favorite[key];
|
|
if (ctx.type === 'world' &&
|
|
!ctx.hide_) {
|
|
++N;
|
|
}
|
|
}
|
|
if (N) {
|
|
this.bulk({
|
|
fn: 'getFavoriteWorlds',
|
|
N,
|
|
param: {
|
|
n: 100,
|
|
offset: 0
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
API.refreshFavoriteAvatars = function () {
|
|
var N = 0;
|
|
for (var key in this.favorite) {
|
|
var ctx = this.favorite[key];
|
|
if (ctx.type === 'avatar' &&
|
|
!ctx.hide_) {
|
|
++N;
|
|
}
|
|
}
|
|
if (N) {
|
|
this.bulk({
|
|
fn: 'getFavoriteAvatars',
|
|
N,
|
|
param: {
|
|
n: 100,
|
|
offset: 0
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
API.markAllFavoriteGroupsAsExpired = function () {
|
|
for (var key in this.favoriteGroup) {
|
|
var ctx = this.favoriteGroup[key];
|
|
if (!ctx.hide_) {
|
|
ctx.expired_ = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
API.checkExpiredFavoriteGroups = function () {
|
|
for (var key in this.favoriteGroup) {
|
|
var ctx = this.favoriteGroup[key];
|
|
if (ctx.expired_ &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
this.$emit('FAVORITE:GROUP:@DELETE', {
|
|
param: {
|
|
favoriteGroupId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
API.resetFavoriteGroup = function () {
|
|
var i;
|
|
// 96 = ['group_0', 'group_1', 'group_2'] x 32
|
|
this.favoriteFriendGroups = [];
|
|
for (i = 0; i < 3; ++i) {
|
|
this.favoriteFriendGroups.push({
|
|
type: 'friend',
|
|
name: `group_${i}`,
|
|
displayName: `Group ${i + 1}`,
|
|
capacity: 32,
|
|
count: 0
|
|
});
|
|
}
|
|
// 128 = ['worlds1', 'worlds2', 'worlds3', 'worlds4'] x 32
|
|
this.favoriteWorldGroups = [];
|
|
for (i = 0; i < 4; ++i) {
|
|
this.favoriteWorldGroups.push({
|
|
type: 'world',
|
|
name: `worlds${i + 1}`,
|
|
displayName: `Group ${i + 1}`,
|
|
capacity: 32,
|
|
count: 0
|
|
});
|
|
}
|
|
// 16 = ['avatars1'] x 16
|
|
this.favoriteAvatarGroups = [];
|
|
for (i = 0; i < 1; ++i) {
|
|
this.favoriteAvatarGroups.push({
|
|
type: 'avatar',
|
|
name: `avatars${i + 1}`,
|
|
displayName: `Group ${i + 1}`,
|
|
capacity: 16,
|
|
count: 0
|
|
});
|
|
}
|
|
};
|
|
|
|
API.assignFavoriteGroup = function () {
|
|
var assign = [];
|
|
var set1 = function (array, ref) {
|
|
if (!assign[ref.id]) {
|
|
array.find((ctx) => {
|
|
if (ctx.name === ref.name &&
|
|
!ctx.assign_) {
|
|
ctx.assign_ = true;
|
|
ctx.displayName = ref.displayName;
|
|
assign[ref.id] = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
};
|
|
var set2 = function (array, ref) {
|
|
if (!assign[ref.id]) {
|
|
array.find((ctx) => {
|
|
if (!ctx.assign_) {
|
|
ctx.assign_ = true;
|
|
ctx.name = ref.name;
|
|
ctx.displayName = ref.displayName;
|
|
assign[ref.id] = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
};
|
|
var key;
|
|
var ctx;
|
|
for (key in this.favoriteGroup) {
|
|
ctx = this.favoriteGroup[key];
|
|
if (!ctx.hide_) {
|
|
if (ctx.type === 'friend') {
|
|
set1(this.favoriteFriendGroups, ctx);
|
|
} else if (ctx.type === 'world') {
|
|
set1(this.favoriteWorldGroups, ctx);
|
|
} else if (ctx.type === 'avatar') {
|
|
set1(this.favoriteAvatarGroups, ctx);
|
|
}
|
|
}
|
|
}
|
|
for (key in this.favoriteGroup) {
|
|
ctx = this.favoriteGroup[key];
|
|
if (!ctx.hide_) {
|
|
if (ctx.type === 'friend') {
|
|
set2(this.favoriteFriendGroups, ctx);
|
|
} else if (ctx.type === 'world') {
|
|
set2(this.favoriteWorldGroups, ctx);
|
|
} else if (ctx.type === 'avatar') {
|
|
set2(this.favoriteAvatarGroups, ctx);
|
|
}
|
|
}
|
|
}
|
|
for (key in this.favorite) {
|
|
ctx = this.favorite[key];
|
|
if (!ctx.hide_) {
|
|
if (ctx.type === 'friend') {
|
|
// eslint-disable-next-line no-loop-func
|
|
this.favoriteFriendGroups.find((ref) => {
|
|
if (ref.name === ctx.group_) {
|
|
++ref.count;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
} else if (ctx.type === 'world') {
|
|
// eslint-disable-next-line no-loop-func
|
|
this.favoriteWorldGroups.find((ref) => {
|
|
if (ref.name === ctx.group_) {
|
|
++ref.count;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
} else if (ctx.type === 'avatar') {
|
|
// eslint-disable-next-line no-loop-func
|
|
this.favoriteAvatarGroups.find((ref) => {
|
|
if (ref.name === ctx.group_) {
|
|
++ref.count;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
API.refreshFavoriteGroup = function () {
|
|
if (!this.isFavoriteGroupLoading) {
|
|
this.isFavoriteGroupLoading = true;
|
|
this.markAllFavoriteGroupsAsExpired();
|
|
this.bulk({
|
|
fn: 'getFavoriteGroups',
|
|
N: -1,
|
|
param: {
|
|
n: 100,
|
|
offset: 0
|
|
},
|
|
done: (ok) => {
|
|
if (ok) {
|
|
this.checkExpiredFavoriteGroups();
|
|
this.resetFavoriteGroup();
|
|
this.assignFavoriteGroup();
|
|
}
|
|
this.isFavoriteGroupLoading = false;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
API.handleDeleteFavorite = function (objectId) {
|
|
for (var key in this.favorite) {
|
|
var ctx = this.favorite[key];
|
|
if (ctx.favoriteId === objectId &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
API.$emit('FAVORITE:@DELETE', {
|
|
param: {
|
|
favoriteId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
API.updateFavorite = function (ref) {
|
|
var ctx = this.favorite[ref.id];
|
|
if (ctx) {
|
|
Object.assign(ctx, ref);
|
|
} else {
|
|
ctx = {
|
|
id: ref.id,
|
|
type: '',
|
|
favoriteId: '',
|
|
tags: [],
|
|
// custom
|
|
group_: '',
|
|
//
|
|
...ref
|
|
};
|
|
this.favorite[ctx.id] = ctx;
|
|
}
|
|
ctx.expired_ = false;
|
|
if (ctx.tags) {
|
|
[ctx.group_] = ctx.tags;
|
|
}
|
|
return ctx;
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number,
|
|
type: string,
|
|
tag: string
|
|
}
|
|
*/
|
|
API.getFavorites = function (param) {
|
|
return this.call('favorites', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FAVORITE:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
type: string,
|
|
favoriteId: string (objectId),
|
|
tags: string
|
|
}
|
|
*/
|
|
API.addFavorite = function (param) {
|
|
return this.call('favorites', {
|
|
method: 'POST',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FAVORITE:ADD', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
objectId: string
|
|
}
|
|
*/
|
|
API.deleteFavorite = function (param) {
|
|
return this.call(`favorites/${param.objectId}`, {
|
|
method: 'DELETE'
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FAVORITE:DELETE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.updateFavoriteGroup = function (ref) {
|
|
var ctx = this.favoriteGroup[ref.id];
|
|
if (ctx) {
|
|
Object.assign(ctx, ref);
|
|
} else {
|
|
ctx = {
|
|
id: ref.id,
|
|
ownerId: '',
|
|
ownerDisplayName: '',
|
|
name: '',
|
|
displayName: '',
|
|
type: '',
|
|
visibility: '',
|
|
tags: [],
|
|
...ref
|
|
};
|
|
this.favoriteGroup[ctx.id] = ctx;
|
|
}
|
|
ctx.expired_ = false;
|
|
return ctx;
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number,
|
|
type: string
|
|
}
|
|
*/
|
|
API.getFavoriteGroups = function (param) {
|
|
return this.call('favorite/groups', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FAVORITE:GROUP:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
type: string,
|
|
group: string (name),
|
|
displayName: string,
|
|
visibility: string
|
|
}
|
|
*/
|
|
API.saveFavoriteGroup = function (param) {
|
|
return this.call(`favorite/group/${param.type}/${param.group}/${this.currentUser.id}`, {
|
|
method: 'PUT',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FAVORITE:GROUP:SAVE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
type: string,
|
|
group: string (name)
|
|
}
|
|
*/
|
|
API.clearFavoriteGroup = function (param) {
|
|
return this.call(`favorite/group/${param.type}/${param.group}/${this.currentUser.id}`, {
|
|
method: 'DELETE',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FAVORITE:GROUP:CLEAR', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.handleClearFavoriteGroup = function (name) {
|
|
for (var key in this.favorite) {
|
|
var ctx = this.favorite[key];
|
|
if (ctx.group_ === name &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
API.$emit('FAVORITE:@DELETE', {
|
|
param: {
|
|
favoriteId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number
|
|
}
|
|
*/
|
|
API.getFavoriteFriends = function (param) {
|
|
return this.call('auth/user/friends/favorite', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FAVORITE:FRIEND:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number
|
|
}
|
|
*/
|
|
API.getFavoriteWorlds = function (param) {
|
|
return this.call('worlds/favorites', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FAVORITE:WORLD:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number
|
|
}
|
|
*/
|
|
API.getFavoriteAvatars = function (param) {
|
|
return this.call('avatars/favorites', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FAVORITE:AVATAR:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: Feedback
|
|
|
|
API.feedback = {};
|
|
API.isFeedbackLoading = false;
|
|
|
|
API.$on('LOGIN', () => {
|
|
API.feedback = {};
|
|
API.isFeedbackLoading = false;
|
|
});
|
|
|
|
API.$on('FEEDBACK', (args) => {
|
|
args.ref = API.updateFeedback(args.json);
|
|
});
|
|
|
|
API.$on('FEEDBACK:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('FEEDBACK', {
|
|
param: {
|
|
feedbackId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
API.$on('FEEDBACK:DELETE', (args) => {
|
|
var ctx = API.feedback[args.param.feedbackId];
|
|
if (ctx &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
args.ref = ctx;
|
|
API.$emit('FEEDBACK:@DELETE', {
|
|
param: {
|
|
feedbackId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
});
|
|
|
|
API.markAllFeedbacksAsExpired = function () {
|
|
for (var key in this.feedback) {
|
|
var ctx = this.feedback[key];
|
|
if (!ctx.hide_) {
|
|
ctx.expired_ = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
API.checkExpiredFeedbacks = function () {
|
|
for (var key in this.feedback) {
|
|
var ctx = this.feedback[key];
|
|
if (ctx.expired_ &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
this.$emit('FEEDBACK:@DELETE', {
|
|
param: {
|
|
feedbackId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
API.refreshFeedback = function () {
|
|
if (!this.isFeedbackLoading) {
|
|
this.isFeedbackLoading = true;
|
|
this.markAllFeedbacksAsExpired();
|
|
this.bulk({
|
|
fn: 'getFeedbacks',
|
|
N: -1,
|
|
param: {
|
|
n: 100,
|
|
offset: 0
|
|
},
|
|
done: (ok) => {
|
|
if (ok) {
|
|
this.checkExpiredFeedbacks();
|
|
}
|
|
this.isFeedbackLoading = false;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
API.updateFeedback = function (ref) {
|
|
var ctx = this.feedback[ref.id];
|
|
if (ctx) {
|
|
Object.assign(ctx, ref);
|
|
} else {
|
|
ctx = {
|
|
id: ref.id,
|
|
type: '',
|
|
reason: '',
|
|
commenterId: '',
|
|
commenterName: '',
|
|
contentId: '',
|
|
contentType: '',
|
|
contentVersion: '',
|
|
contentName: '',
|
|
contentAuthorId: '',
|
|
contentAuthorName: '',
|
|
tags: [],
|
|
...ref
|
|
};
|
|
this.feedback[ctx.id] = ctx;
|
|
}
|
|
ctx.expired_ = false;
|
|
return ctx;
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number
|
|
}
|
|
*/
|
|
API.getFeedbacks = function (param) {
|
|
return this.call(`users/${this.currentUser.id}/feedback`, {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FEEDBACK:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
feedbackId: string
|
|
}
|
|
*/
|
|
API.deleteFeedback = function (param) {
|
|
return this.call(`feedback/${param.feedbackId}`, {
|
|
method: 'DELETE',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('FEEDBACK:DELETE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: Thing
|
|
|
|
API.thing = {};
|
|
API.isThingLoading = false;
|
|
|
|
API.$on('LOGIN', () => {
|
|
API.thing = {};
|
|
API.isThingLoading = false;
|
|
});
|
|
|
|
API.$on('THING', (args) => {
|
|
args.ref = API.updateThing(args.json);
|
|
});
|
|
|
|
API.$on('THING:LIST', (args) => {
|
|
args.json.forEach((json) => {
|
|
API.$emit('THING', {
|
|
param: {
|
|
thingId: json.id
|
|
},
|
|
json
|
|
});
|
|
});
|
|
});
|
|
|
|
API.$on('THING:ADD', (args) => {
|
|
API.$emit('THING', {
|
|
param: {
|
|
thingId: args.json.id
|
|
},
|
|
json: args.json
|
|
});
|
|
});
|
|
|
|
API.$on('THING:SAVE', (args) => {
|
|
API.$emit('THING', {
|
|
param: {
|
|
thingId: args.json.id
|
|
},
|
|
json: args.json
|
|
});
|
|
});
|
|
|
|
API.$on('THING:DELETE', (args) => {
|
|
var ctx = API.thing[args.param.thingId];
|
|
if (ctx &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
args.ref = ctx;
|
|
API.$emit('THING:@DELETE', {
|
|
param: {
|
|
thingId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
});
|
|
|
|
API.markAllThingsAsExpired = function () {
|
|
for (var key in this.thing) {
|
|
var ctx = this.thing[key];
|
|
if (!ctx.hide_) {
|
|
ctx.expired_ = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
API.checkExpiredThings = function () {
|
|
for (var key in this.thing) {
|
|
var ctx = this.thing[key];
|
|
if (ctx.expired_ &&
|
|
!ctx.hide_) {
|
|
ctx.hide_ = true;
|
|
this.$emit('THING:@DELETE', {
|
|
param: {
|
|
thingId: ctx.id
|
|
},
|
|
ref: ctx
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
API.refreshThing = function () {
|
|
if (!this.isThingLoading) {
|
|
this.isThingLoading = true;
|
|
this.markAllThingsAsExpired();
|
|
this.bulk({
|
|
fn: 'getThings',
|
|
N: -1,
|
|
param: {
|
|
n: 100,
|
|
offset: 0
|
|
},
|
|
done: (ok) => {
|
|
if (ok) {
|
|
this.checkExpiredThings();
|
|
}
|
|
this.isThingLoading = false;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
API.updateThing = function (ref) {
|
|
var ctx = this.thing[ref.id];
|
|
if (ctx) {
|
|
Object.assign(ctx, ref);
|
|
} else {
|
|
ctx = {
|
|
id: ref.id,
|
|
ownerId: '',
|
|
ownerDisplayName: '',
|
|
thingProperty: '',
|
|
otherThingProperty: '',
|
|
tags: [],
|
|
...ref
|
|
};
|
|
this.thing[ctx.id] = ctx;
|
|
}
|
|
ctx.expired_ = false;
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
n: number,
|
|
offset: number
|
|
}
|
|
*/
|
|
API.getThings = function (param) {
|
|
return this.call('things', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('THING:LIST', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
thingId: string,
|
|
thingProperty: string,
|
|
tags: string
|
|
}
|
|
*/
|
|
API.addThing = function (param) {
|
|
return this.call('things', {
|
|
method: 'POST',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('THING:ADD', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
thingId: string,
|
|
thingProperty: string,
|
|
tags: string
|
|
}
|
|
*/
|
|
API.saveThing = function (param) {
|
|
return this.call(`things/${param.thingId}`, {
|
|
method: 'PUT',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('THING:SAVE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
/*
|
|
param: {
|
|
thingId: string
|
|
}
|
|
*/
|
|
API.deleteThing = function (param) {
|
|
return this.call(`things/${param.thingId}`, {
|
|
method: 'DELETE',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('THING:DELETE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: WebSocket
|
|
|
|
API.webSocket = false;
|
|
|
|
API.$on('LOGOUT', () => {
|
|
API.closeWebSocket();
|
|
});
|
|
|
|
API.$on('USER:CURRENT', () => {
|
|
if (API.webSocket === false) {
|
|
API.getAuth();
|
|
}
|
|
});
|
|
|
|
API.$on('AUTH', (args) => {
|
|
if (args.json.ok) {
|
|
API.connectWebSocket(args.json.token);
|
|
}
|
|
});
|
|
|
|
API.$on('PIPELINE', (args) => {
|
|
var { type, content } = args.json;
|
|
switch (type) {
|
|
case 'notification':
|
|
API.$emit('NOTIFICATION', {
|
|
param: {
|
|
notificationId: content.id
|
|
},
|
|
json: content
|
|
});
|
|
break;
|
|
|
|
case 'friend-add':
|
|
API.$emit('USER', {
|
|
param: {
|
|
userId: content.userId
|
|
},
|
|
json: content.user
|
|
});
|
|
API.$emit('FRIEND:ADD', {
|
|
param: {
|
|
userId: content.userId
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'friend-delete':
|
|
API.$emit('FRIEND:DELETE', {
|
|
param: {
|
|
userId: content.userId
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'friend-online':
|
|
API.$emit('USER', {
|
|
param: {
|
|
userId: content.userId
|
|
},
|
|
json: {
|
|
location: content.location,
|
|
...content.user
|
|
}
|
|
});
|
|
API.$emit('FRIEND:STATE', {
|
|
param: {
|
|
userId: content.userId
|
|
},
|
|
json: {
|
|
state: 'online'
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'friend-active':
|
|
API.$emit('USER', {
|
|
param: {
|
|
userId: content.userId
|
|
},
|
|
json: content.user
|
|
});
|
|
API.$emit('FRIEND:STATE', {
|
|
param: {
|
|
userId: content.userId
|
|
},
|
|
json: {
|
|
state: 'active'
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'friend-offline':
|
|
API.$emit('FRIEND:STATE', {
|
|
param: {
|
|
userId: content.userId
|
|
},
|
|
json: {
|
|
state: 'offline'
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'friend-update':
|
|
API.$emit('USER', {
|
|
param: {
|
|
userId: content.userId
|
|
},
|
|
json: content.user
|
|
});
|
|
break;
|
|
|
|
case 'user-update':
|
|
API.$emit('USER:CURRENT', {
|
|
param: {
|
|
userId: content.userId
|
|
},
|
|
json: content.user
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
|
|
API.getAuth = function () {
|
|
return this.call('auth', {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
json
|
|
};
|
|
this.$emit('AUTH', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
API.connectWebSocket = function (token) {
|
|
if (this.webSocket === false) {
|
|
var socket = new WebSocket(`wss://pipeline.vrchat.cloud/?auth=${token}`);
|
|
socket.onclose = () => {
|
|
if (this.webSocket === socket) {
|
|
this.webSocket = false;
|
|
}
|
|
try {
|
|
socket.close();
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
};
|
|
socket.onerror = socket.onclose;
|
|
socket.onmessage = (e) => {
|
|
try {
|
|
var json = JSON.parse(e.data);
|
|
if (json.content) {
|
|
json.content = JSON.parse(json.content);
|
|
}
|
|
this.$emit('PIPELINE', {
|
|
json
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
};
|
|
this.webSocket = socket;
|
|
}
|
|
};
|
|
|
|
API.closeWebSocket = function () {
|
|
if (this.webSocket !== false) {
|
|
var socket = this.webSocket;
|
|
this.webSocket = false;
|
|
try {
|
|
socket.close();
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
}
|
|
};
|
|
|
|
// API: Visit
|
|
|
|
API.getVisits = function () {
|
|
return this.call('visits', {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
json
|
|
};
|
|
this.$emit('VISITS', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: Time
|
|
|
|
API.getServerTime = function () {
|
|
return this.call('time', {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
json
|
|
};
|
|
this.$emit('TIME', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: Youtube
|
|
|
|
/*
|
|
param = {
|
|
q: string,
|
|
type: string ('video'),
|
|
safeSearch: string ('strict'),
|
|
maxResults: number (25),
|
|
part: string ('snippet')
|
|
}
|
|
*/
|
|
API.youtube = function (param) {
|
|
return this.call('youtube', {
|
|
method: 'GET',
|
|
body: param
|
|
}).then((json) => {
|
|
var args = {
|
|
param,
|
|
json
|
|
};
|
|
this.$emit('YOUTUBE', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API: Events
|
|
|
|
// deprecated: moved to user's property
|
|
API.getEvents = function () {
|
|
return this.call('events', {
|
|
method: 'GET'
|
|
}).then((json) => {
|
|
var args = {
|
|
json
|
|
};
|
|
this.$emit('EVENTS', args);
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// API
|
|
|
|
var extractFileId = (s) => {
|
|
var match = String(s).match(/file_[0-9A-Za-z-]+/u);
|
|
return match
|
|
? match[0]
|
|
: '';
|
|
};
|
|
|
|
var buildTreeData = (json) => {
|
|
var node = [];
|
|
for (var key in json) {
|
|
var value = json[key];
|
|
if (typeof value === 'object') {
|
|
if (Array.isArray(value)) {
|
|
node.push({
|
|
children: value.map((val, idx) => {
|
|
if (typeof val === 'object') {
|
|
return {
|
|
children: buildTreeData(val),
|
|
key: idx
|
|
};
|
|
}
|
|
return {
|
|
key: idx,
|
|
value: val
|
|
};
|
|
}),
|
|
key
|
|
});
|
|
} else {
|
|
node.push({
|
|
children: buildTreeData(value),
|
|
key
|
|
});
|
|
}
|
|
} else {
|
|
node.push({
|
|
key,
|
|
value: String(value)
|
|
});
|
|
}
|
|
}
|
|
node.sort((a, b) => {
|
|
var A = String(a.key).toUpperCase();
|
|
var B = String(b.key).toUpperCase();
|
|
if (A < B) {
|
|
return -1;
|
|
}
|
|
if (A > B) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
return node;
|
|
};
|
|
|
|
// Misc
|
|
|
|
var $timers = [];
|
|
|
|
Vue.component('timer', {
|
|
template: '<span v-text="text"></span>',
|
|
props: {
|
|
epoch: {
|
|
type: Number,
|
|
default() {
|
|
return Date.now();
|
|
}
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
text: ''
|
|
};
|
|
},
|
|
methods: {
|
|
update() {
|
|
this.text = timeToText(Date.now() - this.epoch);
|
|
}
|
|
},
|
|
watch: {
|
|
date() {
|
|
this.update();
|
|
}
|
|
},
|
|
mounted() {
|
|
$timers.push(this);
|
|
this.update();
|
|
},
|
|
destroyed() {
|
|
removeFromArray($timers, this);
|
|
}
|
|
});
|
|
|
|
setInterval(() => {
|
|
$timers.forEach((v) => v.update());
|
|
}, 5000);
|
|
|
|
var $app = {
|
|
data: {
|
|
API,
|
|
VRCX,
|
|
nextRefresh: 0,
|
|
isGameRunning: false,
|
|
appVersion: '2019.08.17.1',
|
|
latestAppVersion: '',
|
|
ossDialog: false
|
|
},
|
|
computed: {},
|
|
methods: {},
|
|
watch: {},
|
|
el: '#x-app',
|
|
mounted() {
|
|
LogWatcher.Reset().then(() => {
|
|
API.$on('SHOW_WORLD_DIALOG', (tag) => this.showWorldDialog(tag));
|
|
API.$on('SHOW_LAUNCH_DIALOG', (tag) => this.showLaunchDialog(tag));
|
|
setInterval(() => this.update(), 1000);
|
|
this.update();
|
|
this.$nextTick(() => {
|
|
this.$el.style.display = '';
|
|
this.loginForm.loading = true;
|
|
API.getConfig().catch((err) => {
|
|
this.loginForm.loading = false;
|
|
throw err;
|
|
}).then((args) => {
|
|
API.getCurrentUser().finally(() => {
|
|
this.loginForm.loading = false;
|
|
});
|
|
return args;
|
|
});
|
|
});
|
|
});
|
|
this.checkAppVersion();
|
|
}
|
|
};
|
|
|
|
$app.methods.checkAppVersion = function () {
|
|
var url = 'https://api.github.com/repos/pypy-vrc/VRCX/releases/latest';
|
|
fetch(url).then((res) => res.json()).then((json) => {
|
|
if (json.name &&
|
|
json.published_at) {
|
|
this.latestAppVersion = `${json.name} (${formatDate(json.published_at, 'YYYY-MM-DD HH24:MI:SS')})`;
|
|
} else {
|
|
this.latestAppVersion = 'Error occured';
|
|
}
|
|
});
|
|
};
|
|
|
|
var insertOrUpdateArrayById = (array, json) => {
|
|
var insertOrUpdate = array.some((val, idx, arr) => {
|
|
if (val.id === json.id) {
|
|
$app.$set(arr, idx, json);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
if (!insertOrUpdate) {
|
|
array.push(json);
|
|
}
|
|
};
|
|
|
|
$app.methods.update = function () {
|
|
if (API.isLoggedIn) {
|
|
if (--this.nextRefresh <= 0) {
|
|
this.nextRefresh = 60;
|
|
API.getCurrentUser().catch((err1) => {
|
|
if (err1.status_code === 401) {
|
|
API.getConfig().then((args) => {
|
|
API.login({
|
|
username: this.loginForm.username,
|
|
password: this.loginForm.password
|
|
}).catch((err2) => {
|
|
if (err2.status_code === 401) {
|
|
API.logout();
|
|
}
|
|
throw err2;
|
|
});
|
|
return args;
|
|
});
|
|
}
|
|
throw err1;
|
|
});
|
|
}
|
|
this.refreshGameLog();
|
|
VRCX.IsGameRunning().then((running) => {
|
|
if (this.isGameRunning !== running) {
|
|
this.isGameRunning = running;
|
|
Discord.SetTimestamps(Date.now(), 0);
|
|
}
|
|
this.updateDiscord();
|
|
this.updateOpenVR();
|
|
});
|
|
}
|
|
};
|
|
|
|
$app.methods.updateSharedFeed = function () {
|
|
var arr = [];
|
|
var ref;
|
|
var i;
|
|
var j;
|
|
i = this.gameLogTable.data.length;
|
|
j = 0;
|
|
while (j < 25) {
|
|
if (i <= 0) {
|
|
break;
|
|
}
|
|
ref = this.gameLogTable.data[--i];
|
|
// Location, OnPlayerJoined, OnPlayerLeft
|
|
if (ref.type) {
|
|
// FIXME: 이거 존나 느릴거 같은데
|
|
var isFriend = false;
|
|
var isFavorite = false;
|
|
for (var key in API.user) {
|
|
var ctx = API.user[key];
|
|
if (ctx.displayName === ref.data) {
|
|
isFriend = Boolean(this.friend[ctx.id]);
|
|
isFavorite = Boolean(API.favoriteObject[ctx.id]);
|
|
break;
|
|
}
|
|
}
|
|
arr.push({
|
|
...ref,
|
|
isFriend,
|
|
isFavorite
|
|
});
|
|
} else {
|
|
arr.push(ref);
|
|
}
|
|
++j;
|
|
}
|
|
i = this.feedTable.data.length;
|
|
j = 0;
|
|
while (j < 25) {
|
|
if (i <= 0) {
|
|
break;
|
|
}
|
|
ref = this.feedTable.data[--i];
|
|
// GPS, Online, Offline, Status, Avatar
|
|
if (ref.type !== 'Avatar') {
|
|
arr.push({
|
|
...ref,
|
|
isFriend: Boolean(this.friend[ref.userId]),
|
|
isFavorite: Boolean(API.favoriteObject[ref.userId])
|
|
});
|
|
++j;
|
|
}
|
|
}
|
|
arr.sort((a, b) => {
|
|
if (a.created_at < b.created_at) {
|
|
return 1;
|
|
}
|
|
if (a.created_at > b.created_at) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
});
|
|
if (arr.length > 25) {
|
|
arr.length = 25;
|
|
}
|
|
VRCXStorage.SetArray('sharedFeeds', arr);
|
|
};
|
|
|
|
$app.methods.notifyMenu = function (index) {
|
|
if (this.$refs.menu.activeIndex !== index) {
|
|
var item = this.$refs.menu.items[index];
|
|
if (item) {
|
|
item.$el.classList.add('notify');
|
|
}
|
|
}
|
|
};
|
|
|
|
$app.methods.selectMenu = function (index) {
|
|
// NOTE
|
|
// 툴팁이 쌓여서 느려지기 때문에 날려줌.
|
|
// 근데 이 방법이 안전한지는 모르겠음
|
|
document.querySelectorAll('[role="tooltip"]').forEach((node) => {
|
|
node.remove();
|
|
});
|
|
var item = this.$refs.menu.items[index];
|
|
if (item) {
|
|
item.$el.classList.remove('notify');
|
|
}
|
|
};
|
|
|
|
$app.methods.showLaunchDialog = function (tag) {
|
|
var L = API.parseLocation(tag);
|
|
if (L.worldId) {
|
|
this.$msgbox({
|
|
title: 'Launch World',
|
|
message: `<span style="word-break:break-all;font-size:12px">${escapeTag(`https://vrchat.net/launch?worldId=${encodeURIComponent(L.worldId)}&instanceId=${encodeURIComponent(L.instanceId)}`)}</span>`,
|
|
dangerouslyUseHTMLString: true,
|
|
distinguishCancelAndClose: true,
|
|
showCancelButton: true,
|
|
cancelButtonText: 'Invite',
|
|
confirmButtonText: 'Launch',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
VRCX.StartGame(tag);
|
|
} else if (action === 'cancel') {
|
|
$app.showInviteDialog(tag);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
$app.methods.promptTOTP = function () {
|
|
this.$prompt('Enter a numeric code from your authenticator app', 'Two-factor Authentication', {
|
|
distinguishCancelAndClose: true,
|
|
cancelButtonText: 'Use OTP',
|
|
confirmButtonText: 'Verify',
|
|
inputPlaceholder: 'Code',
|
|
inputPattern: /^[0-9]{6}$/u,
|
|
inputErrorMessage: 'Invalid Code',
|
|
callback: (action, instance) => {
|
|
if (action === 'confirm') {
|
|
API.verifyTOTP({
|
|
code: instance.inputValue
|
|
}).catch((err) => {
|
|
if (err.status_code === 400) {
|
|
this.promptTOTP();
|
|
}
|
|
throw err;
|
|
}).then((args) => {
|
|
API.getCurrentUser();
|
|
return args;
|
|
});
|
|
} else if (action === 'cancel') {
|
|
this.promptOTP();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.promptOTP = function () {
|
|
this.$prompt('Enter one of your saved recovery codes', 'Two-factor Authentication', {
|
|
distinguishCancelAndClose: true,
|
|
cancelButtonText: 'Use TOTP',
|
|
confirmButtonText: 'Verify',
|
|
inputPlaceholder: 'Code',
|
|
inputPattern: /^[a-z0-9]{4}-[a-z0-9]{4}$/u,
|
|
inputErrorMessage: 'Invalid Code',
|
|
callback: (action, instance) => {
|
|
if (action === 'confirm') {
|
|
API.verifyOTP({
|
|
code: instance.inputValue
|
|
}).catch((err) => {
|
|
if (err.status_code === 400) {
|
|
this.promptOTP();
|
|
}
|
|
throw err;
|
|
}).then((args) => {
|
|
API.getCurrentUser();
|
|
return args;
|
|
});
|
|
} else if (action === 'cancel') {
|
|
this.promptTOTP();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
API.$on('USER:2FA', () => {
|
|
$app.promptTOTP();
|
|
});
|
|
|
|
API.$on('LOGOUT', () => {
|
|
new Noty({
|
|
type: 'success',
|
|
text: `See you again, <strong>${escapeTag(API.currentUser.displayName)}</strong>!`
|
|
}).show();
|
|
});
|
|
|
|
API.$on('LOGIN', (args) => {
|
|
$app.$refs.menu.activeIndex = 'feed';
|
|
new Noty({
|
|
type: 'success',
|
|
text: `Hello there, <strong>${escapeTag(args.ref.displayName)}</strong>!`
|
|
}).show();
|
|
});
|
|
|
|
$app.data.loginForm = {
|
|
loading: true,
|
|
username: '',
|
|
password: '',
|
|
rules: {
|
|
username: [
|
|
{
|
|
required: true,
|
|
trigger: 'blur'
|
|
}
|
|
],
|
|
password: [
|
|
{
|
|
required: true,
|
|
trigger: 'blur'
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
$app.methods.login = function () {
|
|
this.$refs.loginForm.validate((valid) => {
|
|
if (valid &&
|
|
!this.loginForm.loading) {
|
|
this.loginForm.loading = true;
|
|
API.getConfig().catch((err) => {
|
|
this.loginForm.loading = false;
|
|
throw err;
|
|
}).then((args) => {
|
|
API.login({
|
|
username: this.loginForm.username,
|
|
password: this.loginForm.password
|
|
}).finally(() => {
|
|
this.loginForm.loading = false;
|
|
});
|
|
return args;
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.loginWithSteam = function () {
|
|
if (!this.loginForm.loading) {
|
|
this.loginForm.loading = true;
|
|
VRCX.LoginWithSteam().catch((err) => {
|
|
this.loginForm.loading = false;
|
|
throw err;
|
|
}).then((steamTicket) => {
|
|
if (steamTicket) {
|
|
API.getConfig().catch((err) => {
|
|
this.loginForm.loading = false;
|
|
throw err;
|
|
}).then((args) => {
|
|
API.loginWithSteam({
|
|
steamTicket
|
|
}).finally(() => {
|
|
this.loginForm.loading = false;
|
|
});
|
|
return args;
|
|
});
|
|
} else {
|
|
this.loginForm.loading = false;
|
|
this.$message({
|
|
message: 'It only works when VRChat is running.',
|
|
type: 'error'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
$app.methods.loadMemo = function (id) {
|
|
return VRCXStorage.Get(`memo_${id}`);
|
|
};
|
|
|
|
$app.methods.saveMemo = function (id, memo) {
|
|
var key = `memo_${id}`;
|
|
if (memo) {
|
|
VRCXStorage.Set(key, String(memo));
|
|
} else {
|
|
VRCXStorage.Remove(key);
|
|
}
|
|
var ref = this.friend[id];
|
|
if (ref) {
|
|
ref.memo = String(memo || '');
|
|
}
|
|
};
|
|
|
|
// App: Friends
|
|
|
|
$app.data.friend = {};
|
|
$app.data.friendNo = 0;
|
|
$app.data.isFriendGroup0 = true;
|
|
$app.data.isFriendGroup1 = true;
|
|
$app.data.isFriendGroup2 = true;
|
|
$app.data.isFriendGroup3 = false;
|
|
$app.data.friendGroup0_ = [];
|
|
$app.data.friendGroup1_ = [];
|
|
$app.data.friendGroup2_ = [];
|
|
$app.data.friendGroup3_ = [];
|
|
$app.data.friendGroupA_ = [];
|
|
$app.data.friendGroupB_ = [];
|
|
$app.data.friendGroupC_ = [];
|
|
$app.data.friendGroupD_ = [];
|
|
$app.data.sortFriendGroup0 = false;
|
|
$app.data.sortFriendGroup1 = false;
|
|
$app.data.sortFriendGroup2 = false;
|
|
$app.data.sortFriendGroup3 = false;
|
|
$app.data.orderFriendGroup0 = VRCXStorage.GetBool('orderFriendGroup0');
|
|
$app.data.orderFriendGroup1 = VRCXStorage.GetBool('orderFriendGroup1');
|
|
$app.data.orderFriendGroup2 = VRCXStorage.GetBool('orderFriendGroup2');
|
|
$app.data.orderFriendGroup3 = VRCXStorage.GetBool('orderFriendGroup3');
|
|
var saveOrderFriendGroup = function () {
|
|
VRCXStorage.SetBool('orderFriendGroup0', this.orderFriendGroup0);
|
|
VRCXStorage.SetBool('orderFriendGroup1', this.orderFriendGroup1);
|
|
VRCXStorage.SetBool('orderFriendGroup2', this.orderFriendGroup2);
|
|
VRCXStorage.SetBool('orderFriendGroup3', this.orderFriendGroup3);
|
|
};
|
|
$app.watch.orderFriendGroup0 = saveOrderFriendGroup;
|
|
$app.watch.orderFriendGroup1 = saveOrderFriendGroup;
|
|
$app.watch.orderFriendGroup2 = saveOrderFriendGroup;
|
|
$app.watch.orderFriendGroup3 = saveOrderFriendGroup;
|
|
|
|
API.$on('LOGIN', () => {
|
|
$app.friend = {};
|
|
$app.friendNo = 0;
|
|
$app.isFriendGroup0 = true;
|
|
$app.isFriendGroup1 = true;
|
|
$app.isFriendGroup2 = true;
|
|
$app.isFriendGroup3 = false;
|
|
$app.friendGroup0_ = [];
|
|
$app.friendGroup1_ = [];
|
|
$app.friendGroup2_ = [];
|
|
$app.friendGroup3_ = [];
|
|
$app.friendGroupA_ = [];
|
|
$app.friendGroupB_ = [];
|
|
$app.friendGroupC_ = [];
|
|
$app.friendGroupD_ = [];
|
|
$app.sortFriendGroup0 = false;
|
|
$app.sortFriendGroup1 = false;
|
|
$app.sortFriendGroup2 = false;
|
|
$app.sortFriendGroup3 = false;
|
|
});
|
|
|
|
API.$on('USER:CURRENT', (args) => {
|
|
// initFriendship()이 LOGIN에서 처리되기 때문에
|
|
// USER:CURRENT에서 처리를 함
|
|
$app.refreshFriend(args.ref, args.origin);
|
|
});
|
|
|
|
API.$on('USER', (args) => {
|
|
$app.updateFriend(args.ref.id);
|
|
});
|
|
|
|
API.$on('FRIEND:ADD', (args) => {
|
|
$app.addFriend(args.param.userId);
|
|
});
|
|
|
|
API.$on('FRIEND:DELETE', (args) => {
|
|
$app.removeFriend(args.param.userId);
|
|
});
|
|
|
|
API.$on('FRIEND:STATE', (args) => {
|
|
$app.updateFriend(args.param.userId, args.json.state);
|
|
});
|
|
|
|
API.$on('FAVORITE', (args) => {
|
|
$app.updateFriend(args.ref.favoriteId);
|
|
});
|
|
|
|
API.$on('FAVORITE:@DELETE', (args) => {
|
|
$app.updateFriend(args.ref.favoriteId);
|
|
});
|
|
|
|
$app.methods.refreshFriend = function (ref, origin) {
|
|
var map = {};
|
|
ref.friends.forEach((id) => {
|
|
map[id] = 'offline';
|
|
});
|
|
ref.offlineFriends.forEach((id) => {
|
|
map[id] = 'offline';
|
|
});
|
|
ref.activeFriends.forEach((id) => {
|
|
map[id] = 'active';
|
|
});
|
|
ref.onlineFriends.forEach((id) => {
|
|
map[id] = 'online';
|
|
});
|
|
var key;
|
|
for (key in map) {
|
|
if (this.friend[key]) {
|
|
this.updateFriend(key, map[key], origin);
|
|
} else {
|
|
this.addFriend(key, map[key]);
|
|
}
|
|
}
|
|
for (key in this.friend) {
|
|
if (!map[key]) {
|
|
this.removeFriend(key);
|
|
}
|
|
}
|
|
if (origin) {
|
|
API.refreshFriend();
|
|
}
|
|
};
|
|
|
|
$app.methods.addFriend = function (id, state) {
|
|
if (!this.friend[id]) {
|
|
var ref = API.user[id];
|
|
var ctx = {
|
|
id,
|
|
state: state || 'offline',
|
|
ref,
|
|
vip: Boolean(API.favoriteObject[id]),
|
|
name: '',
|
|
no: ++this.friendNo,
|
|
memo: this.loadMemo(id)
|
|
};
|
|
if (ref) {
|
|
ctx.name = ref.name;
|
|
} else {
|
|
ref = this.friendLog[id];
|
|
if (ref &&
|
|
ref.displayName) {
|
|
ctx.name = ref.displayName;
|
|
}
|
|
}
|
|
this.$set(this.friend, id, ctx);
|
|
if (ctx.state === 'online') {
|
|
if (ctx.vip) {
|
|
this.sortFriendGroup0 = true;
|
|
this.friendGroup0_.push(ctx);
|
|
this.friendGroupA_.unshift(ctx);
|
|
} else {
|
|
this.sortFriendGroup1 = true;
|
|
this.friendGroup1_.push(ctx);
|
|
this.friendGroupB_.unshift(ctx);
|
|
}
|
|
} else if (ctx.state === 'active') {
|
|
this.sortFriendGroup2 = true;
|
|
this.friendGroup2_.push(ctx);
|
|
this.friendGroupC_.unshift(ctx);
|
|
} else {
|
|
this.sortFriendGroup3 = true;
|
|
this.friendGroup3_.push(ctx);
|
|
this.friendGroupD_.unshift(ctx);
|
|
}
|
|
}
|
|
};
|
|
|
|
$app.methods.removeFriend = function (id) {
|
|
var ctx = this.friend[id];
|
|
if (ctx) {
|
|
this.$delete(this.friend, id);
|
|
if (ctx.state === 'online') {
|
|
if (ctx.vip) {
|
|
removeFromArray(this.friendGroup0_, ctx);
|
|
removeFromArray(this.friendGroupA_, ctx);
|
|
} else {
|
|
removeFromArray(this.friendGroup1_, ctx);
|
|
removeFromArray(this.friendGroupB_, ctx);
|
|
}
|
|
} else if (ctx.state === 'active') {
|
|
removeFromArray(this.friendGroup2_, ctx);
|
|
removeFromArray(this.friendGroupC_, ctx);
|
|
} else {
|
|
removeFromArray(this.friendGroup3_, ctx);
|
|
removeFromArray(this.friendGroupD_, ctx);
|
|
}
|
|
}
|
|
};
|
|
|
|
$app.methods.updateFriend = function (id, state, origin) {
|
|
var ctx = this.friend[id];
|
|
if (ctx) {
|
|
var ref = API.user[id];
|
|
var vip = Boolean(API.favoriteObject[id]);
|
|
if (state === undefined ||
|
|
ctx.state === state) {
|
|
if (ctx.ref !== ref) {
|
|
ctx.ref = ref;
|
|
// NOTE
|
|
// AddFriend (CurrentUser) 이후,
|
|
// 서버에서 오는 순서라고 보면 될 듯.
|
|
if (ctx.state === 'online') {
|
|
if (ctx.vip) {
|
|
removeFromArray(this.friendGroupA_, ctx);
|
|
this.friendGroupA_.push(ctx);
|
|
} else {
|
|
removeFromArray(this.friendGroupB_, ctx);
|
|
this.friendGroupB_.push(ctx);
|
|
}
|
|
} else if (ctx.state === 'active') {
|
|
removeFromArray(this.friendGroupC_, ctx);
|
|
this.friendGroupC_.push(ctx);
|
|
} else {
|
|
removeFromArray(this.friendGroupD_, ctx);
|
|
this.friendGroupD_.push(ctx);
|
|
}
|
|
}
|
|
if (ctx.vip !== vip) {
|
|
ctx.vip = vip;
|
|
if (ctx.state === 'online') {
|
|
if (ctx.vip) {
|
|
removeFromArray(this.friendGroup1_, ctx);
|
|
removeFromArray(this.friendGroupB_, ctx);
|
|
this.sortFriendGroup0 = true;
|
|
this.friendGroup0_.push(ctx);
|
|
this.friendGroupA_.unshift(ctx);
|
|
} else {
|
|
removeFromArray(this.friendGroup0_, ctx);
|
|
removeFromArray(this.friendGroupA_, ctx);
|
|
this.sortFriendGroup1 = true;
|
|
this.friendGroup1_.push(ctx);
|
|
this.friendGroupB_.unshift(ctx);
|
|
}
|
|
}
|
|
}
|
|
if (ctx.ref &&
|
|
ctx.name !== ctx.ref.displayName) {
|
|
ctx.name = ctx.ref.displayName;
|
|
if (ctx.state === 'online') {
|
|
if (ctx.vip) {
|
|
this.sortFriendGroup0 = true;
|
|
} else {
|
|
this.sortFriendGroup1 = true;
|
|
}
|
|
} else if (ctx.state === 'active') {
|
|
this.sortFriendGroup2 = true;
|
|
} else {
|
|
this.sortFriendGroup3 = true;
|
|
}
|
|
}
|
|
// FIXME: 도배 가능성 있음
|
|
if (origin &&
|
|
ctx.state !== 'online' &&
|
|
ctx.ref &&
|
|
ctx.ref.location !== '' &&
|
|
ctx.ref.location !== 'offline') {
|
|
API.getUser({
|
|
userId: id
|
|
});
|
|
}
|
|
} else {
|
|
if (ctx.state === 'online') {
|
|
if (ctx.vip) {
|
|
removeFromArray(this.friendGroup0_, ctx);
|
|
removeFromArray(this.friendGroupA_, ctx);
|
|
} else {
|
|
removeFromArray(this.friendGroup1_, ctx);
|
|
removeFromArray(this.friendGroupB_, ctx);
|
|
}
|
|
} else if (ctx.state === 'active') {
|
|
removeFromArray(this.friendGroup2_, ctx);
|
|
removeFromArray(this.friendGroupC_, ctx);
|
|
} else {
|
|
removeFromArray(this.friendGroup3_, ctx);
|
|
removeFromArray(this.friendGroupD_, ctx);
|
|
}
|
|
ctx.state = state;
|
|
if (ctx.ref !== ref) {
|
|
ctx.ref = ref;
|
|
}
|
|
if (ctx.vip !== vip) {
|
|
ctx.vip = vip;
|
|
}
|
|
if (ctx.ref &&
|
|
ctx.name !== ctx.ref.displayName) {
|
|
ctx.name = ctx.ref.displayName;
|
|
}
|
|
if (ctx.state === 'online') {
|
|
if (ctx.vip) {
|
|
this.sortFriendGroup0 = true;
|
|
this.friendGroup0_.push(ctx);
|
|
this.friendGroupA_.unshift(ctx);
|
|
} else {
|
|
this.sortFriendGroup1 = true;
|
|
this.friendGroup1_.push(ctx);
|
|
this.friendGroupB_.unshift(ctx);
|
|
}
|
|
} else if (ctx.state === 'active') {
|
|
this.sortFriendGroup2 = true;
|
|
this.friendGroup2_.push(ctx);
|
|
this.friendGroupC_.unshift(ctx);
|
|
} else {
|
|
this.sortFriendGroup3 = true;
|
|
this.friendGroup3_.push(ctx);
|
|
this.friendGroupD_.unshift(ctx);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var sortFriendByName = (a, b) => {
|
|
var A = String(a.name).toUpperCase();
|
|
var B = String(b.name).toUpperCase();
|
|
if (A < B) {
|
|
return -1;
|
|
}
|
|
if (A > B) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
$app.computed.friendGroup0 = function () {
|
|
if (this.orderFriendGroup0) {
|
|
return this.friendGroupA_;
|
|
}
|
|
if (this.sortFriendGroup0) {
|
|
this.sortFriendGroup0 = false;
|
|
this.friendGroup0_.sort(sortFriendByName);
|
|
}
|
|
return this.friendGroup0_;
|
|
};
|
|
|
|
$app.computed.friendGroup1 = function () {
|
|
if (this.orderFriendGroup1) {
|
|
return this.friendGroupB_;
|
|
}
|
|
if (this.sortFriendGroup1) {
|
|
this.sortFriendGroup1 = false;
|
|
this.friendGroup1_.sort(sortFriendByName);
|
|
}
|
|
return this.friendGroup1_;
|
|
};
|
|
|
|
$app.computed.friendGroup2 = function () {
|
|
if (this.orderFriendGroup2) {
|
|
return this.friendGroupC_;
|
|
}
|
|
if (this.sortFriendGroup2) {
|
|
this.sortFriendGroup2 = false;
|
|
this.friendGroup2_.sort(sortFriendByName);
|
|
}
|
|
return this.friendGroup2_;
|
|
};
|
|
|
|
$app.computed.friendGroup3 = function () {
|
|
if (this.orderFriendGroup3) {
|
|
return this.friendGroupD_;
|
|
}
|
|
if (this.sortFriendGroup3) {
|
|
this.sortFriendGroup3 = false;
|
|
this.friendGroup3_.sort(sortFriendByName);
|
|
}
|
|
return this.friendGroup3_;
|
|
};
|
|
|
|
$app.methods.userStatusClass = function (user) {
|
|
var style = {};
|
|
if (user) {
|
|
if (user.location === 'offline') {
|
|
style.offline = true;
|
|
} else if (user.status === 'active') {
|
|
style.active = true;
|
|
} else if (user.status === 'join me') {
|
|
style.joinme = true;
|
|
} else if (user.status === 'busy') {
|
|
style.busy = true;
|
|
}
|
|
}
|
|
return style;
|
|
};
|
|
|
|
$app.methods.deleteFriend = function (id) {
|
|
// FIXME: 메시지 수정
|
|
this.$confirm('Continue? Delete Friend', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
API.deleteFriend({
|
|
userId: id
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// App: Quick Search
|
|
|
|
$app.data.quickSearch = '';
|
|
$app.data.quickSearchItems = [];
|
|
|
|
$app.methods.quickSearchRemoteMethod = function (query) {
|
|
this.quickSearchItems = [];
|
|
if (query) {
|
|
var QUERY = query.toUpperCase();
|
|
for (var key in this.friend) {
|
|
var ctx = this.friend[key];
|
|
if (ctx.ref) {
|
|
var NAME = ctx.name.toUpperCase();
|
|
var match = NAME.includes(QUERY);
|
|
if (!match) {
|
|
var uname = String(ctx.ref.username);
|
|
match = uname.toUpperCase().includes(QUERY) &&
|
|
!uname.startsWith('steam_');
|
|
}
|
|
if (!match &&
|
|
ctx.memo) {
|
|
match = String(ctx.memo).toUpperCase().includes(QUERY);
|
|
}
|
|
if (match) {
|
|
this.quickSearchItems.push({
|
|
value: ctx.id,
|
|
label: ctx.name,
|
|
ref: ctx.ref,
|
|
NAME
|
|
});
|
|
}
|
|
}
|
|
}
|
|
this.quickSearchItems.sort((a, b) => {
|
|
var A = a.NAME.startsWith(QUERY);
|
|
var B = b.NAME.startsWith(QUERY);
|
|
if (A !== B) {
|
|
if (A) {
|
|
return -1;
|
|
}
|
|
if (B) {
|
|
return 1;
|
|
}
|
|
}
|
|
if (a.NAME < b.NAME) {
|
|
return -1;
|
|
}
|
|
if (a.NAME > b.NAME) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
if (this.quickSearchItems.length > 4) {
|
|
this.quickSearchItems.length = 4;
|
|
}
|
|
this.quickSearchItems.push({
|
|
value: `search:${query}`,
|
|
label: query
|
|
});
|
|
}
|
|
};
|
|
|
|
$app.methods.quickSearchChange = function (value) {
|
|
if (value) {
|
|
if (value.startsWith('search:')) {
|
|
this.searchText = value.substr(7);
|
|
this.search();
|
|
this.$refs.menu.activeIndex = 'search';
|
|
} else {
|
|
this.showUserDialog(value);
|
|
}
|
|
}
|
|
};
|
|
|
|
// NOTE: 그냥 열고 닫고 했을때 changed 이벤트 발생이 안되기 때문에 넣음
|
|
$app.methods.quickSearchVisibleChange = function (value) {
|
|
if (value) {
|
|
this.quickSearch = '';
|
|
}
|
|
};
|
|
|
|
// App: Feed
|
|
|
|
$app.data.feedTable = {
|
|
data: [],
|
|
filters: [
|
|
{
|
|
prop: 'type',
|
|
value: [],
|
|
filterFn: (row, filter) => filter.value.some((v) => v === row.type)
|
|
},
|
|
{
|
|
prop: 'displayName',
|
|
value: ''
|
|
},
|
|
{
|
|
prop: 'userId',
|
|
value: false,
|
|
filterFn: (row, filter) => !filter.value || Boolean(API.favoriteObject[row.userId])
|
|
}
|
|
],
|
|
tableProps: {
|
|
stripe: true,
|
|
size: 'mini',
|
|
defaultSort: {
|
|
prop: 'created_at',
|
|
order: 'descending'
|
|
}
|
|
},
|
|
pageSize: 10,
|
|
paginationProps: {
|
|
small: true,
|
|
layout: 'sizes,prev,pager,next,total',
|
|
pageSizes: [
|
|
10,
|
|
25,
|
|
50,
|
|
100
|
|
]
|
|
}
|
|
};
|
|
|
|
API.$on('LOGIN', (args) => {
|
|
$app.feedTable.data = VRCXStorage.GetArray(`${args.ref.id}_feedTable`);
|
|
$app.sweepFeed();
|
|
});
|
|
|
|
API.$on('USER:UPDATE', (args) => {
|
|
var { ref, prop } = args;
|
|
if ($app.friend[ref.id]) {
|
|
if (prop.location) {
|
|
if (prop.location[0] === 'offline') {
|
|
$app.addFeed('Offline', ref, {
|
|
location: prop.location[1],
|
|
time: prop.location[2]
|
|
});
|
|
} else if (prop.location[1] === 'offline') {
|
|
$app.addFeed('Online', ref, {
|
|
location: prop.location[0]
|
|
});
|
|
} else {
|
|
$app.addFeed('GPS', ref, {
|
|
location: [
|
|
prop.location[0],
|
|
prop.location[1]
|
|
],
|
|
time: prop.location[2]
|
|
});
|
|
}
|
|
}
|
|
if (prop.currentAvatarThumbnailImageUrl) {
|
|
$app.addFeed('Avatar', ref, {
|
|
avatar: prop.currentAvatarThumbnailImageUrl
|
|
});
|
|
}
|
|
if (prop.status ||
|
|
prop.statusDescription) {
|
|
$app.addFeed('Status', ref, {
|
|
status: [
|
|
{
|
|
status: prop.status
|
|
? prop.status[0]
|
|
: ref.status,
|
|
statusDescription: prop.statusDescription
|
|
? prop.statusDescription[0]
|
|
: ref.statusDescription
|
|
},
|
|
{
|
|
status: prop.status
|
|
? prop.status[1]
|
|
: ref.status,
|
|
statusDescription: prop.statusDescription
|
|
? prop.statusDescription[1]
|
|
: ref.statusDescription
|
|
}
|
|
]
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
var saveFeedTimer = false;
|
|
$app.methods.saveFeed = function () {
|
|
if (saveFeedTimer === false) {
|
|
saveFeedTimer = setTimeout(() => {
|
|
saveFeedTimer = false;
|
|
VRCXStorage.SetArray(`${API.currentUser.id}_feedTable`, this.feedTable.data);
|
|
}, 1);
|
|
}
|
|
};
|
|
|
|
$app.methods.addFeed = function (type, ref, extra) {
|
|
this.feedTable.data.push({
|
|
created_at: new Date().toJSON(),
|
|
type,
|
|
userId: ref.id,
|
|
displayName: ref.displayName,
|
|
...extra
|
|
});
|
|
this.sweepFeed();
|
|
this.saveFeed();
|
|
this.notifyMenu('feed');
|
|
};
|
|
|
|
$app.methods.clearFeed = function () {
|
|
// FIXME: 메시지 수정
|
|
this.$confirm('Continue? Clear Feed', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
// 필터된 데이터만 삭제 하려면.. 허어
|
|
var T = this.feedTable;
|
|
T.data = T.data.filter((row) => !T.filters.every((filter) => {
|
|
if (filter.value) {
|
|
if (!Array.isArray(filter.value)) {
|
|
if (filter.filterFn) {
|
|
return filter.filterFn(row, filter);
|
|
}
|
|
return String(row[filter.prop]).toUpperCase().includes(String(filter.value).toUpperCase());
|
|
}
|
|
if (filter.value.length) {
|
|
if (filter.filterFn) {
|
|
return filter.filterFn(row, filter);
|
|
}
|
|
var prop = String(row[filter.prop]).toUpperCase();
|
|
return filter.value.some((v) => prop.includes(String(v).toUpperCase()));
|
|
}
|
|
}
|
|
return true;
|
|
}));
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.sweepFeed = function () {
|
|
var array = this.feedTable.data;
|
|
// 로그는 3일까지만 남김
|
|
var limit = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toJSON();
|
|
var i = 0;
|
|
var j = array.length;
|
|
while (i < j &&
|
|
array[i].created_at < limit) {
|
|
++i;
|
|
}
|
|
if (i === j) {
|
|
this.feedTable.data = [];
|
|
} else if (i) {
|
|
array.splice(0, i);
|
|
}
|
|
};
|
|
|
|
// App: gameLog
|
|
|
|
$app.data.lastLocation = '';
|
|
$app.data.lastLocation_ = {};
|
|
$app.data.discordActive = VRCXStorage.GetBool('discordActive');
|
|
$app.data.discordInstance = VRCXStorage.GetBool('discordInstance');
|
|
var saveDiscordOption = function () {
|
|
VRCXStorage.SetBool('discordActive', this.discordActive);
|
|
VRCXStorage.SetBool('discordInstance', this.discordInstance);
|
|
};
|
|
$app.watch.discordActive = saveDiscordOption;
|
|
$app.watch.discordInstance = saveDiscordOption;
|
|
|
|
$app.data.gameLogTable = {
|
|
data: [],
|
|
filters: [
|
|
{
|
|
prop: 'type',
|
|
value: [],
|
|
filterFn: (row, filter) => filter.value.some((v) => v === row.type)
|
|
},
|
|
{
|
|
prop: 'detail',
|
|
value: ''
|
|
}
|
|
],
|
|
tableProps: {
|
|
stripe: true,
|
|
size: 'mini',
|
|
defaultSort: {
|
|
prop: 'created_at',
|
|
order: 'descending'
|
|
}
|
|
},
|
|
pageSize: 10,
|
|
paginationProps: {
|
|
small: true,
|
|
layout: 'sizes,prev,pager,next,total',
|
|
pageSizes: [
|
|
10,
|
|
25,
|
|
50,
|
|
100
|
|
]
|
|
}
|
|
};
|
|
|
|
$app.methods.resetGameLog = function () {
|
|
LogWatcher.Reset().then(() => {
|
|
this.gameLogTable.data = [];
|
|
});
|
|
};
|
|
|
|
$app.methods.refreshGameLog = function () {
|
|
LogWatcher.HasLog().then((result) => {
|
|
if (result) {
|
|
LogWatcher.GetLogs().then((logs) => {
|
|
logs.forEach((log) => {
|
|
var ctx = {
|
|
created_at: log[0],
|
|
type: log[1],
|
|
data: log[2]
|
|
};
|
|
this.gameLogTable.data.push(ctx);
|
|
if (ctx.type === 'Location') {
|
|
this.lastLocation = ctx.data;
|
|
}
|
|
});
|
|
this.sweepGameLog();
|
|
this.updateSharedFeed();
|
|
// sweepGameLog로 기록이 삭제되면
|
|
// 아무 것도 없는데 알림이 떠서 이상함
|
|
if (this.gameLogTable.length) {
|
|
this.notifyMenu('gameLog');
|
|
}
|
|
});
|
|
} else {
|
|
this.updateSharedFeed();
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.sweepGameLog = function () {
|
|
var array = this.gameLogTable.data;
|
|
// 로그는 3일까지만 남김
|
|
var limit = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toJSON();
|
|
var i = 0;
|
|
var j = array.length;
|
|
while (i < j &&
|
|
array[i].created_at < limit) {
|
|
++i;
|
|
}
|
|
if (i === j) {
|
|
this.gameLogTable.data = [];
|
|
} else if (i) {
|
|
array.splice(0, i);
|
|
}
|
|
};
|
|
|
|
$app.methods.updateDiscord = function () {
|
|
if (this.isGameRunning &&
|
|
this.lastLocation) {
|
|
if (this.lastLocation !== this.lastLocation_.tag) {
|
|
var L = API.parseLocation(this.lastLocation);
|
|
L.worldName = L.worldId;
|
|
this.lastLocation_ = L;
|
|
if (L.worldId) {
|
|
var ref = API.world[L.worldId];
|
|
if (ref) {
|
|
L.worldName = ref.name;
|
|
} else {
|
|
API.getWorld({
|
|
worldId: L.worldId
|
|
}).then((args) => {
|
|
L.worldName = args.ref.name;
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
// NOTE
|
|
// 글자 수가 짧으면 업데이트가 안된다..
|
|
var LL = this.lastLocation_;
|
|
if (LL.worldName.length < 2) {
|
|
LL.worldName += '\uFFA0'.repeat(2 - LL.worldName.length);
|
|
}
|
|
if (this.discordInstance) {
|
|
Discord.SetText(LL.worldName, `#${LL.instanceName} ${LL.accessType}`);
|
|
} else {
|
|
Discord.SetText(LL.worldName, '');
|
|
}
|
|
Discord.SetActive(this.discordActive);
|
|
} else {
|
|
Discord.SetActive(false);
|
|
}
|
|
};
|
|
|
|
$app.methods.lookupUser = function (name) {
|
|
for (var key in API.user) {
|
|
var ctx = API.user[key];
|
|
if (ctx.displayName === name) {
|
|
this.showUserDialog(ctx.id);
|
|
return;
|
|
}
|
|
}
|
|
this.searchText = name;
|
|
this.search();
|
|
this.$refs.menu.activeIndex = 'search';
|
|
this.$refs.searchTab.currentName = '0';
|
|
};
|
|
|
|
// App: Search
|
|
|
|
$app.data.searchText = '';
|
|
$app.data.searchUsers = [];
|
|
$app.data.searchUserParam = {};
|
|
$app.data.searchWorlds = [];
|
|
$app.data.searchWorldOption = '';
|
|
$app.data.searchWorldParam = {};
|
|
$app.data.searchAvatars = [];
|
|
$app.data.searchAvatarParam = {};
|
|
$app.data.isSearchUserLoading = false;
|
|
$app.data.isSearchWorldLoading = false;
|
|
$app.data.isSearchAvatarLoading = false;
|
|
|
|
API.$on('LOGIN', () => {
|
|
$app.searchText = '';
|
|
$app.searchUsers = [];
|
|
$app.searchUserParam = {};
|
|
$app.searchWorlds = [];
|
|
$app.searchWorldOption = '';
|
|
$app.searchWorldParam = {};
|
|
$app.searchAvatars = [];
|
|
$app.searchAvatarParam = {};
|
|
$app.isSearchUserLoading = false;
|
|
$app.isSearchWorldLoading = false;
|
|
$app.isSearchAvatarLoading = false;
|
|
});
|
|
|
|
$app.methods.clearSearch = function () {
|
|
this.searchUsers = [];
|
|
this.searchWorlds = [];
|
|
this.searchAvatars = [];
|
|
};
|
|
|
|
$app.methods.search = function () {
|
|
this.searchUser();
|
|
this.searchWorld({});
|
|
};
|
|
|
|
$app.methods.searchUser = function () {
|
|
this.searchUserParam = {
|
|
n: 10,
|
|
offset: 0,
|
|
search: this.searchText
|
|
};
|
|
this.moreSearchUser();
|
|
};
|
|
|
|
$app.methods.moreSearchUser = function (go) {
|
|
var param = this.searchUserParam;
|
|
if (go) {
|
|
param.offset += param.n * go;
|
|
if (param.offset < 0) {
|
|
param.offset = 0;
|
|
}
|
|
}
|
|
this.isSearchUserLoading = true;
|
|
API.getUsers(param).finally(() => {
|
|
this.isSearchUserLoading = false;
|
|
}).then((args) => {
|
|
this.searchUsers = [];
|
|
args.json.forEach((json) => {
|
|
insertOrUpdateArrayById(this.searchUsers, json);
|
|
});
|
|
return args;
|
|
});
|
|
};
|
|
|
|
$app.methods.searchWorld = function (ref) {
|
|
this.searchWorldOption = '';
|
|
var param = {
|
|
n: 10,
|
|
offset: 0
|
|
};
|
|
switch (ref.sortHeading) {
|
|
case 'featured':
|
|
param.sort = 'order';
|
|
param.featured = 'true';
|
|
break;
|
|
case 'trending':
|
|
param.sort = 'popularity';
|
|
param.featured = 'false';
|
|
break;
|
|
case 'updated':
|
|
param.sort = 'updated';
|
|
break;
|
|
case 'created':
|
|
param.sort = 'created';
|
|
break;
|
|
case 'publication':
|
|
param.sort = 'publicationDate';
|
|
break;
|
|
case 'shuffle':
|
|
param.sort = 'shuffle';
|
|
break;
|
|
case 'active':
|
|
this.searchWorldOption = 'active';
|
|
break;
|
|
case 'recent':
|
|
this.searchWorldOption = 'recent';
|
|
break;
|
|
case 'favorite':
|
|
this.searchWorldOption = 'favorites';
|
|
break;
|
|
case 'labs':
|
|
param.sort = 'labsPublicationDate';
|
|
break;
|
|
case 'heat':
|
|
param.sort = 'heat';
|
|
param.featured = 'false';
|
|
break;
|
|
default:
|
|
param.sort = 'popularity';
|
|
param.search = this.searchText;
|
|
break;
|
|
}
|
|
param.order = ref.sortOrder || 'descending';
|
|
if (ref.sortOwnership === 'mine') {
|
|
param.user = 'me';
|
|
param.releaseStatus = 'all';
|
|
}
|
|
if (ref.tag) {
|
|
param.tag = ref.tag;
|
|
}
|
|
// TODO: option.platform
|
|
this.searchWorldParam = param;
|
|
this.moreSearchWorld();
|
|
};
|
|
|
|
$app.methods.moreSearchWorld = function (go) {
|
|
var param = this.searchWorldParam;
|
|
if (go) {
|
|
param.offset += param.n * go;
|
|
if (param.offset < 0) {
|
|
param.offset = 0;
|
|
}
|
|
}
|
|
this.isSearchWorldLoading = true;
|
|
API.getWorlds(param, this.searchWorldOption).finally(() => {
|
|
this.isSearchWorldLoading = false;
|
|
}).then((args) => {
|
|
this.searchWorlds = [];
|
|
args.json.forEach((json) => {
|
|
insertOrUpdateArrayById(this.searchWorlds, json);
|
|
});
|
|
return args;
|
|
});
|
|
};
|
|
|
|
$app.methods.searchAvatar = function (option) {
|
|
var param = {
|
|
n: 10,
|
|
offset: 0
|
|
};
|
|
switch (option) {
|
|
case 'updated':
|
|
param.sort = 'updated';
|
|
break;
|
|
case 'created':
|
|
param.sort = 'created';
|
|
break;
|
|
case 'mine':
|
|
param.user = 'me';
|
|
param.releaseStatus = 'all';
|
|
break;
|
|
default:
|
|
param.sort = 'popularity';
|
|
param.search = this.searchText;
|
|
break;
|
|
}
|
|
param.order = 'descending';
|
|
// TODO: option.platform
|
|
this.searchAvatarParam = param;
|
|
this.moreSearchAvatar();
|
|
};
|
|
|
|
$app.methods.moreSearchAvatar = function (go) {
|
|
var param = this.searchAvatarParam;
|
|
if (go) {
|
|
param.offset += param.n * go;
|
|
if (param.offset < 0) {
|
|
param.offset = 0;
|
|
}
|
|
}
|
|
this.isSearchAvatarLoading = true;
|
|
API.getAvatars(param).finally(() => {
|
|
this.isSearchAvatarLoading = false;
|
|
}).then((args) => {
|
|
this.searchAvatars = [];
|
|
args.json.forEach((json) => {
|
|
insertOrUpdateArrayById(this.searchAvatars, json);
|
|
});
|
|
return args;
|
|
});
|
|
};
|
|
|
|
// App: Favorite
|
|
|
|
$app.data.favoriteFriend = {};
|
|
$app.data.favoriteWorld = {};
|
|
$app.data.favoriteAvatar = {};
|
|
$app.data.favoriteFriends_ = [];
|
|
$app.data.favoriteWorlds_ = [];
|
|
$app.data.favoriteAvatars_ = [];
|
|
$app.data.sortFavoriteFriends = false;
|
|
$app.data.sortFavoriteWorlds = false;
|
|
$app.data.sortFavoriteAvatars = false;
|
|
|
|
API.$on('LOGIN', () => {
|
|
$app.favoriteFriend = {};
|
|
$app.favoriteWorld = {};
|
|
$app.favoriteAvatar = {};
|
|
$app.favoriteFriends_ = [];
|
|
$app.favoriteWorlds_ = [];
|
|
$app.favoriteAvatars_ = [];
|
|
$app.sortFavoriteFriends = false;
|
|
$app.sortFavoriteWorlds = false;
|
|
$app.sortFavoriteAvatars = false;
|
|
});
|
|
|
|
API.$on('FAVORITE', (args) => {
|
|
$app.updateFavorite(args.ref.type, args.ref.favoriteId);
|
|
});
|
|
|
|
API.$on('FAVORITE:@DELETE', (args) => {
|
|
$app.updateFavorite(args.ref.type, args.ref.favoriteId);
|
|
});
|
|
|
|
API.$on('USER', (args) => {
|
|
$app.updateFavorite('friend', args.ref.id);
|
|
});
|
|
|
|
API.$on('WORLD', (args) => {
|
|
$app.updateFavorite('world', args.ref.id);
|
|
});
|
|
|
|
API.$on('AVATAR', (args) => {
|
|
$app.updateFavorite('avatar', args.ref.id);
|
|
});
|
|
|
|
$app.methods.updateFavorite = function (type, objectId) {
|
|
var favorite = API.favoriteObject[objectId];
|
|
var ctx;
|
|
var ref;
|
|
if (type === 'friend') {
|
|
ctx = this.favoriteFriend[objectId];
|
|
if (favorite) {
|
|
ref = API.user[objectId];
|
|
if (ctx) {
|
|
if (ctx.ref !== ref) {
|
|
ctx.ref = ref;
|
|
}
|
|
if (ref &&
|
|
ctx.name !== ref.displayName) {
|
|
ctx.name = ref.displayName;
|
|
this.sortFavoriteFriends = true;
|
|
}
|
|
} else {
|
|
ctx = {
|
|
id: objectId,
|
|
group: favorite.group_,
|
|
ref,
|
|
name: ''
|
|
};
|
|
if (ref) {
|
|
ctx.name = ref.displayName;
|
|
} else {
|
|
ref = this.friendLog[objectId];
|
|
if (ref &&
|
|
ref.displayName) {
|
|
ctx.name = ref.displayName;
|
|
}
|
|
}
|
|
this.$set(this.favoriteFriend, objectId, ctx);
|
|
this.favoriteFriends_.push(ctx);
|
|
this.sortFavoriteFriends = true;
|
|
}
|
|
} else if (ctx) {
|
|
this.$delete(this.favoriteFriend, objectId);
|
|
removeFromArray(this.favoriteFriends_, ctx);
|
|
}
|
|
} else if (type === 'world') {
|
|
ctx = this.favoriteWorld[objectId];
|
|
if (favorite) {
|
|
ref = API.world[objectId];
|
|
if (ctx) {
|
|
if (ctx.ref !== ref) {
|
|
ctx.ref = ref;
|
|
}
|
|
if (ref &&
|
|
ctx.name !== ref.name) {
|
|
ctx.name = ref.name;
|
|
this.sortFavoriteWorlds = true;
|
|
}
|
|
} else {
|
|
ctx = {
|
|
id: objectId,
|
|
group: favorite.group_,
|
|
ref,
|
|
name: ''
|
|
};
|
|
if (ref) {
|
|
ctx.name = ref.name;
|
|
}
|
|
this.$set(this.favoriteWorld, objectId, ctx);
|
|
this.favoriteWorlds_.push(ctx);
|
|
this.sortFavoriteWorlds = true;
|
|
}
|
|
} else {
|
|
this.$delete(this.favoriteWorld, objectId);
|
|
removeFromArray(this.favoriteWorlds_, ctx);
|
|
}
|
|
} else if (type === 'avatar') {
|
|
ctx = this.favoriteAvatar[objectId];
|
|
if (favorite) {
|
|
ref = API.avatar[objectId];
|
|
if (ctx) {
|
|
if (ctx.ref !== ref) {
|
|
ctx.ref = ref;
|
|
}
|
|
if (ref &&
|
|
ctx.name !== ref.name) {
|
|
ctx.name = ref.name;
|
|
this.sortFavoriteAvatars = true;
|
|
}
|
|
} else {
|
|
ctx = {
|
|
id: objectId,
|
|
group: favorite.group_,
|
|
ref,
|
|
name: ''
|
|
};
|
|
if (ref) {
|
|
ctx.name = ref.name;
|
|
}
|
|
this.$set(this.favoriteAvatar, objectId, ctx);
|
|
this.favoriteAvatars_.push(ctx);
|
|
this.sortFavoriteAvatars = true;
|
|
}
|
|
} else {
|
|
this.$delete(this.favoriteAvatar, objectId);
|
|
removeFromArray(this.favoriteAvatars_, ctx);
|
|
}
|
|
}
|
|
};
|
|
|
|
$app.methods.deleteFavorite = function (objectId) {
|
|
// FIXME: 메시지 수정
|
|
this.$confirm('Continue? Delete Favorite', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
API.deleteFavorite({
|
|
objectId
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.changeFavoriteGroupName = function (ctx) {
|
|
this.$prompt('Enter a new name', 'Change Group Name', {
|
|
distinguishCancelAndClose: true,
|
|
cancelButtonText: 'Cancel',
|
|
confirmButtonText: 'Change',
|
|
inputPlaceholder: 'Name',
|
|
inputValue: ctx.displayName,
|
|
inputPattern: /\S+/u,
|
|
inputErrorMessage: 'Name is required',
|
|
callback: (action, instance) => {
|
|
if (action === 'confirm') {
|
|
API.saveFavoriteGroup({
|
|
type: ctx.type,
|
|
group: ctx.name,
|
|
displayName: instance.inputValue
|
|
}).then((args) => {
|
|
this.$message('Group updated!');
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.clearFavoriteGroup = function (ctx) {
|
|
// FIXME: 메시지 수정
|
|
this.$confirm('Continue? Clear Group', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action2) => {
|
|
if (action2 === 'confirm') {
|
|
API.clearFavoriteGroup({
|
|
type: ctx.type,
|
|
group: ctx.name
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
var sortFavoriteByName = (a, b) => {
|
|
var A = String(a.name).toUpperCase();
|
|
var B = String(b.name).toUpperCase();
|
|
if (A < B) {
|
|
return -1;
|
|
}
|
|
if (A > B) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
$app.computed.favoriteFriends = function () {
|
|
if (this.sortFavoriteFriends) {
|
|
this.sortFavoriteFriends = false;
|
|
this.favoriteFriends_.sort(sortFavoriteByName);
|
|
}
|
|
return this.favoriteFriends_;
|
|
};
|
|
|
|
$app.computed.favoriteWorlds = function () {
|
|
if (this.sortFavoriteWorlds) {
|
|
this.sortFavoriteWorlds = false;
|
|
this.favoriteWorlds_.sort(sortFavoriteByName);
|
|
}
|
|
return this.favoriteWorlds_;
|
|
};
|
|
|
|
$app.computed.favoriteAvatars = function () {
|
|
if (this.sortFavoriteAvatars) {
|
|
this.sortFavoriteAvatars = false;
|
|
this.favoriteAvatars_.sort(sortFavoriteByName);
|
|
}
|
|
return this.favoriteAvatars_;
|
|
};
|
|
|
|
// App: friendLog
|
|
|
|
$app.data.friendLog = {};
|
|
$app.data.friendLogTable = {
|
|
data: [],
|
|
filters: [
|
|
{
|
|
prop: 'type',
|
|
value: [],
|
|
filterFn: (row, filter) => filter.value.some((v) => v === row.type)
|
|
},
|
|
{
|
|
prop: 'displayName',
|
|
value: ''
|
|
}
|
|
],
|
|
tableProps: {
|
|
stripe: true,
|
|
size: 'mini',
|
|
defaultSort: {
|
|
prop: 'created_at',
|
|
order: 'descending'
|
|
}
|
|
},
|
|
pageSize: 10,
|
|
paginationProps: {
|
|
small: true,
|
|
layout: 'sizes,prev,pager,next,total',
|
|
pageSizes: [
|
|
10,
|
|
25,
|
|
50,
|
|
100
|
|
]
|
|
}
|
|
};
|
|
|
|
API.$on('LOGIN', (args) => {
|
|
$app.initFriendship(args.ref);
|
|
});
|
|
|
|
API.$on('USER:CURRENT', (args) => {
|
|
$app.updateFriendships(args.ref);
|
|
});
|
|
|
|
API.$on('USER', (args) => {
|
|
$app.updateFriendship(args.ref);
|
|
});
|
|
|
|
API.$on('FRIEND:ADD', (args) => {
|
|
$app.addFriendship(args.param.userId);
|
|
});
|
|
|
|
API.$on('FRIEND:DELETE', (args) => {
|
|
$app.deleteFriendship(args.param.userId);
|
|
});
|
|
|
|
API.$on('FRIEND:REQUEST', (args) => {
|
|
var ref = API.user[args.param.userId];
|
|
if (ref) {
|
|
$app.friendLogTable.data.push({
|
|
created_at: new Date().toJSON(),
|
|
type: 'FriendRequest',
|
|
userId: ref.id,
|
|
displayName: ref.displayName
|
|
});
|
|
$app.saveFriendLog();
|
|
}
|
|
});
|
|
|
|
API.$on('FRIEND:REQUEST:CANCEL', (args) => {
|
|
var ref = API.user[args.param.userId];
|
|
if (ref) {
|
|
$app.friendLogTable.data.push({
|
|
created_at: new Date().toJSON(),
|
|
type: 'CancelFriendRequst',
|
|
userId: ref.id,
|
|
displayName: ref.displayName
|
|
});
|
|
$app.saveFriendLog();
|
|
}
|
|
});
|
|
|
|
var saveFriendLogTimer = false;
|
|
$app.methods.saveFriendLog = function () {
|
|
if (saveFriendLogTimer === false) {
|
|
saveFriendLogTimer = setTimeout(() => {
|
|
saveFriendLogTimer = false;
|
|
VRCXStorage.SetObject(`${API.currentUser.id}_friendLog`, this.friendLog);
|
|
VRCXStorage.SetArray(`${API.currentUser.id}_friendLogTable`, this.friendLogTable.data);
|
|
VRCXStorage.Set(`${API.currentUser.id}_friendLogUpdatedAt`, new Date().toJSON());
|
|
}, 1);
|
|
}
|
|
};
|
|
|
|
$app.methods.initFriendship = function (ref) {
|
|
if (VRCXStorage.Get(`${ref.id}_friendLogUpdatedAt`)) {
|
|
this.friendLog = VRCXStorage.GetObject(`${ref.id}_friendLog`);
|
|
this.friendLogTable.data = VRCXStorage.GetArray(`${ref.id}_friendLogTable`);
|
|
} else {
|
|
this.friendLog = {};
|
|
ref.friends.forEach((id) => {
|
|
this.friendLog[id] = {
|
|
id
|
|
};
|
|
});
|
|
this.friendLogTable.data = [];
|
|
this.saveFriendLog();
|
|
}
|
|
};
|
|
|
|
$app.methods.addFriendship = function (id) {
|
|
if (!this.friendLog[id]) {
|
|
var ctx = {
|
|
id,
|
|
displayName: null
|
|
};
|
|
this.$set(this.friendLog, id, ctx);
|
|
var ref = API.user[id];
|
|
if (ref) {
|
|
ctx.displayName = ref.displayName;
|
|
this.friendLogTable.data.push({
|
|
created_at: new Date().toJSON(),
|
|
type: 'Friend',
|
|
userId: ref.id,
|
|
displayName: ctx.displayName
|
|
});
|
|
}
|
|
this.saveFriendLog();
|
|
this.notifyMenu('friendLog');
|
|
}
|
|
};
|
|
|
|
$app.methods.deleteFriendship = function (id) {
|
|
var ctx = this.friendLog[id];
|
|
if (ctx) {
|
|
this.$delete(this.friendLog, id);
|
|
this.friendLogTable.data.push({
|
|
created_at: new Date().toJSON(),
|
|
type: 'Unfriend',
|
|
userId: id,
|
|
displayName: ctx.displayName
|
|
});
|
|
this.saveFriendLog();
|
|
this.notifyMenu('friendLog');
|
|
}
|
|
};
|
|
|
|
$app.methods.updateFriendships = function (ref) {
|
|
var map = {};
|
|
ref.friends.forEach((id) => {
|
|
map[id] = true;
|
|
this.addFriendship(id);
|
|
});
|
|
for (var key in this.friendLog) {
|
|
if (!map[key]) {
|
|
this.deleteFriendship(key);
|
|
}
|
|
}
|
|
};
|
|
|
|
$app.methods.updateFriendship = function (ref) {
|
|
var ctx = this.friendLog[ref.id];
|
|
if (ctx &&
|
|
ctx.displayName !== ref.displayName) {
|
|
if (ctx.displayName) {
|
|
this.friendLogTable.data.push({
|
|
created_at: new Date().toJSON(),
|
|
type: 'DisplayName',
|
|
userId: ref.id,
|
|
displayName: ref.displayName,
|
|
previousDisplayName: ctx.displayName
|
|
});
|
|
} else if (ctx.displayName === null) {
|
|
this.friendLogTable.data.push({
|
|
created_at: new Date().toJSON(),
|
|
type: 'Friend',
|
|
userId: ref.id,
|
|
displayName: ref.displayName
|
|
});
|
|
}
|
|
ctx.displayName = ref.displayName;
|
|
this.saveFriendLog();
|
|
this.notifyMenu('friendLog');
|
|
}
|
|
};
|
|
|
|
$app.methods.deleteFriendLog = function (row) {
|
|
// FIXME: 메시지 수정
|
|
this.$confirm('Continue? Delete Log', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
if (removeFromArray(this.friendLogTable.data, row)) {
|
|
this.saveFriendLog();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// App: Moderation
|
|
|
|
$app.data.playerModerationTable = {
|
|
data: [],
|
|
filters: [
|
|
{
|
|
prop: 'type',
|
|
value: [],
|
|
filterFn: (row, filter) => filter.value.some((v) => v === row.type)
|
|
},
|
|
{
|
|
prop: [
|
|
'sourceDisplayName',
|
|
'targetDisplayName'
|
|
],
|
|
value: ''
|
|
}
|
|
],
|
|
tableProps: {
|
|
stripe: true,
|
|
size: 'mini',
|
|
defaultSort: {
|
|
prop: 'created',
|
|
order: 'descending'
|
|
}
|
|
},
|
|
pageSize: 10,
|
|
paginationProps: {
|
|
small: true,
|
|
layout: 'sizes,prev,pager,next,total',
|
|
pageSizes: [
|
|
10,
|
|
25,
|
|
50,
|
|
100
|
|
]
|
|
}
|
|
};
|
|
|
|
API.$on('LOGIN', () => {
|
|
$app.playerModerationTable.data = [];
|
|
});
|
|
|
|
API.$on('PLAYER-MODERATION', (args) => {
|
|
var insertOrUpdate = $app.playerModerationTable.data.some((val, idx, arr) => {
|
|
if (val.id === args.ref.id) {
|
|
if (args.ref.hide_) {
|
|
$app.$delete(arr, idx);
|
|
} else {
|
|
$app.$set(arr, idx, args.ref);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
if (!insertOrUpdate &&
|
|
!args.ref.hide_) {
|
|
$app.playerModerationTable.data.push(args.ref);
|
|
$app.notifyMenu('moderation');
|
|
}
|
|
});
|
|
|
|
API.$on('PLAYER-MODERATION:@DELETE', (args) => {
|
|
$app.playerModerationTable.data.find((val, idx, arr) => {
|
|
if (val.id === args.ref.id) {
|
|
$app.$delete(arr, idx);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
});
|
|
|
|
$app.methods.deletePlayerModeration = function (row) {
|
|
// FIXME: 메시지 수정
|
|
this.$confirm('Continue? Delete Moderation', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
API.deletePlayerModeration({
|
|
moderated: row.targetUserId,
|
|
type: row.type
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// App: Notification
|
|
|
|
$app.data.notificationTable = {
|
|
data: [],
|
|
filters: [
|
|
{
|
|
prop: 'type',
|
|
value: [],
|
|
filterFn: (row, filter) => filter.value.some((v) => v === row.type)
|
|
},
|
|
{
|
|
prop: 'senderUsername',
|
|
value: ''
|
|
}
|
|
],
|
|
tableProps: {
|
|
stripe: true,
|
|
size: 'mini',
|
|
defaultSort: {
|
|
prop: 'created_at',
|
|
order: 'descending'
|
|
}
|
|
},
|
|
pageSize: 10,
|
|
paginationProps: {
|
|
small: true,
|
|
layout: 'sizes,prev,pager,next,total',
|
|
pageSizes: [
|
|
10,
|
|
25,
|
|
50,
|
|
100
|
|
]
|
|
}
|
|
};
|
|
|
|
API.$on('LOGIN', () => {
|
|
$app.notificationTable.data = [];
|
|
});
|
|
|
|
API.$on('NOTIFICATION', (args) => {
|
|
var insertOrUpdate = $app.notificationTable.data.some((val, idx, arr) => {
|
|
if (val.id === args.ref.id) {
|
|
if (args.ref.hide_) {
|
|
$app.$delete(arr, idx);
|
|
} else {
|
|
$app.$set(arr, idx, args.ref);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
if (!insertOrUpdate &&
|
|
!args.ref.hide_) {
|
|
$app.notificationTable.data.push(args.ref);
|
|
$app.notifyMenu('notification');
|
|
}
|
|
});
|
|
|
|
API.$on('NOTIFICATION:@DELETE', (args) => {
|
|
$app.notificationTable.data.find((val, idx, arr) => {
|
|
if (val.id === args.ref.id) {
|
|
$app.$delete(arr, idx);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
});
|
|
|
|
$app.methods.parseInviteLocation = function (row) {
|
|
try {
|
|
var L = API.parseLocation(row.details.worldId);
|
|
return `${row.details.worldName} #${L.instanceName} ${L.accessType}`;
|
|
} catch (err) {
|
|
console.error(err);
|
|
return '';
|
|
}
|
|
};
|
|
|
|
$app.methods.acceptNotification = function (row) {
|
|
// FIXME: 메시지 수정
|
|
this.$confirm('Continue? Accept Friend Request', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
API.acceptNotification({
|
|
notificationId: row.id
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.hideNotification = function (row) {
|
|
// FIXME: 메시지 수정
|
|
this.$confirm('Continue? Delete Notification', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
API.hideNotification({
|
|
notificationId: row.id
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// App: More
|
|
|
|
$app.data.currentUserTreeData = [];
|
|
$app.data.pastDisplayNameTable = {
|
|
data: [],
|
|
tableProps: {
|
|
stripe: true,
|
|
size: 'mini',
|
|
defaultSort: {
|
|
prop: 'updated_at',
|
|
order: 'descending'
|
|
}
|
|
},
|
|
pageSize: 10,
|
|
paginationProps: {
|
|
small: true,
|
|
layout: 'sizes,prev,pager,next,total',
|
|
pageSizes: [
|
|
10,
|
|
25,
|
|
50,
|
|
100
|
|
]
|
|
}
|
|
};
|
|
$app.data.visits = 0;
|
|
$app.data.openVR = VRCXStorage.GetBool('openVR');
|
|
$app.data.openVRAlways = VRCXStorage.GetBool('openVRAlways');
|
|
var saveOpenVROption = function () {
|
|
VRCXStorage.SetBool('openVR', this.openVR);
|
|
VRCXStorage.SetBool('openVRAlways', this.openVRAlways);
|
|
};
|
|
$app.watch.openVR = saveOpenVROption;
|
|
$app.watch.openVRAlways = saveOpenVROption;
|
|
|
|
API.$on('LOGIN', () => {
|
|
$app.currentUserTreeData = [];
|
|
$app.pastDisplayNameTable.data = [];
|
|
});
|
|
|
|
API.$on('USER:CURRENT', (args) => {
|
|
if (args.ref.pastDisplayNames) {
|
|
$app.pastDisplayNameTable.data = args.ref.pastDisplayNames;
|
|
}
|
|
});
|
|
|
|
API.$on('VISITS', (args) => {
|
|
$app.visits = args.json;
|
|
});
|
|
|
|
$app.methods.logout = function () {
|
|
this.$confirm('Continue? Logout', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
API.logout();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.resetHome = function () {
|
|
this.$confirm('Continue? Reset Home', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
API.saveCurrentUser({
|
|
homeLocation: ''
|
|
}).then((args) => {
|
|
this.$message({
|
|
message: 'Home world has been reset',
|
|
type: 'success'
|
|
});
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.updateOpenVR = function () {
|
|
if (this.openVR &&
|
|
(this.isGameRunning || this.openVRAlways)) {
|
|
VRCX.StartVR();
|
|
} else {
|
|
VRCX.StopVR();
|
|
}
|
|
};
|
|
|
|
$app.methods.refreshCurrentUserTreeData = function () {
|
|
this.currentUserTreeData = buildTreeData(API.currentUser);
|
|
};
|
|
|
|
$app.methods.promptUserDialog = function () {
|
|
this.$prompt('Enter a User ID (UUID)', 'Direct Access', {
|
|
distinguishCancelAndClose: true,
|
|
confirmButtonText: 'Send',
|
|
cancelButtonText: 'Cancel',
|
|
inputPattern: /\S+/u,
|
|
inputErrorMessage: 'User ID is required',
|
|
callback: (action, instance) => {
|
|
if (action === 'confirm' &&
|
|
instance.inputValue) {
|
|
this.showUserDialog(instance.inputValue);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.promptWorldDialog = function () {
|
|
this.$prompt('Enter a World ID (UUID)', 'Direct Access', {
|
|
distinguishCancelAndClose: true,
|
|
confirmButtonText: 'Send',
|
|
cancelButtonText: 'Cancel',
|
|
inputPattern: /\S+/u,
|
|
inputErrorMessage: 'World ID is required',
|
|
callback: (action, instance) => {
|
|
if (action === 'confirm' &&
|
|
instance.inputValue) {
|
|
this.showWorldDialog(instance.inputValue);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.promptAvatarDialog = function () {
|
|
this.$prompt('Enter a Avatar ID (UUID)', 'Direct Access', {
|
|
distinguishCancelAndClose: true,
|
|
confirmButtonText: 'Send',
|
|
cancelButtonText: 'Cancel',
|
|
inputPattern: /\S+/u,
|
|
inputErrorMessage: 'Avatar ID is required',
|
|
callback: (action, instance) => {
|
|
if (action === 'confirm' &&
|
|
instance.inputValue) {
|
|
this.showAvatarDialog(instance.inputValue);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// App: Dialog
|
|
|
|
var adjustDialogZ = (el) => {
|
|
var z = 0;
|
|
document.querySelectorAll('.v-modal,.el-dialog__wrapper').forEach((v) => {
|
|
var _z = Number(v.style.zIndex) || 0;
|
|
if (_z &&
|
|
_z > z &&
|
|
v !== el) {
|
|
z = _z;
|
|
}
|
|
});
|
|
if (z) {
|
|
el.style.zIndex = z + 1;
|
|
}
|
|
};
|
|
|
|
// App: User Dialog
|
|
|
|
$app.data.userDialog = {
|
|
visible: false,
|
|
loading: false,
|
|
id: '',
|
|
ref: {},
|
|
friend: {},
|
|
isFriend: false,
|
|
incomingRequest: false,
|
|
outgoingRequest: false,
|
|
isBlock: false,
|
|
isMute: false,
|
|
isHideAvatar: false,
|
|
isFavorite: false,
|
|
|
|
location_: {},
|
|
users: [],
|
|
instance: {},
|
|
|
|
worlds: [],
|
|
avatars: [],
|
|
isWorldsLoading: false,
|
|
isAvatarsLoading: false,
|
|
|
|
treeData: [],
|
|
memo: ''
|
|
};
|
|
|
|
$app.watch['userDialog.memo'] = function () {
|
|
var D = this.userDialog;
|
|
this.saveMemo(D.id, D.memo);
|
|
};
|
|
|
|
API.$on('LOGOUT', () => {
|
|
$app.userDialog.visible = false;
|
|
});
|
|
|
|
API.$on('USER', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.ref.id === D.id) {
|
|
D.ref = args.ref;
|
|
$app.updateUserDialogLocation();
|
|
}
|
|
});
|
|
|
|
API.$on('WORLD', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.ref.id === D.location_.worldId) {
|
|
$app.updateUserDialogLocation();
|
|
}
|
|
});
|
|
|
|
API.$on('FRIEND:STATUS', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.param.userId === D.id) {
|
|
D.isFriend = args.json.isFriend;
|
|
D.incomingRequest = args.json.incomingRequest;
|
|
D.outgoingRequest = args.json.outgoingRequest;
|
|
}
|
|
});
|
|
|
|
API.$on('FRIEND:REQUEST', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.param.userId === D.id) {
|
|
if (args.json.success) {
|
|
D.isFriend = true;
|
|
} else {
|
|
D.outgoingRequest = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
API.$on('FRIEND:REQUEST:CANCEL', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.param.userId === D.id) {
|
|
D.outgoingRequest = false;
|
|
}
|
|
});
|
|
|
|
API.$on('NOTIFICATION', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.ref &&
|
|
args.ref.senderUserId === D.id &&
|
|
args.ref.type === 'friendRequest') {
|
|
D.incomingRequest = true;
|
|
}
|
|
});
|
|
|
|
API.$on('NOTIFICATION:ACCEPT', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.ref &&
|
|
args.ref.senderUserId === D.id &&
|
|
args.ref.type === 'friendRequest') {
|
|
D.isFriend = true;
|
|
}
|
|
});
|
|
|
|
API.$on('NOTIFICATION:@DELETE', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.ref.senderUserId === D.id &&
|
|
args.ref.type === 'friendRequest') {
|
|
D.incomingRequest = false;
|
|
}
|
|
});
|
|
|
|
API.$on('FRIEND:DELETE', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.param.userId === D.id) {
|
|
D.isFriend = false;
|
|
}
|
|
});
|
|
|
|
API.$on('PLAYER-MODERATION', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.ref.targetUserId === D.id &&
|
|
args.ref.sourceUserId === API.currentUser.id &&
|
|
!args.ref.hide_) {
|
|
if (args.ref.type === 'block') {
|
|
D.isBlock = true;
|
|
} else if (args.ref.type === 'mute') {
|
|
D.isMute = true;
|
|
} else if (args.ref.type === 'hideAvatar') {
|
|
D.isHideAvatar = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
API.$on('PLAYER-MODERATION:@DELETE', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.ref.targetUserId === D.id &&
|
|
args.ref.sourceUserId === API.currentUser.id) {
|
|
if (args.ref.type === 'block') {
|
|
D.isBlock = false;
|
|
} else if (args.ref.type === 'mute') {
|
|
D.isMute = false;
|
|
} else if (args.ref.type === 'hideAvatar') {
|
|
D.isHideAvatar = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
API.$on('FAVORITE', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.ref.favoriteId === D.id &&
|
|
!args.ref.hide_) {
|
|
D.isFavorite = true;
|
|
}
|
|
});
|
|
|
|
API.$on('FAVORITE:@DELETE', (args) => {
|
|
var D = $app.userDialog;
|
|
if (D.visible &&
|
|
args.ref.favoriteId === D.id) {
|
|
D.isFavorite = false;
|
|
}
|
|
});
|
|
|
|
$app.methods.showUserDialog = function (userId) {
|
|
this.$nextTick(() => adjustDialogZ(this.$refs.userDialog.$el));
|
|
var D = this.userDialog;
|
|
D.id = userId;
|
|
D.treeData = [];
|
|
D.memo = this.loadMemo(userId);
|
|
D.visible = true;
|
|
D.loading = true;
|
|
API.getCachedUser({
|
|
userId
|
|
}).catch((err) => {
|
|
D.loading = false;
|
|
D.visible = false;
|
|
throw err;
|
|
}).then((args) => {
|
|
if (args.ref.id === D.id) {
|
|
D.loading = false;
|
|
D.ref = args.ref;
|
|
D.friend = this.friend[D.id];
|
|
D.isFriend = Boolean(D.friend);
|
|
D.incomingRequest = false;
|
|
D.outgoingRequest = false;
|
|
D.isBlock = false;
|
|
D.isMute = false;
|
|
D.isHideAvatar = false;
|
|
var key;
|
|
var ref;
|
|
for (key in API.playerModeration) {
|
|
ref = API.playerModeration[key];
|
|
if (ref.targetUserId === D.id &&
|
|
ref.sourceUserId === API.currentUser.id &&
|
|
!ref.hide_) {
|
|
if (ref.type === 'block') {
|
|
D.isBlock = true;
|
|
} else if (ref.type === 'mute') {
|
|
D.isMute = true;
|
|
} else if (ref.type === 'hideAvatar') {
|
|
D.isHideAvatar = true;
|
|
}
|
|
}
|
|
}
|
|
D.isFavorite = Boolean(API.favoriteObject[D.id]);
|
|
this.updateUserDialogLocation();
|
|
D.worlds = [];
|
|
D.avatars = [];
|
|
D.isWorldsLoading = false;
|
|
D.isAvatarsLoading = false;
|
|
for (key in API.world) {
|
|
ref = API.world[key];
|
|
if (ref.authorId === D.id) {
|
|
D.worlds.push(ref);
|
|
}
|
|
}
|
|
for (key in API.avatar) {
|
|
ref = API.avatar[key];
|
|
if (ref.authorId === D.id) {
|
|
D.avatars.push(ref);
|
|
}
|
|
}
|
|
$app.sortUserDialogWorlds();
|
|
$app.sortUserDialogAvatars();
|
|
API.getFriendStatus({
|
|
userId: D.id
|
|
});
|
|
if (args.cache) {
|
|
API.getUser(args.param);
|
|
}
|
|
}
|
|
return args;
|
|
});
|
|
};
|
|
|
|
$app.methods.updateUserDialogLocation = function () {
|
|
var ref;
|
|
var D = this.userDialog;
|
|
var L = API.parseLocation(D.ref.location);
|
|
D.location_ = L;
|
|
if (L.userId) {
|
|
ref = API.user[L.userId];
|
|
if (ref) {
|
|
L.user = ref;
|
|
} else {
|
|
API.getUser({
|
|
userId: L.userId
|
|
}).then((args) => {
|
|
this.$set(L, 'user', args.ref);
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
D.users = [];
|
|
if (!L.isOffline) {
|
|
for (var key in this.friend) {
|
|
ref = API.user[key];
|
|
if (ref &&
|
|
ref.location === D.ref.location) {
|
|
D.users.push(ref);
|
|
}
|
|
}
|
|
D.users.sort((a, b) => {
|
|
var A = String(a.displayName).toUpperCase();
|
|
var B = String(b.displayName).toUpperCase();
|
|
if (A < B) {
|
|
return -1;
|
|
}
|
|
if (A > B) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
D.instance = {};
|
|
if (L.worldId) {
|
|
var handle = (instances) => {
|
|
if (instances) {
|
|
instances.find((v) => {
|
|
if (v[0] === L.instanceId) {
|
|
D.instance = {
|
|
id: v[0],
|
|
occupants: v[1]
|
|
};
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
};
|
|
ref = API.world[L.worldId];
|
|
if (ref) {
|
|
handle(ref.instances);
|
|
} else {
|
|
API.getWorld({
|
|
worldId: L.worldId
|
|
}).then((args) => {
|
|
if (L.tag === D.location_.tag) {
|
|
handle(args.ref.instances);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
$app.methods.sortUserDialogWorlds = function () {
|
|
this.userDialog.worlds.sort((a, b) => {
|
|
var A = String(a.name).toUpperCase();
|
|
var B = String(b.name).toUpperCase();
|
|
if (A < B) {
|
|
return -1;
|
|
}
|
|
if (A > B) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
};
|
|
|
|
$app.methods.refreshUserDialogWorlds = function () {
|
|
var D = this.userDialog;
|
|
if (!D.isWorldsLoading) {
|
|
D.isWorldsLoading = true;
|
|
var param = {
|
|
n: 100,
|
|
offset: 0,
|
|
sort: 'updated',
|
|
order: 'descending',
|
|
user: 'friends',
|
|
userId: D.id,
|
|
releaseStatus: 'public'
|
|
};
|
|
if (param.userId === API.currentUser.id) {
|
|
param.user = 'me';
|
|
param.releaseStatus = 'all';
|
|
}
|
|
API.bulk({
|
|
fn: 'getWorlds',
|
|
N: -1,
|
|
param,
|
|
handle: (args) => {
|
|
args.json.forEach((json) => {
|
|
insertOrUpdateArrayById(D.worlds, json);
|
|
});
|
|
},
|
|
done: () => {
|
|
this.sortUserDialogWorlds();
|
|
D.isWorldsLoading = false;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
$app.methods.sortUserDialogAvatars = function () {
|
|
this.userDialog.avatars.sort((a, b) => {
|
|
var A = String(a.name).toUpperCase();
|
|
var B = String(b.name).toUpperCase();
|
|
if (A < B) {
|
|
return -1;
|
|
}
|
|
if (A > B) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
};
|
|
|
|
$app.methods.refreshUserDialogAvatars = function () {
|
|
var D = this.userDialog;
|
|
if (!D.isAvatarsLoading) {
|
|
D.isAvatarsLoading = true;
|
|
var param = {
|
|
n: 100,
|
|
offset: 0,
|
|
sort: 'updated',
|
|
order: 'descending',
|
|
user: 'friends',
|
|
userId: D.id,
|
|
releaseStatus: 'public'
|
|
};
|
|
if (param.userId === API.currentUser.id) {
|
|
param.user = 'me';
|
|
param.releaseStatus = 'all';
|
|
}
|
|
API.bulk({
|
|
fn: 'getAvatars',
|
|
N: -1,
|
|
param,
|
|
handle: (args) => {
|
|
args.json.forEach((json) => {
|
|
insertOrUpdateArrayById(D.avatars, json);
|
|
});
|
|
},
|
|
done: () => {
|
|
this.sortUserDialogAvatars();
|
|
D.isAvatarsLoading = false;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
var performUserDialogCommand = (command, userId) => {
|
|
var key;
|
|
switch (command) {
|
|
case 'Delete Favorite':
|
|
API.deleteFavorite({
|
|
objectId: userId
|
|
});
|
|
break;
|
|
case 'Unfriend':
|
|
API.deleteFriend({
|
|
userId
|
|
});
|
|
break;
|
|
case 'Accept Friend Request':
|
|
key = API.getFriendRequest(userId);
|
|
if (key === '') {
|
|
API.sendFriendRequest({
|
|
userId
|
|
});
|
|
} else {
|
|
API.acceptNotification({
|
|
notificationId: key
|
|
});
|
|
}
|
|
break;
|
|
case 'Decline Friend Request':
|
|
key = API.getFriendRequest(userId);
|
|
if (key === '') {
|
|
API.cancelFriendRequest({
|
|
userId
|
|
});
|
|
} else {
|
|
API.hideNotification({
|
|
notificationId: key
|
|
});
|
|
}
|
|
break;
|
|
case 'Cancel Friend Request':
|
|
API.cancelFriendRequest({
|
|
userId
|
|
});
|
|
break;
|
|
case 'Send Friend Request':
|
|
API.sendFriendRequest({
|
|
userId
|
|
});
|
|
break;
|
|
case 'Unblock':
|
|
API.deletePlayerModeration({
|
|
moderated: userId,
|
|
type: 'block'
|
|
});
|
|
break;
|
|
case 'Block':
|
|
API.sendPlayerModeration({
|
|
moderated: userId,
|
|
type: 'block'
|
|
});
|
|
break;
|
|
case 'Unmute':
|
|
API.deletePlayerModeration({
|
|
moderated: userId,
|
|
type: 'mute'
|
|
});
|
|
break;
|
|
case 'Mute':
|
|
API.sendPlayerModeration({
|
|
moderated: userId,
|
|
type: 'mute'
|
|
});
|
|
break;
|
|
case 'Show Avatar':
|
|
API.deletePlayerModeration({
|
|
moderated: userId,
|
|
type: 'hideAvatar'
|
|
});
|
|
break;
|
|
case 'Hide Avatar':
|
|
API.sendPlayerModeration({
|
|
moderated: userId,
|
|
type: 'hideAvatar'
|
|
});
|
|
break;
|
|
}
|
|
};
|
|
|
|
$app.methods.userDialogCommand = function (command) {
|
|
var D = this.userDialog;
|
|
if (D.visible) {
|
|
if (command === 'Add Favorite') {
|
|
this.showFavoriteDialog('friend', D.id);
|
|
} else if (command === 'Message') {
|
|
this.$prompt('Enter a message', 'Send Message', {
|
|
distinguishCancelAndClose: true,
|
|
confirmButtonText: 'Send',
|
|
cancelButtonText: 'Cancel',
|
|
inputPattern: /\S+/u,
|
|
inputErrorMessage: 'Message is required',
|
|
callback: (action, instance) => {
|
|
if (action === 'confirm' &&
|
|
instance.inputValue) {
|
|
API.sendNotification({
|
|
receiverUserId: D.id,
|
|
type: 'message',
|
|
message: instance.inputValue,
|
|
seen: false,
|
|
details: '{}'
|
|
}).then((args) => {
|
|
this.$message('Message sent');
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
this.$confirm(`Continue? ${command}`, 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
performUserDialogCommand(command, D.id);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
$app.methods.refreshUserDialogTreeData = function () {
|
|
var D = this.userDialog;
|
|
D.treeData = buildTreeData(D.ref);
|
|
};
|
|
|
|
// App: World Dialog
|
|
|
|
$app.data.worldDialog = {
|
|
visible: false,
|
|
loading: false,
|
|
id: '',
|
|
location_: {},
|
|
ref: {},
|
|
isFavorite: false,
|
|
rooms: [],
|
|
treeData: [],
|
|
fileCreatedAt: '',
|
|
fileSize: ''
|
|
};
|
|
|
|
API.$on('LOGOUT', () => {
|
|
$app.worldDialog.visible = false;
|
|
});
|
|
|
|
API.$on('WORLD', (args) => {
|
|
var D = $app.worldDialog;
|
|
if (D.visible &&
|
|
args.ref.id === D.id) {
|
|
D.ref = args.ref;
|
|
var id = extractFileId(args.ref.assetUrl);
|
|
if (id) {
|
|
API.call(`file/${id}`).then((json) => {
|
|
var ref = json.versions[json.versions.length - 1];
|
|
D.fileCreatedAt = ref.created_at;
|
|
D.fileSize = `${(ref.file.sizeInBytes / 1048576).toFixed(2)} MiB`;
|
|
});
|
|
}
|
|
$app.updateWorldDialogInstances();
|
|
}
|
|
});
|
|
|
|
API.$on('FAVORITE', (args) => {
|
|
var D = $app.worldDialog;
|
|
if (D.visible &&
|
|
args.ref.favoriteId === D.id &&
|
|
!args.ref.hide_) {
|
|
D.isFavorite = true;
|
|
}
|
|
});
|
|
|
|
API.$on('FAVORITE:@DELETE', (args) => {
|
|
var D = $app.worldDialog;
|
|
if (D.visible &&
|
|
args.ref.favoriteId === D.id) {
|
|
D.isFavorite = false;
|
|
}
|
|
});
|
|
|
|
$app.methods.showWorldDialog = function (tag) {
|
|
this.$nextTick(() => adjustDialogZ(this.$refs.worldDialog.$el));
|
|
var D = this.worldDialog;
|
|
var L = API.parseLocation(tag);
|
|
if (L.worldId) {
|
|
D.id = L.worldId;
|
|
D.location_ = L;
|
|
D.treeData = [];
|
|
D.fileCreatedAt = '';
|
|
D.fileSize = 'Loading';
|
|
D.visible = true;
|
|
D.loading = true;
|
|
API.getCachedWorld({
|
|
worldId: L.worldId
|
|
}).catch((err) => {
|
|
D.loading = false;
|
|
D.visible = false;
|
|
throw err;
|
|
}).then((args) => {
|
|
if (D.id === args.ref.id) {
|
|
D.loading = false;
|
|
D.ref = args.ref;
|
|
D.isFavorite = Boolean(API.favoriteObject[D.id]);
|
|
D.rooms = [];
|
|
this.updateWorldDialogInstances();
|
|
if (args.cache) {
|
|
API.getWorld(args.param);
|
|
}
|
|
}
|
|
return args;
|
|
});
|
|
}
|
|
};
|
|
|
|
$app.methods.updateWorldDialogInstances = function () {
|
|
var ref;
|
|
var D = this.worldDialog;
|
|
if (D.ref.instances) {
|
|
var map = {};
|
|
D.ref.instances.forEach((v) => {
|
|
map[v[0]] = {
|
|
id: v[0],
|
|
occupants: v[1],
|
|
users: []
|
|
};
|
|
});
|
|
var { instanceId } = D.location_;
|
|
if (instanceId &&
|
|
!map[instanceId]) {
|
|
map[instanceId] = {
|
|
id: instanceId,
|
|
occupants: 0,
|
|
users: []
|
|
};
|
|
}
|
|
for (var key in this.friend) {
|
|
ref = API.user[key];
|
|
if (ref &&
|
|
ref.location_.worldId === D.id) {
|
|
({ instanceId } = ref.location_);
|
|
if (map[instanceId]) {
|
|
map[instanceId].users.push(ref);
|
|
} else {
|
|
map[instanceId] = {
|
|
id: instanceId,
|
|
occupants: 0,
|
|
users: [ref]
|
|
};
|
|
}
|
|
}
|
|
}
|
|
D.rooms = [];
|
|
Object.values(map).sort((a, b) => b.users.length - a.users.length || b.occupants - a.occupants).forEach((v) => {
|
|
var L = API.parseLocation(`${D.id}:${v.id}`);
|
|
v.location_ = L;
|
|
v.location = L.tag;
|
|
if (L.userId) {
|
|
ref = API.user[L.userId];
|
|
if (ref) {
|
|
L.user = ref;
|
|
} else {
|
|
API.getUser({
|
|
userId: L.userId
|
|
}).then((args) => {
|
|
this.$set(L, 'user', args.ref);
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
v.users.sort((a, b) => {
|
|
var A = String(a.displayName).toUpperCase();
|
|
var B = String(b.displayName).toUpperCase();
|
|
if (A < B) {
|
|
return -1;
|
|
}
|
|
if (A > B) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
D.rooms.push(v);
|
|
});
|
|
}
|
|
};
|
|
|
|
$app.methods.worldDialogCommand = function (command) {
|
|
var D = this.worldDialog;
|
|
if (D.visible) {
|
|
if (command === 'New Instance') {
|
|
this.showNewInstanceDialog(D.location_.tag);
|
|
} else if (command === 'Add Favorite') {
|
|
this.showFavoriteDialog('world', D.id);
|
|
} else {
|
|
this.$confirm(`Continue? ${command}`, 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
switch (command) {
|
|
case 'Delete Favorite':
|
|
API.deleteFavorite({
|
|
objectId: D.id
|
|
});
|
|
break;
|
|
case 'Make Home':
|
|
API.saveCurrentUser({
|
|
homeLocation: D.id
|
|
}).then((args) => {
|
|
this.$message({
|
|
message: 'Home world updated',
|
|
type: 'success'
|
|
});
|
|
return args;
|
|
});
|
|
break;
|
|
case 'Reset Home':
|
|
API.saveCurrentUser({
|
|
homeLocation: ''
|
|
}).then((args) => {
|
|
this.$message({
|
|
message: 'Home world has been reset',
|
|
type: 'success'
|
|
});
|
|
return args;
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
$app.methods.refreshWorldDialogTreeData = function () {
|
|
var D = this.worldDialog;
|
|
D.treeData = buildTreeData(D.ref);
|
|
};
|
|
|
|
$app.computed.worldDialogPlatform = function () {
|
|
var { ref } = this.worldDialog;
|
|
var platforms = [];
|
|
if (ref.unityPackages) {
|
|
ref.unityPackages.forEach((v) => {
|
|
var platform = 'PC';
|
|
if (v.platform === 'standalonewindows') {
|
|
platform = 'PC';
|
|
} else if (v.platform === 'android') {
|
|
platform = 'Quest';
|
|
} else if (v.platform) {
|
|
({ platform } = v);
|
|
}
|
|
platforms.push(`${platform}/${v.unityVersion}`);
|
|
});
|
|
}
|
|
return platforms.join(', ');
|
|
};
|
|
|
|
// App: Avatar Dialog
|
|
|
|
$app.data.avatarDialog = {
|
|
visible: false,
|
|
loading: false,
|
|
id: '',
|
|
ref: {},
|
|
isFavorite: false,
|
|
treeData: [],
|
|
fileCreatedAt: '',
|
|
fileSize: ''
|
|
};
|
|
|
|
API.$on('LOGOUT', () => {
|
|
$app.avatarDialog.visible = false;
|
|
});
|
|
|
|
API.$on('AVATAR', (args) => {
|
|
var D = $app.avatarDialog;
|
|
if (D.visible &&
|
|
args.ref.id === D.id) {
|
|
D.ref = args.ref;
|
|
var id = extractFileId(args.ref.assetUrl);
|
|
if (id) {
|
|
API.call(`file/${id}`).then((json) => {
|
|
var ref = json.versions[json.versions.length - 1];
|
|
D.fileCreatedAt = ref.created_at;
|
|
D.fileSize = `${(ref.file.sizeInBytes / 1048576).toFixed(2)} MiB`;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
API.$on('FAVORITE', (args) => {
|
|
var D = $app.avatarDialog;
|
|
if (D.visible &&
|
|
args.ref.favoriteId === D.id &&
|
|
!args.ref.hide_) {
|
|
D.isFavorite = true;
|
|
}
|
|
});
|
|
|
|
API.$on('FAVORITE:@DELETE', (args) => {
|
|
var D = $app.avatarDialog;
|
|
if (D.visible &&
|
|
args.ref.favoriteId === D.id) {
|
|
D.isFavorite = false;
|
|
}
|
|
});
|
|
|
|
$app.methods.showAvatarDialog = function (avatarId) {
|
|
this.$nextTick(() => adjustDialogZ(this.$refs.avatarDialog.$el));
|
|
var D = this.avatarDialog;
|
|
D.id = avatarId;
|
|
D.treeData = [];
|
|
D.fileCreatedAt = '';
|
|
D.fileSize = 'Loading';
|
|
D.visible = true;
|
|
D.loading = true;
|
|
API.getCachedAvatar({
|
|
avatarId
|
|
}).catch((err) => {
|
|
D.loading = false;
|
|
D.visible = false;
|
|
throw err;
|
|
}).then((args) => {
|
|
if (D.id === args.ref.id) {
|
|
D.loading = false;
|
|
D.ref = args.ref;
|
|
D.isFavorite = Boolean(API.favoriteObject[D.ref.id]);
|
|
if (args.cache) {
|
|
API.getAvatar(args.param);
|
|
}
|
|
}
|
|
return args;
|
|
});
|
|
};
|
|
|
|
$app.methods.avatarDialogCommand = function (command) {
|
|
var D = this.avatarDialog;
|
|
if (D.visible) {
|
|
if (command === 'Add Favorite') {
|
|
this.showFavoriteDialog('avatar', D.id);
|
|
} else {
|
|
this.$confirm(`Continue? ${command}`, 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
switch (command) {
|
|
case 'Delete Favorite':
|
|
API.deleteFavorite({
|
|
objectId: D.id
|
|
});
|
|
break;
|
|
case 'Select Avatar':
|
|
API.selectAvatar({
|
|
avatarId: D.id
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
$app.methods.refreshAvatarDialogTreeData = function () {
|
|
var D = this.avatarDialog;
|
|
D.treeData = buildTreeData(D.ref);
|
|
};
|
|
|
|
$app.computed.avatarDialogLastUpdate = function () {
|
|
var at = '';
|
|
var { ref } = this.avatarDialog;
|
|
if (ref.unityPackages) {
|
|
ref.unityPackages.forEach((v) => {
|
|
if (at < v.created_at) {
|
|
at = v.created_at;
|
|
}
|
|
});
|
|
}
|
|
return at || ref.updated_at || '';
|
|
};
|
|
|
|
$app.computed.avatarDialogPlatform = function () {
|
|
var { ref } = this.avatarDialog;
|
|
var platforms = [];
|
|
if (ref.unityPackages) {
|
|
ref.unityPackages.forEach((v) => {
|
|
var platform = 'PC';
|
|
if (v.platform === 'standalonewindows') {
|
|
platform = 'PC';
|
|
} else if (v.platform === 'android') {
|
|
platform = 'Quest';
|
|
} else if (v.platform) {
|
|
({ platform } = v);
|
|
}
|
|
platforms.push(`${platform}/${v.unityVersion}`);
|
|
});
|
|
}
|
|
return platforms.join(', ');
|
|
};
|
|
|
|
// App: Favorite Dialog
|
|
|
|
$app.data.favoriteDialog = {
|
|
visible: false,
|
|
loading: false,
|
|
type: '',
|
|
objectId: '',
|
|
groups: []
|
|
};
|
|
|
|
API.$on('LOGOUT', () => {
|
|
$app.favoriteDialog.visible = false;
|
|
});
|
|
|
|
$app.methods.addFavorite = function (group) {
|
|
var D = this.favoriteDialog;
|
|
D.loading = true;
|
|
API.addFavorite({
|
|
type: D.type,
|
|
favoriteId: D.objectId,
|
|
tags: group.name
|
|
}).finally(() => {
|
|
D.loading = false;
|
|
}).then((args) => {
|
|
D.visible = false;
|
|
return args;
|
|
});
|
|
};
|
|
|
|
$app.methods.showFavoriteDialog = function (type, objectId) {
|
|
this.$nextTick(() => adjustDialogZ(this.$refs.favoriteDialog.$el));
|
|
var D = this.favoriteDialog;
|
|
D.type = type;
|
|
D.objectId = objectId;
|
|
if (type === 'friend') {
|
|
D.groups = API.favoriteFriendGroups;
|
|
D.visible = true;
|
|
} else if (type === 'world') {
|
|
D.groups = API.favoriteWorldGroups;
|
|
D.visible = true;
|
|
} else if (type === 'avatar') {
|
|
D.groups = API.favoriteAvatarGroups;
|
|
D.visible = true;
|
|
}
|
|
};
|
|
|
|
// App: Invite Dialog
|
|
|
|
$app.data.inviteDialog = {
|
|
visible: false,
|
|
loading: false,
|
|
worldId: '',
|
|
worldName: '',
|
|
userIds: []
|
|
};
|
|
|
|
API.$on('LOGOUT', () => {
|
|
$app.inviteDialog.visible = false;
|
|
});
|
|
|
|
$app.methods.sendInvite = function () {
|
|
this.$confirm('Continue? Invite', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
var D = this.inviteDialog;
|
|
if (!D.loading) {
|
|
D.loading = true;
|
|
var param = {
|
|
receiverUserId: '',
|
|
type: 'invite',
|
|
message: '',
|
|
seen: false,
|
|
details: {
|
|
worldId: D.worldId,
|
|
worldName: D.worldName
|
|
}
|
|
};
|
|
var invite = () => {
|
|
if (D.userIds.length) {
|
|
param.receiverUserId = D.userIds.shift();
|
|
API.sendNotification(param).finally(invite);
|
|
} else {
|
|
D.loading = false;
|
|
D.visible = false;
|
|
this.$message({
|
|
message: 'Invite sent',
|
|
type: 'success'
|
|
});
|
|
}
|
|
};
|
|
invite();
|
|
}
|
|
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app.methods.showInviteDialog = function (tag) {
|
|
this.$nextTick(() => adjustDialogZ(this.$refs.inviteDialog.$el));
|
|
var L = API.parseLocation(tag);
|
|
if (!(L.isOffline || L.isPrivate) &&
|
|
L.worldId) {
|
|
var handle = (ref) => {
|
|
var D = this.inviteDialog;
|
|
D.userIds = [];
|
|
D.worldId = L.tag;
|
|
D.worldName = ref.name;
|
|
D.visible = true;
|
|
};
|
|
var ref = API.world[L.worldId];
|
|
if (ref) {
|
|
handle(ref);
|
|
} else {
|
|
API.getWorld({
|
|
worldId: L.worldId
|
|
}).then((args) => {
|
|
handle(args.ref);
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// App: Social Status Dialog
|
|
|
|
$app.data.socialStatusDialog = {
|
|
visible: false,
|
|
loading: false,
|
|
status: '',
|
|
statusDescription: ''
|
|
};
|
|
|
|
API.$on('LOGOUT', () => {
|
|
$app.socialStatusDialog.visible = false;
|
|
});
|
|
|
|
$app.methods.saveSocialStatus = function () {
|
|
var D = this.socialStatusDialog;
|
|
if (!D.loading) {
|
|
D.loading = true;
|
|
API.saveCurrentUser({
|
|
status: D.status,
|
|
statusDescription: D.statusDescription
|
|
}).finally(() => {
|
|
D.loading = false;
|
|
}).then((args) => {
|
|
D.visible = false;
|
|
this.$message({
|
|
message: 'Status updated',
|
|
type: 'success'
|
|
});
|
|
return args;
|
|
});
|
|
}
|
|
};
|
|
|
|
$app.methods.showSocialStatusDialog = function () {
|
|
this.$nextTick(() => adjustDialogZ(this.$refs.socialStatusDialog.$el));
|
|
var D = this.socialStatusDialog;
|
|
D.status = API.currentUser.status;
|
|
D.statusDescription = API.currentUser.statusDescription;
|
|
D.visible = true;
|
|
};
|
|
|
|
// App: New Instance Dialog
|
|
|
|
$app.data.newInstanceDialog = {
|
|
visible: false,
|
|
loading: false,
|
|
worldId: '',
|
|
instanceId: '',
|
|
accessType: '',
|
|
location: '',
|
|
url: ''
|
|
};
|
|
|
|
API.$on('LOGOUT', () => {
|
|
$app.newInstanceDialog.visible = false;
|
|
});
|
|
|
|
$app.methods.buildInstanceTag = function () {
|
|
var D = this.newInstanceDialog;
|
|
var tags = [];
|
|
tags.push((99999 * Math.random() + 1).toFixed(0));
|
|
if (D.accessType !== 'public') {
|
|
if (D.accessType === 'friends+') {
|
|
tags.push(`~hidden(${API.currentUser.id})`);
|
|
} else if (D.accessType === 'friends') {
|
|
tags.push(`~friends(${API.currentUser.id})`);
|
|
} else {
|
|
tags.push(`~private(${API.currentUser.id})`);
|
|
}
|
|
// NOTE : crypto.getRandomValues()를 쓰면 안전한 대신 무겁겠지..
|
|
var nonce = [];
|
|
for (var i = 0; i < 10; ++i) {
|
|
nonce.push(Math.random().toString(16).substr(2).toUpperCase());
|
|
}
|
|
nonce = nonce.join('').substr(0, 64);
|
|
tags.push(`~nonce(${nonce})`);
|
|
if (D.accessType === 'invite+') {
|
|
tags.push('~canRequestInvite');
|
|
}
|
|
}
|
|
D.instanceId = tags.join('');
|
|
};
|
|
|
|
var updateLocationURL = function () {
|
|
var D = this.newInstanceDialog;
|
|
if (D.instanceId) {
|
|
D.location = `${D.worldId}:${D.instanceId}`;
|
|
D.url = `https://vrchat.net/launch?worldId=${encodeURIComponent(D.worldId)}&instanceId=${encodeURIComponent(D.instanceId)}`;
|
|
} else {
|
|
D.location = D.worldId;
|
|
D.url = `https://vrchat.net/launch?worldId=${encodeURIComponent(D.worldId)}`;
|
|
}
|
|
};
|
|
$app.watch['newInstanceDialog.worldId'] = updateLocationURL;
|
|
$app.watch['newInstanceDialog.instanceId'] = updateLocationURL;
|
|
|
|
$app.methods.showNewInstanceDialog = function (tag) {
|
|
this.$nextTick(() => adjustDialogZ(this.$refs.inviteDialog.$el));
|
|
var L = API.parseLocation(tag);
|
|
if (!(L.isOffline || L.isPrivate) &&
|
|
L.worldId) {
|
|
var D = this.newInstanceDialog;
|
|
D.worldId = L.worldId;
|
|
D.accessType = 'public';
|
|
this.buildInstanceTag();
|
|
D.visible = true;
|
|
}
|
|
};
|
|
|
|
$app.methods.makeHome = function (tag) {
|
|
this.$confirm('Continue? Make Home', 'Confirm', {
|
|
confirmButtonText: 'Confirm',
|
|
cancelButtonText: 'Cancel',
|
|
type: 'info',
|
|
callback: (action) => {
|
|
if (action === 'confirm') {
|
|
API.saveCurrentUser({
|
|
homeLocation: tag
|
|
}).then((args) => {
|
|
this.$message({
|
|
message: 'Home world updated',
|
|
type: 'success'
|
|
});
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
$app = new Vue($app);
|
|
window.$app = $app;
|
|
});
|
|
} else {
|
|
location = 'https://github.com/pypy-vrc/vrcx';
|
|
}
|