diff --git a/html/app.css b/html/app.css index f809b428..aff60513 100644 --- a/html/app.css +++ b/html/app.css @@ -1,498 +1 @@ -@charset "utf-8"; -/* -Copyright(c) 2019-2020 pypy and individual contributors. -All rights reserved. - -This work is licensed under the terms of the MIT license. -For a copy, see . -*/ - -.color-palettes { - background: #409EFF; - background: #67C23A; - background: #E6A23C; - background: #F56C6C; - background: #909399; - background: #FD9200; - background: #E6E6E6; - background: #C0C4CC; -} - -.noty_layout { - word-break: break-all; -} - -.noty_theme__mint.noty_bar { - margin: 4px 0; - overflow: hidden; - border-radius: 2px; - position: relative; -} - -.noty_theme__mint.noty_bar .noty_body { - padding: 10px; - font-size: 14px; -} - -.noty_theme__mint.noty_bar .noty_buttons { - padding: 10px; -} - -.noty_theme__mint.noty_type__alert, .noty_theme__mint.noty_type__notification { - background-color: #fff; - border-bottom: 1px solid #D1D1D1; - color: #2F2F2F; -} - -.noty_theme__mint.noty_type__warning { - background-color: #FFAE42; - border-bottom: 1px solid #E89F3C; - color: #fff; -} - -.noty_theme__mint.noty_type__error { - background-color: #DE636F; - border-bottom: 1px solid #CA5A65; - color: #fff; -} - -.noty_theme__mint.noty_type__info, .noty_theme__mint.noty_type__information { - background-color: #7F7EFF; - border-bottom: 1px solid #7473E8; - color: #fff; -} - -.noty_theme__mint.noty_type__success { - background-color: #AFC765; - border-bottom: 1px solid #A0B55C; - color: #fff; -} - -.el-table+.pagination-bar { - margin-top: 15px; -} - -.el-dialog__body { - padding: 20px; -} - -.el-dialog__footer>.el-button+.el-button { - margin-left: 5px; -} - -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: rgba(0, 0, 0, 0.05); - border-radius: 16px; -} - -::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.2); - border-radius: 16px; -} - -body, input, textarea, select, button { - font-family: 'Noto Sans JP', 'Noto Sans KR', 'Meiryo UI', 'Malgun Gothic', 'Segoe UI', sans-serif; - line-height: normal; -} - -a { - color: #409eff; -} - -.x-link { - cursor: pointer; -} - -.x-link:hover { - text-decoration: underline; -} - -.x-ellipsis { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.x-app { - display: flex; - position: absolute; - width: 100%; - height: 100%; - overflow: hidden auto; -} - -.x-container { - flex: 1; - padding: 10px; - overflow: hidden auto; - background: #fff; - position: relative; -} - -.x-login-container { - display: flex; - position: absolute; - width: 100%; - height: 100%; - background: #fff; - /* modal 시작이 2000이라서 */ - z-index: 1999; -} - -.x-menu-container { - flex: none; - overflow: hidden auto; - background: #383838; -} - -.x-menu-container>.el-menu { - background: 0; - border: 0; -} - -.el-menu-item.is-active::before { - position: absolute; - content: ''; - left: 1px; - top: 4px; - width: 2px; - height: 48px; - background: #DCDFE6; -} - -.el-menu-item.notify::after { - position: absolute; - content: ''; - right: 4px; - top: 4px; - width: 4px; - height: 4px; - background: #EBEEF5; - border-radius: 50% -} - -.x-aside-container { - flex: none; - width: 236px; - display: flex; - flex-direction: column; - background: #f8f8f8; -} - -.el-popper.x-quick-search { - min-width: 0 !important; - width: 225px; -} - -.el-popper.x-quick-search .el-select-dropdown__item { - padding: 0 10px; - width: 100%; - height: auto; - font-size: 12px; - line-height: normal; -} - -.x-friend-list { - overflow: hidden auto; - padding: 0 10px; -} - -.x-friend-group>.el-icon-arrow-right { - transition: transform .3s; -} - -.x-friend-group>.el-icon-arrow-right.rotate { - transform: rotate(90deg); -} - -.x-aside-container>.x-friend-list { - flex: 1; -} - -.x-dialog .x-friend-list { - display: flex; - align-items: flex-start; - flex-wrap: wrap; - max-height: 150px; -} - -.x-friend-list>.x-friend-group { - padding: 20px 0 5px; - font-weight: bold; - font-size: 12px; -} - -.x-friend-item { - display: flex; - align-items: center; - padding: 5px; - cursor: pointer; - font-size: 12px; - box-sizing: border-box; -} - -.x-friend-item:hover { - background: #f0f0f0; - border-radius: 2px; -} - -.x-aside-container>.x-friend-list>.x-friend-item:hover { - background: #fff; - border-radius: 2px; -} - -.el-select-dropdown__item .x-friend-item:hover { - background: none; - border-radius: 0; -} - -.x-dialog .x-friend-item { - width: 175px; -} - -.x-friend-item>.avatar { - flex: none; - width: 40px; - height: 40px; - margin-right: 8px; - display: inline-block; - position: relative; -} - -.x-friend-item>img.avatar { - width: 50px; - height: 37.5px; - margin-left: 5px; - margin-right: 0; - border-radius: 2px; -} - -.x-friend-item>.avatar>img { - width: 100%; - height: 100%; - border-radius: 40%; - object-fit: cover; -} - -.x-friend-item>.avatar.offline>img { - filter: grayscale(1); -} - -.x-friend-item:hover>.avatar.offline>img { - filter: none; -} - -.x-friend-item>.avatar.active::after, .x-friend-item>.avatar.joinme::after, .x-friend-item>.avatar.busy::after { - content: ''; - position: absolute; - right: 0; - bottom: 0; - width: 8px; - height: 8px; - border-radius: 50%; - border: 2px solid #fff; - background: #909399; -} - -.x-friend-item>.avatar.active::after { - background: #67C23A; -} - -.x-friend-item>.avatar.joinme::after { - background: #409EFF; -} - -.x-friend-item>.avatar.askme::after { - background: #FD9200; -} - -.x-friend-item>.avatar.busy::after { - background: #F56C6C; -} - -.x-friend-item.offline>.avatar::after { - display: none; -} - -.x-friend-item>.detail { - flex: 1; - overflow: hidden; -} - -.x-friend-item>.detail>.name, .x-friend-item>.detail>.extra { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.x-friend-item>.detail>.name { - color: #303133; - font-weight: bold; -} - -.x-friend-item>.detail>.extra { - color: #606266; - font-weight: normal; -} - -.x-dialog>.el-dialog { - margin-bottom: 10px; - max-width: 100%; -} - -.x-user-dialog>.el-dialog>.el-dialog__header, .x-world-dialog>.el-dialog>.el-dialog__header, .x-avatar-dialog>.el-dialog>.el-dialog__header { - padding: 0; - display: none; -} - -.x-user-dialog>.el-dialog>.el-dialog__body, .x-world-dialog>.el-dialog>.el-dialog__body, .x-avatar-dialog>.el-dialog>.el-dialog__body { - padding: 20px; -} - -.el-popper.hex { - font-family: monospace; - text-align: center; - min-width: auto; - padding: 10px; -} - -i.x-user-status { - display: inline-block; - width: 10px; - height: 10px; - border-radius: 50%; - background: gray; -} - -i.x-user-status.active { - background: #67C23A; -} - -i.x-user-status.joinme { - background: #409EFF; -} - -i.x-user-status.askme { - background: #FD9200; -} - -i.x-user-status.busy { - background: #F56C6C; -} - -.x-friend-item>.detail>.name.x-tag-untrusted { - color: rgb(204, 204, 204); -} - -.el-tag.x-tag-untrusted { - border-color: rgb(204, 204, 204); - color: rgb(204, 204, 204); -} - -.x-friend-item>.detail>.name.x-tag-basic { - color: rgb(23, 120, 255); -} - -.el-tag.x-tag-basic { - border-color: rgb(23, 120, 255); - color: rgb(23, 120, 255); -} - -.x-friend-item>.detail>.name.x-tag-known { - color: rgb(43, 207, 92); -} - -.el-tag.x-tag-known { - border-color: rgb(43, 207, 92); - color: rgb(43, 207, 92); -} - -.x-friend-item>.detail>.name.x-tag-trusted { - color: rgb(255, 123, 66); -} - -.el-tag.x-tag-trusted { - border-color: rgb(255, 123, 66); - color: rgb(255, 123, 66); -} - -.x-friend-item>.detail>.name.x-tag-veteran { - color: rgb(129, 67, 230); -} - -.el-tag.x-tag-veteran { - border-color: rgb(129, 67, 230); - color: rgb(129, 67, 230); -} - -.x-friend-item>.detail>.name.x-tag-legend { - /*color: rgb(255, 255, 0);*/ - color: rgb(255, 208, 0); -} - -.el-tag.x-tag-legend { - /*border-color: rgb(255, 255, 0); - color: rgb(255, 255, 0);*/ - border-color: rgb(255, 208, 0); - color: rgb(255, 208, 0); -} - -.x-friend-item>.detail>.name.x-tag-legendary { - color: rgb(0, 0, 0); -} - -.el-tag.x-tag-legendary { - border-color: rgb(0, 0, 0); - color: rgb(0, 0, 0); -} - -.x-friend-item>.detail>.name.x-tag-vip { - color: rgb(181, 38, 38); -} - -.el-tag.x-tag-vip { - border-color: rgb(181, 38, 38); - color: rgb(181, 38, 38); -} - -.x-friend-item>.detail>.name.x-tag-troll { - color: rgb(120, 47, 47); -} - -.el-tag.x-tag-troll { - border-color: rgb(120, 47, 47); - color: rgb(120, 47, 47); -} - -.x-friend-item>.detail>.name.x-tag-friend { - /*color: rgb(255, 255, 0);*/ - color: rgb(255, 208, 0); -} - -.el-tag.x-tag-friend { - /*border-color: rgb(255, 255, 0); - color: rgb(255, 255, 0);*/ - border-color: rgb(255, 208, 0); - color: rgb(255, 208, 0); -} - -.el-tree-node { - white-space: normal; -} - -.el-tree-node__content { - height: auto; -} - -.x-user-dialog .el-textarea__inner { - background: none; - border: 0; - border-radius: 2px; - padding: 0; -} \ No newline at end of file +.color-palettes{background:#409eff;background:#67c23a;background:#e6a23c;background:#f56c6c;background:#909399;background:#fd9200;background:#e6e6e6;background:#c0c4cc}.noty_layout{word-break:break-all}.noty_theme__mint.noty_bar{margin:4px 0;overflow:hidden;border-radius:2px;position:relative}.noty_theme__mint.noty_bar .noty_body{padding:10px;font-size:14px}.noty_theme__mint.noty_bar .noty_buttons{padding:10px}.noty_theme__mint.noty_type__alert,.noty_theme__mint.noty_type__notification{background-color:#fff;border-bottom:1px solid #d1d1d1;color:#2f2f2f}.noty_theme__mint.noty_type__warning{background-color:#ffae42;border-bottom:1px solid #e89f3c;color:#fff}.noty_theme__mint.noty_type__error{background-color:#de636f;border-bottom:1px solid #ca5a65;color:#fff}.noty_theme__mint.noty_type__info,.noty_theme__mint.noty_type__information{background-color:#7f7eff;border-bottom:1px solid #7473e8;color:#fff}.noty_theme__mint.noty_type__success{background-color:#afc765;border-bottom:1px solid #a0b55c;color:#fff}.el-table+.pagination-bar{margin-top:15px}.el-dialog__body{padding:20px}.el-dialog__footer>.el-button+.el-button{margin-left:5px}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:rgba(0,0,0,.05);border-radius:16px}::-webkit-scrollbar-thumb{background:rgba(0,0,0,.2);border-radius:16px}body,input,textarea,select,button{font-family:"Noto Sans JP","Noto Sans KR","Meiryo UI","Malgun Gothic","Segoe UI",sans-serif;line-height:normal}a{color:#409eff}.x-link{cursor:pointer}.x-link:hover{text-decoration:underline}.x-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.x-app{display:flex;position:absolute;width:100%;height:100%;overflow:hidden auto}.x-container{flex:1;padding:10px;overflow:hidden auto;background:#fff;position:relative}.x-login-container{display:flex;position:absolute;width:100%;height:100%;background:#fff;z-index:1999}.x-menu-container{flex:none;overflow:hidden auto;background:#383838}.x-menu-container>.el-menu{background:0;border:0}.el-menu-item.is-active::before{position:absolute;content:"";left:1px;top:4px;width:2px;height:48px;background:#dcdfe6}.el-menu-item.notify::after{position:absolute;content:"";right:4px;top:4px;width:4px;height:4px;background:#ebeef5;border-radius:50%}.x-aside-container{flex:none;width:236px;display:flex;flex-direction:column;background:#f8f8f8}.el-popper.x-quick-search{min-width:0 !important;width:225px}.el-popper.x-quick-search .el-select-dropdown__item{padding:0 10px;width:100%;height:auto;font-size:12px;line-height:normal}.x-friend-list{overflow:hidden auto;padding:0 10px}.x-friend-group>.el-icon-arrow-right{transition:transform .3s}.x-friend-group>.el-icon-arrow-right.rotate{transform:rotate(90deg)}.x-aside-container>.x-friend-list{flex:1}.x-dialog .x-friend-list{display:flex;align-items:flex-start;flex-wrap:wrap;max-height:150px}.x-friend-list>.x-friend-group{padding:20px 0 5px;font-weight:bold;font-size:12px}.x-friend-item{display:flex;align-items:center;padding:5px;cursor:pointer;font-size:12px;box-sizing:border-box}.x-friend-item:hover{background:#f0f0f0;border-radius:2px}.x-aside-container>.x-friend-list>.x-friend-item:hover{background:#fff;border-radius:2px}.el-select-dropdown__item .x-friend-item:hover{background:none;border-radius:0}.x-dialog .x-friend-item{width:175px}.x-friend-item>.avatar{flex:none;width:40px;height:40px;margin-right:8px;display:inline-block;position:relative}.x-friend-item>img.avatar{width:50px;height:37.5px;margin-left:5px;margin-right:0;border-radius:2px}.x-friend-item>.avatar>img{width:100%;height:100%;border-radius:40%;object-fit:cover}.x-friend-item>.avatar.offline>img{filter:grayscale(1)}.x-friend-item:hover>.avatar.offline>img{filter:none}.x-friend-item>.avatar.active::after,.x-friend-item>.avatar.joinme::after,.x-friend-item>.avatar.busy::after{content:"";position:absolute;right:0;bottom:0;width:8px;height:8px;border-radius:50%;border:2px solid #fff;background:#909399}.x-friend-item>.avatar.active::after{background:#67c23a}.x-friend-item>.avatar.joinme::after{background:#409eff}.x-friend-item>.avatar.askme::after{background:#fd9200}.x-friend-item>.avatar.busy::after{background:#f56c6c}.x-friend-item.offline>.avatar::after{display:none}.x-friend-item>.detail{flex:1;overflow:hidden}.x-friend-item>.detail>.name,.x-friend-item>.detail>.extra{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.x-friend-item>.detail>.name{color:#303133;font-weight:bold}.x-friend-item>.detail>.extra{color:#606266;font-weight:normal}.x-dialog>.el-dialog{margin-bottom:10px;max-width:100%}.x-user-dialog>.el-dialog>.el-dialog__header,.x-world-dialog>.el-dialog>.el-dialog__header,.x-avatar-dialog>.el-dialog>.el-dialog__header{padding:0;display:none}.x-user-dialog>.el-dialog>.el-dialog__body,.x-world-dialog>.el-dialog>.el-dialog__body,.x-avatar-dialog>.el-dialog>.el-dialog__body{padding:20px}.el-popper.hex{font-family:monospace;text-align:center;min-width:auto;padding:10px}i.x-user-status{display:inline-block;width:10px;height:10px;border-radius:50%;background:gray}i.x-user-status.active{background:#67c23a}i.x-user-status.joinme{background:#409eff}i.x-user-status.askme{background:#fd9200}i.x-user-status.busy{background:#f56c6c}.x-friend-item>.detail>.name.x-tag-untrusted{color:#ccc}.el-tag.x-tag-untrusted{border-color:#ccc;color:#ccc}.x-friend-item>.detail>.name.x-tag-basic{color:#1778ff}.el-tag.x-tag-basic{border-color:#1778ff;color:#1778ff}.x-friend-item>.detail>.name.x-tag-known{color:#2bcf5c}.el-tag.x-tag-known{border-color:#2bcf5c;color:#2bcf5c}.x-friend-item>.detail>.name.x-tag-trusted{color:#ff7b42}.el-tag.x-tag-trusted{border-color:#ff7b42;color:#ff7b42}.x-friend-item>.detail>.name.x-tag-veteran{color:#8143e6}.el-tag.x-tag-veteran{border-color:#8143e6;color:#8143e6}.x-friend-item>.detail>.name.x-tag-legend{color:#ffd000}.el-tag.x-tag-legend{border-color:#ffd000;color:#ffd000}.x-friend-item>.detail>.name.x-tag-legendary{color:#000}.el-tag.x-tag-legendary{border-color:#000;color:#000}.x-friend-item>.detail>.name.x-tag-vip{color:#b52626}.el-tag.x-tag-vip{border-color:#b52626;color:#b52626}.x-friend-item>.detail>.name.x-tag-troll{color:#782f2f}.el-tag.x-tag-troll{border-color:#782f2f;color:#782f2f}.x-friend-item>.detail>.name.x-tag-friend{color:#ffd000}.el-tag.x-tag-friend{border-color:#ffd000;color:#ffd000}.el-tree-node{white-space:normal}.el-tree-node__content{height:auto}.x-user-dialog .el-textarea__inner{background:none;border:0;border-radius:2px;padding:0} diff --git a/html/app.dark.css b/html/app.dark.css index 25c3fd14..8e890ff7 100644 --- a/html/app.dark.css +++ b/html/app.dark.css @@ -1,325 +1 @@ -@charset "utf-8"; -/* -Copyright(c) 2019-2020 pypy and individual contributors. -All rights reserved. - -This work is licensed under the terms of the MIT license. -For a copy, see . -*/ - -::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.05); - border-radius: 16px; -} - -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.2); - border-radius: 16px; -} - -.el-loading-mask { - background-color: rgba(0, 0, 0, 0.6); -} - -.el-input__inner { - background-color: #444444; - border: #333333; -} - -.el-table td, .el-table th.is-leaf { - background-color: #292929; - border-bottom: 1px solid #5f5f5f; -} - -.el-table--border::after, .el-table--group::after, .el-table::before { - background-color: #5f5f5f; -} - -.el-table--striped .el-table__body tr.el-table__row--striped td { - background-color: #202020; -} - -.el-table--enable-row-hover .el-table__body tr:hover>td { - background-color: #323232; -} - -.el-pagination .btn-next, .el-pagination .btn-prev { - background-color: #333333; - color: #bbbbbb; -} - -.el-pagination button:disabled { - background-color: #333333; - color: #101010; -} - -.el-dialog, .el-pager li { - background-color: #333333; -} - -.el-pager li { - color: #bbbbbb; -} - -.el-table { - color: #ffffff; -} - -.el-pagination__total { - color: #bbbbbb; -} - -.el-tag--plain.el-tag--info { - background-color: #333333; -} - -.el-tag--plain.el-tag--success { - background-color: #333333; -} - -.el-tag--plain.el-tag--success { - background-color: #333333; -} - -.el-button { - color: #c5cad6; -} - -.el-button:not(.el-button--text) { - background-color: #353535; - border-color: #404040; -} - -.el-button:not(.el-button--text):focus, .el-button:not(.el-button--text):hover { - color: #000000; - border-color: #656565; - background-color: #737373; -} - -.el-tabs__item { - color: #c2c4ca; -} - -.el-tabs--card>.el-tabs__header { - border-bottom-color: #5f5f5f; -} - -.el-dropdown-menu { - background-color: #353535; - border-color: #404040; -} - -.el-dropdown-menu__item--divided:before { - background-color: #404040; -} - -.el-dropdown-menu__item { - color: #d4d4d4; -} - -.el-dropdown-menu__item:focus, .el-dropdown-menu__item:not(.is-disabled):hover { - background-color: #444444; - color: #66b1ff; -} - -.el-popper[x-placement^=bottom] .popper__arrow::after { - border-bottom-color: #333333; -} - -.el-popper[x-placement^=bottom] .popper__arrow { - border-bottom-color: #404040; -} - -.el-message-box { - background-color: #333333; - border-color: #5f5f5f; -} - -.el-tree { - background: #202020; - color: #bbbbbb; -} - -.el-menu-item:focus, .el-menu-item:hover { - background-color: #505050; -} - -.el-tabs--card>.el-tabs__header .el-tabs__item { - border-left-color: #5f5f5f; -} - -.el-tabs--card>.el-tabs__header .el-tabs__item.is-active { - border-left-color: #5f5f5f; -} - -.el-tabs--card>.el-tabs__header .el-tabs__nav { - border-color: #5f5f5f; -} - -.el-collapse-item__header { - background-color: inherit; - color: #d0d0d0; - border-bottom-color: #5f5f5f; -} - -.el-collapse-item__wrap { - background-color: #333333; - border-bottom-color: #5f5f5f; -} - -.el-message-box__title { - color: #909090; -} - -.el-dialog__title { - color: #909090; -} - -.el-message-box__content { - color: #a5a7ad; -} - -.el-input__inner { - color: #ffffff; -} - -.el-collapse-item__content { - color: #848484; -} - -.el-switch__core { - background-color: #212121; - border-color: #5f5f5f; -} - -.el-popover { - background-color: #333333; - border-color: #5f5f5f; -} - -.el-popper[x-placement^=right] .popper__arrow::after { - border-right-color: #5f5f5f; -} - -.el-popper[x-placement^=right] .popper__arrow { - border-right-color: #5f5f5f; -} - -.el-switch__label { - color: #a0a0a0; -} - -.el-table, .el-table__expanded-cell { - background-color: inherit; -} - -.el-tree-node__content:hover { - background-color: #272727; -} - -.el-tree-node:focus>.el-tree-node__content { - background-color: #333333; -} - -.el-select-dropdown { - background-color: #353535; -} - -.el-select-dropdown.is-multiple .el-select-dropdown__item.selected { - background-color: #404040; -} - -.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover { - background-color: #404040; -} - -.el-select-dropdown__item.hover, .el-select-dropdown__item:hover { - background-color: #3e3e3e; -} - -.el-tag.el-tag--info { - background-color: #404040; - border-color: #252525; -} - -.el-table__expanded-cell:hover { - background-color: #323232 !important; -} - -.el-tabs--card>.el-tabs__header .el-tabs__item.is-active { - border-bottom-color: #9c9c9c; -} - -.el-dialog__body { - color: #ffffff; -} - -.x-app { - background-color: #101010; -} - -.x-container { - background: #222; -} - -.x-login-container { - background-color: #101010; -} - -.x-aside-container { - background-color: #171717; -} - -.x-friend-list>.x-friend-group { - color: #ffffff; -} - -.x-friend-item:hover { - background: #3e3e3e; -} - -.x-friend-item>.avatar.active::after, .x-friend-item>.avatar.joinme::after, .x-friend-item>.avatar.askme::after, .x-friend-item>.avatar.busy::after { - border: 2px solid #000; -} - -.x-friend-item>.detail>.name { - color: #ffffff; -} - -.x-friend-item>.detail>.extra { - color: #c7c7c7; -} - -.x-friend-item>.detail>.name.x-tag-veteran { - color: rgb(177, 143, 255); -} - -.el-tag.x-tag-veteran { - border-color: rgb(177, 143, 255); - color: rgb(177, 143, 255); -} - -.x-friend-item>.detail>.name.x-tag-legendary { - color: rgb(255, 255, 255); -} - -.el-tag.x-tag-legendary { - border-color: rgb(255, 255, 255); - color: rgb(255, 255, 255); -} - -.x-user-dialog .el-textarea__inner { - color: #ffffff; -} - -html, body { - background-color: #101010; -} - -body, input, textarea, select, button { - color: #ffffff; -} - -.x-login-container p { - color: #dddddd; -} \ No newline at end of file +::-webkit-scrollbar-track{background:rgba(255,255,255,.05);border-radius:16px}::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2);border-radius:16px}.el-loading-mask{background-color:rgba(0,0,0,.6)}.el-input__inner{background-color:#444;border:#333}.el-table td,.el-table th.is-leaf{background-color:#292929;border-bottom:1px solid #5f5f5f}.el-table--border::after,.el-table--group::after,.el-table::before{background-color:#5f5f5f}.el-table--striped .el-table__body tr.el-table__row--striped td{background-color:#202020}.el-table--enable-row-hover .el-table__body tr:hover>td{background-color:#323232}.el-pagination .btn-next,.el-pagination .btn-prev{background-color:#333;color:#bbb}.el-pagination button:disabled{background-color:#333;color:#101010}.el-dialog,.el-pager li{background-color:#333}.el-pager li{color:#bbb}.el-table{color:#fff}.el-pagination__total{color:#bbb}.el-tag--plain.el-tag--info{background-color:#333}.el-tag--plain.el-tag--success{background-color:#333}.el-tag--plain.el-tag--success{background-color:#333}.el-button{color:#c5cad6}.el-button:not(.el-button--text){background-color:#353535;border-color:#404040}.el-button:not(.el-button--text):focus,.el-button:not(.el-button--text):hover{color:#000;border-color:#656565;background-color:#737373}.el-tabs__item{color:#c2c4ca}.el-tabs--card>.el-tabs__header{border-bottom-color:#5f5f5f}.el-dropdown-menu{background-color:#353535;border-color:#404040}.el-dropdown-menu__item--divided:before{background-color:#404040}.el-dropdown-menu__item{color:#d4d4d4}.el-dropdown-menu__item:focus,.el-dropdown-menu__item:not(.is-disabled):hover{background-color:#444;color:#66b1ff}.el-popper[x-placement^=bottom] .popper__arrow::after{border-bottom-color:#333}.el-popper[x-placement^=bottom] .popper__arrow{border-bottom-color:#404040}.el-message-box{background-color:#333;border-color:#5f5f5f}.el-tree{background:#202020;color:#bbb}.el-menu-item:focus,.el-menu-item:hover{background-color:#505050}.el-tabs--card>.el-tabs__header .el-tabs__item{border-left-color:#5f5f5f}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-left-color:#5f5f5f}.el-tabs--card>.el-tabs__header .el-tabs__nav{border-color:#5f5f5f}.el-collapse-item__header{background-color:inherit;color:#d0d0d0;border-bottom-color:#5f5f5f}.el-collapse-item__wrap{background-color:#333;border-bottom-color:#5f5f5f}.el-message-box__title{color:#909090}.el-dialog__title{color:#909090}.el-message-box__content{color:#a5a7ad}.el-input__inner{color:#fff}.el-collapse-item__content{color:#848484}.el-switch__core{background-color:#212121;border-color:#5f5f5f}.el-popover{background-color:#333;border-color:#5f5f5f}.el-popper[x-placement^=right] .popper__arrow::after{border-right-color:#5f5f5f}.el-popper[x-placement^=right] .popper__arrow{border-right-color:#5f5f5f}.el-switch__label{color:#a0a0a0}.el-table,.el-table__expanded-cell{background-color:inherit}.el-tree-node__content:hover{background-color:#272727}.el-tree-node:focus>.el-tree-node__content{background-color:#333}.el-select-dropdown{background-color:#353535}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected{background-color:#404040}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#404040}.el-select-dropdown__item.hover,.el-select-dropdown__item:hover{background-color:#3e3e3e}.el-tag.el-tag--info{background-color:#404040;border-color:#252525}.el-table__expanded-cell:hover{background-color:#323232 !important}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:#9c9c9c}.el-dialog__body{color:#fff}.x-app{background-color:#101010}.x-container{background:#222}.x-login-container{background-color:#101010}.x-aside-container{background-color:#171717}.x-friend-list>.x-friend-group{color:#fff}.x-friend-item:hover{background:#3e3e3e}.x-friend-item>.avatar.active::after,.x-friend-item>.avatar.joinme::after,.x-friend-item>.avatar.askme::after,.x-friend-item>.avatar.busy::after{border:2px solid #000}.x-friend-item>.detail>.name{color:#fff}.x-friend-item>.detail>.extra{color:#c7c7c7}.x-friend-item>.detail>.name.x-tag-veteran{color:#b18fff}.el-tag.x-tag-veteran{border-color:#b18fff;color:#b18fff}.x-friend-item>.detail>.name.x-tag-legendary{color:#fff}.el-tag.x-tag-legendary{border-color:#fff;color:#fff}.x-user-dialog .el-textarea__inner{color:#fff}html,body{background-color:#101010}body,input,textarea,select,button{color:#fff}.x-login-container p{color:#ddd} diff --git a/html/app.js b/html/app.js index 5e8d920d..5f62554a 100644 --- a/html/app.js +++ b/html/app.js @@ -1,6839 +1 @@ -// Copyright(c) 2019-2020 pypy and individual contributors. -// All rights reserved. -// -// This work is licensed under the terms of the MIT license. -// For a copy, see . - -CefSharp.BindObjectAsync( - 'VRCX', - 'VRCXStorage', - 'SQLite', - 'LogWatcher', - 'Discord' -).then(function () { - document.addEventListener('keyup', function (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 array = JSON.parse(this.Get(key)); - 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 object = JSON.parse(this.Get(key)); - if (object === Object(object)) { - return object; - } - } catch (err) { - console.error(err); - } - return {}; - }; - - VRCXStorage.SetObject = function (key, value) { - this.Set(key, JSON.stringify(value)); - }; - - setInterval(function () { - VRCXStorage.Flush(); - }, 5 * 60 * 1000); - - Noty.overrideDefaults({ - /* - animation: { - open: 'animated bounceInLeft', - close: 'animated bounceOutLeft' - }, - */ - layout: 'bottomLeft', - theme: 'mint', - timeout: 6000 - }); - - var removeFromArray = function (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 = function (tag) { - var s = String(tag); - return s.replace(/["&'<>]/gu, (c) => `&#${c.charCodeAt(0)};`); - }; - Vue.filter('escapeTag', escapeTag); - - var commaNumber = function (num) { - var s = String(Number(num)); - return s.replace(/(\d)(?=(\d{3})+(?!\d))/gu, '$1,'); - }; - Vue.filter('commaNumber', commaNumber); - - var formatDate = function (date, format) { - var dt = new Date(date); - if (isNaN(dt)) { - return escapeTag(date); - } - var hours = dt.getHours(); - var map = { - 'YYYY': String(10000 + dt.getFullYear()).substr(-4), - 'MM': String(101 + dt.getMonth()).substr(-2), - 'DD': String(100 + dt.getDate()).substr(-2), - 'HH24': String(100 + hours).substr(-2), - 'HH': String(100 + (hours > 12 - ? hours - 12 - : hours)).substr(-2), - 'MI': String(100 + dt.getMinutes()).substr(-2), - 'SS': String(100 + dt.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 = function (text) { - var s = String(text); - return s.split('').map((c) => c.charCodeAt(0).toString(16)).join(' '); - }; - Vue.filter('textToHex', textToHex); - - var timeToText = function (sec) { - var n = Number(sec); - if (isNaN(n)) { - return escapeTag(sec); - } - n = Math.floor(n / 1000); - var arr = []; - if (n < 0) { - n = -n; - } - if (n >= 86400) { - arr.push(`${Math.floor(n / 86400)}d`); - n %= 86400; - } - if (n >= 3600) { - arr.push(`${Math.floor(n / 3600)}h`); - n %= 3600; - } - if (n >= 60) { - arr.push(`${Math.floor(n / 60)}m`); - n %= 60; - } - if (n || - arr.length === 0) { - arr.push(`${n}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); - - var uuidv4 = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/gu, (c) => { - var v = Math.random() * 16 | 0; - if (c !== 'x') { - v |= 8; - } - return v.toString(16); - }); - - var $appDarkStyle = document.createElement('link'); - $appDarkStyle.disabled = true; - $appDarkStyle.rel = 'stylesheet'; - $appDarkStyle.href = `app.dark.css?_=${Date.now()}`; - document.head.appendChild($appDarkStyle); - - // - // Languages - // - - var subsetOfLanguages = { - eng: 'English', - kor: '한국어', - rus: 'Русский', - spa: 'Español', - por: 'Português', - zho: '中文', - deu: 'Deutsch', - jpn: '日本語', - fra: 'Français', - swe: 'Svenska', - nld: 'Nederlands', - pol: 'Polski', - dan: 'Dansk', - nor: 'Norsk', - ita: 'Italiano', - tha: 'ภาษาไทย', - fin: 'Suomi', - hun: 'Magyar', - ces: 'Čeština', - tur: 'Türkçe', - ara: 'العربية' - }; - - // vrchat to famfamfam - var languageMappings = { - eng: 'us', - kor: 'kr', - rus: 'ru', - spa: 'es', - por: 'pt', - zho: 'cn', - deu: 'de', - jpn: 'jp', - fra: 'fr', - swe: 'se', - nld: 'nl', - pol: 'pl', - dan: 'dk', - nor: 'no', - ita: 'it', - tha: 'th', - fin: 'fi', - hun: 'hu', - ces: 'cz', - tur: 'tr', - ara: 'ae' - }; - - // - // API - // - - var API = {}; - - API.eventHandlers = new Map(); - - API.$emit = function (name, ...args) { - // console.log(name, ...args); - var handlers = this.eventHandlers.get(name); - if (handlers === undefined) { - return; - } - try { - for (var handler of handlers) { - handler.apply(this, args); - } - } catch (err) { - console.error(err); - } - }; - - API.$on = function (name, handler) { - var handlers = this.eventHandlers.get(name); - if (handlers === undefined) { - handlers = []; - this.eventHandlers.set(name, handlers); - } - handlers.push(handler); - }; - - API.$off = function (name, handler) { - var handlers = this.eventHandlers.get(name); - if (handlers === undefined) { - return; - } - var { length } = handlers; - for (var i = 0; i < length; ++i) { - if (handlers[i] === handler) { - if (length > 1) { - handlers.splice(i, 1); - } else { - this.eventHandlers.delete(name); - } - break; - } - } - }; - - API.pendingGetRequests = new Map(); - - API.call = function (endpoint, options) { - var resource = `https://api.vrchat.cloud/api/1/${endpoint}`; - var init = { - method: 'GET', - mode: 'cors', - credentials: 'include', - cache: 'no-cache', - redirect: 'follow', - referrerPolicy: 'no-referrer', - ...options - }; - var { params } = init; - var isGetRequest = init.method === 'GET'; - if (isGetRequest) { - // transform body to url - if (params === Object(params)) { - var url = new URL(resource); - var { searchParams } = url; - for (var key in params) { - searchParams.set(key, params[key]); - } - resource = url.toString(); - } - // merge requests - var req = this.pendingGetRequests.get(resource); - if (req !== undefined) { - return req; - } - } else { - init.headers = { - 'Content-Type': 'application/json;charset=utf-8', - ...init.headers - }; - init.body = params === Object(params) - ? JSON.stringify(params) - : '{}'; - } - var req = fetch(resource, init).catch((err) => { - this.$throw(0, err); - }).then((res) => res.json().catch(() => { - if (res.ok) { - this.$throw(0, 'Invalid JSON response'); - } - this.$throw(res.status); - }).then((json) => { - if (res.ok) { - if (json.success === Object(json.success)) { - new Noty({ - type: 'success', - text: escapeTag(json.success.message) - }).show(); - } - return json; - } - if (json === Object(json)) { - if (json.error === Object(json.error)) { - 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 - ); - } - } - this.$throw(res.status, json); - return json; - })); - if (isGetRequest) { - req.finally(() => { - this.pendingGetRequests.delete(resource); - }); - this.pendingGetRequests.set(resource, req); - } - return req; - }; - - API.statusCodes = { - 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' - }; - - // FIXME : extra를 없애줘 - API.$throw = function (code, error, extra) { - var text = []; - if (code > 0) { - var status = this.statusCodes[code]; - if (status === undefined) { - text.push(`${code}`); - } else { - text.push(`${code} ${status}`); - } - } - if (error !== undefined) { - text.push(JSON.stringify(error)); - } - if (extra !== undefined) { - text.push(JSON.stringify(extra)); - } - text = text.map((s) => escapeTag(s)).join('
'); - if (text.length) { - new Noty({ - type: 'error', - text - }).show(); - } - throw new Error(text); - }; - - API.$bulk = function (options, args) { - if (options.handle !== undefined) { - options.handle.call(this, args, options); - } - if (args.json.length > 0 && - (options.params.offset += args.json.length, - // eslint-disable-next-line no-nested-ternary - options.N > 0 - ? options.N > options.params.offset - : options.N < 0 - ? args.json.length - : options.params.n === args.json.length)) { - this.bulk(options); - } else if (options.done !== undefined) { - options.done.call(this, true, options); - } - return args; - }; - - API.bulk = function (options) { - this[options.fn](options.params).catch((err) => { - if (options.done !== undefined) { - options.done.call(this, false, options); - } - throw err; - }).then((args) => this.$bulk(options, args)); - }; - - // API: Config - - API.cachedConfig = {}; - - API.$on('CONFIG', function (args) { - args.ref = this.applyConfig(args.json); - }); - - API.applyConfig = function (json) { - var ref = { - clientApiKey: '', - ...json - }; - this.cachedConfig = ref; - return ref; - }; - - API.getConfig = function () { - return this.call('config', { - method: 'GET' - }).then((json) => { - var args = { - json - }; - this.$emit('CONFIG', args); - return args; - }); - }; - - // API: Location - - API.parseLocation = function (tag) { - tag = String(tag || ''); - var ctx = { - tag, - isOffline: false, - isPrivate: false, - worldId: '', - instanceId: '', - instanceName: '', - accessType: '', - userId: null, - hiddenId: null, - privateId: null, - friendsId: null, - canRequestInvite: false - }; - if (tag === 'offline') { - ctx.isOffline = true; - } else if (tag === 'private') { - ctx.isPrivate = true; - } else if (tag.startsWith('local') === false) { - var sep = tag.indexOf(':'); - if (sep >= 0) { - ctx.worldId = tag.substr(0, sep); - ctx.instanceId = tag.substr(sep + 1); - ctx.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') { - ctx.hiddenId = value; - } else if (key === 'private') { - ctx.privateId = value; - } else if (key === 'friends') { - ctx.friendsId = value; - } else if (key === 'canRequestInvite') { - ctx.canRequestInvite = true; - } - } else { - ctx.instanceName = s; - } - }); - ctx.accessType = 'public'; - if (ctx.privateId !== null) { - if (ctx.canRequestInvite) { - // InvitePlus - ctx.accessType = 'invite+'; - } else { - // InviteOnly - ctx.accessType = 'invite'; - } - ctx.userId = ctx.privateId; - } else if (ctx.friendsId !== null) { - // FriendsOnly - ctx.accessType = 'friends'; - ctx.userId = ctx.friendsId; - } else if (ctx.hiddenId !== null) { - // FriendsOfGuests - ctx.accessType = 'friends+'; - ctx.userId = ctx.hiddenId; - } - } else { - ctx.worldId = tag; - } - } - return ctx; - }; - - Vue.component('launch', { - template: '', - 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: '{{ text }}', - 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.cachedWorlds.get(L.worldId); - if (ref === undefined) { - API.getWorld({ - worldId: L.worldId - }).then((args) => { - if (L.tag === this.location) { - if (L.instanceId) { - this.text = `${args.json.name} #${L.instanceName} ${L.accessType}`; - } else { - this.text = args.json.name; - } - } - return args; - }); - } else if (L.instanceId) { - this.text = `${ref.name} #${L.instanceName} ${L.accessType}`; - } else { - this.text = ref.name; - } - } - }, - 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.cachedUsers = new Map(); - API.currentUser = {}; - - API.$on('LOGOUT', function () { - VRCX.DeleteAllCookies(); - this.isLoggedIn = false; - }); - - API.$on('USER:CURRENT', function (args) { - args.ref = this.applyCurrentUser(args.json); - }); - - API.$on('USER:CURRENT:SAVE', function (args) { - this.$emit('USER:CURRENT', args); - }); - - API.$on('USER', function (args) { - args.ref = this.applyUser(args.json); - }); - - API.$on('USER:LIST', function (args) { - for (var json of args.json) { - this.$emit('USER', { - json, - params: { - userId: json.id - } - }); - } - }); - - API.logout = function () { - return this.call('logout', { - method: 'PUT' - }).finally(() => { - this.$emit('LOGOUT'); - }); - }; - - /* - params: { - username: string, - password: string - } - */ - API.login = function (params) { - var auth = `${params.username}:${params.password}`; - auth = encodeURIComponent(auth); - auth = auth.replace(/%([0-9A-F]{2})/gu, (_, s) => String.fromCharCode(parseInt(s, 16))); - auth = auth.replace('%', '%25'); - auth = btoa(auth); - return this.call(`auth/user?apiKey=${this.cachedConfig.clientApiKey}`, { - method: 'GET', - headers: { - Authorization: `Basic ${auth}` - } - }).then((json) => { - var args = { - json, - params, - origin: true - }; - if (json.requiresTwoFactorAuth) { - this.$emit('USER:2FA', args); - } else { - this.$emit('USER:CURRENT', args); - } - return args; - }); - }; - - /* - params: { - steamTicket: string - } - */ - API.loginWithSteam = function (params) { - return this.call(`auth/steam?apiKey=${this.cachedConfig.clientApiKey}`, { - method: 'POST', - params - }).then((json) => { - var args = { - json, - params, - origin: true - }; - if (json.requiresTwoFactorAuth) { - this.$emit('USER:2FA', args); - } else { - this.$emit('USER:CURRENT', args); - } - return args; - }); - }; - - /* - params: { - code: string - } - */ - API.verifyOTP = function (params) { - return this.call('auth/twofactorauth/otp/verify', { - method: 'POST', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('OTP', args); - return args; - }); - }; - - /* - params: { - code: string - } - */ - API.verifyTOTP = function (params) { - return this.call('auth/twofactorauth/totp/verify', { - method: 'POST', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('TOTP', args); - return args; - }); - }; - - API.applyUserTrustLevel = function (ref) { - ref.$isModerator = ref.developerType && - ref.developerType !== 'none'; - ref.$isTroll = false; - var { tags } = ref; - if (tags.includes('admin_moderator')) { - ref.$isModerator = true; - } - if (tags.includes('system_troll') || - tags.includes('system_probable_troll')) { - ref.$isTroll = true; - } - if (tags.includes('system_legend')) { - ref.$trustLevel = 'Legendary User'; - ref.$trustClass = 'x-tag-legendary'; - } else if (tags.includes('system_trust_legend')) { - ref.$trustLevel = 'Veteran User'; - ref.$trustClass = 'x-tag-legend'; - } else if (tags.includes('system_trust_veteran')) { - ref.$trustLevel = 'Trusted User'; - ref.$trustClass = 'x-tag-veteran'; - } else if (tags.includes('system_trust_trusted')) { - ref.$trustLevel = 'Known User'; - ref.$trustClass = 'x-tag-trusted'; - } else if (tags.includes('system_trust_known')) { - ref.$trustLevel = 'User'; - ref.$trustClass = 'x-tag-known'; - } else if (tags.includes('system_trust_basic')) { - ref.$trustLevel = 'New User'; - ref.$trustClass = 'x-tag-basic'; - } else { - ref.$trustLevel = 'Visitor'; - ref.$trustClass = 'x-tag-untrusted'; - } - if (ref.$isModerator) { - ref.$trustLevel = 'VRChat Team'; - ref.$trustClass = 'x-tag-vip'; - } else if (ref.$isTroll) { - ref.$trustLevel = 'Nuisance'; - ref.$trustClass = 'x-tag-troll'; - } - }; - - // FIXME: it may performance issue. review here - API.applyUserLanguage = function (ref) { - ref.$languages = []; - var { tags } = ref; - for (var tag of tags) { - if (tag.startsWith('language_') === false) { - continue; - } - var key = tag.substr(9); - var value = subsetOfLanguages[key]; - if (value === undefined) { - continue; - } - ref.$languages.push({ - key, - value - }); - } - }; - - API.applyCurrentUser = function (json) { - var ref = this.currentUser; - if (this.isLoggedIn) { - Object.assign(ref, json); - if (ref.homeLocation !== ref.$homeLocation.tag) { - ref.$homeLocation = this.parseLocation(ref.homeLocation); - } - this.applyUserTrustLevel(ref); - this.applyUserLanguage(ref); - } else { - ref = { - id: '', - username: '', - displayName: '', - bio: '', - bioLinks: [], - pastDisplayNames: [], - friends: [], - currentAvatarImageUrl: '', - currentAvatarThumbnailImageUrl: '', - currentAvatar: '', - homeLocation: '', - twoFactorAuthEnabled: false, - status: '', - statusDescription: '', - state: '', - tags: [], - developerType: '', - last_login: '', - last_platform: '', - allowAvatarCopying: false, - friendKey: '', - onlineFriends: [], - activeFriends: [], - offlineFriends: [], - // VRCX - $homeLocation: {}, - $isModerator: false, - $isTroll: false, - $trustLevel: 'Visitor', - $trustClass: 'x-tag-untrusted', - $languages: [], - // - ...json - }; - ref.$homeLocation = this.parseLocation(ref.homeLocation); - this.applyUserTrustLevel(ref); - this.applyUserLanguage(ref); - this.currentUser = ref; - this.isLoggedIn = true; - this.$emit('LOGIN', { - json, - ref - }); - } - VRCXStorage.SetObject('currentUser', ref); - return ref; - }; - - API.getCurrentUser = function () { - return this.call(`auth/user?apiKey=${this.cachedConfig.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; - }); - }; - - var userUpdateQueue = []; - var userUpdateTimer = null; - var queueUserUpdate = function (ctx) { - userUpdateQueue.push(ctx); - if (userUpdateTimer !== null) { - return; - } - userUpdateTimer = setTimeout(function () { - userUpdateTimer = null; - var { length } = userUpdateQueue; - for (var i = 0; i < length; ++i) { - API.$emit('USER:UPDATE', userUpdateQueue[i]); - } - userUpdateQueue.length = 0; - }, 1); - }; - - API.applyUser = function (json) { - var ref = this.cachedUsers.get(json.id); - if (ref === undefined) { - ref = { - id: '', - username: '', - displayName: '', - bio: '', - bioLinks: [], - currentAvatarImageUrl: '', - currentAvatarThumbnailImageUrl: '', - status: '', - statusDescription: '', - state: '', - tags: [], - developerType: '', - last_login: '', - last_platform: '', - allowAvatarCopying: false, - isFriend: false, - friendKey: '', - location: '', - worldId: '', - instanceId: '', - // VRCX - $location: {}, - $location_at: Date.now(), - $isModerator: false, - $isTroll: false, - $trustLevel: 'Visitor', - $trustClass: 'x-tag-untrusted', - $languages: [], - // - ...json - }; - ref.$location = this.parseLocation(ref.location); - this.applyUserTrustLevel(ref); - this.applyUserLanguage(ref); - this.cachedUsers.set(ref.id, ref); - } else { - var props = {}; - for (var prop in ref) { - if (ref[prop] !== Object(ref[prop])) { - props[prop] = true; - } - } - var $ref = { ...ref }; - Object.assign(ref, json); - if (ref.location !== ref.$location.tag) { - ref.$location = this.parseLocation(ref.location); - } - this.applyUserTrustLevel(ref); - this.applyUserLanguage(ref); - for (var prop in ref) { - if (ref[prop] !== Object(ref[prop])) { - props[prop] = true; - } - } - var has = false; - for (var prop in props) { - var asis = $ref[prop]; - var tobe = ref[prop]; - if (asis === tobe) { - delete props[prop]; - } else { - has = true; - props[prop] = [ - tobe, - asis - ]; - } - } - // FIXME - // if the status is offline, just ignore status and statusDescription only. - if (has && - (ref.status !== 'offline' && $ref.status !== 'offline')) { - if (props.location) { - var ts = Date.now(); - props.location.push(ts - ref.$location_at); - ref.$location_at = ts; - } - queueUserUpdate({ - ref, - props - }); - } - } - return ref; - }; - - /* - params: { - userId: string - } - */ - API.getUser = function (params) { - return this.call(`users/${params.userId}`, { - method: 'GET' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('USER', args); - return args; - }); - }; - - /* - params: { - userId: string - } - */ - API.getCachedUser = function (params) { - return new Promise((resolve, reject) => { - var ref = this.cachedUsers.get(params.userId); - if (ref === undefined) { - this.getUser(params).catch(reject).then(resolve); - } else { - resolve({ - cache: true, - json: ref, - params, - ref - }); - } - }); - }; - - /* - params: { - n: number, - offset: number, - search: string, - sort: string ('nuisanceFactor', 'created', '_created_at', 'last_login'), - order: string ('ascending', 'descending') - } - */ - API.getUsers = function (params) { - return this.call('users', { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('USER:LIST', args); - return args; - }); - }; - - /* - params: { - status: string ('active', 'offline', 'busy', 'ask me', 'join me'), - statusDescription: string - } - */ - API.saveCurrentUser = function (params) { - return this.call(`users/${this.currentUser.id}`, { - method: 'PUT', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('USER:CURRENT:SAVE', args); - return args; - }); - }; - - /* - params: { - tags: array[string] - } - */ - API.addUserTags = function (params) { - return this.call(`users/${this.currentUser.id}/addTags`, { - method: 'POST', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('USER:CURRENT:SAVE', args); - return args; - }); - }; - - /* - params: { - tags: array[string] - } - */ - API.removeUserTags = function (params) { - return this.call(`users/${this.currentUser.id}/removeTags`, { - method: 'POST', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('USER:CURRENT:SAVE', args); - return args; - }); - }; - - // API: World - - API.cachedWorlds = new Map(); - - API.$on('WORLD', function (args) { - args.ref = this.applyWorld(args.json); - }); - - API.$on('WORLD:LIST', function (args) { - for (var json of args.json) { - this.$emit('WORLD', { - json, - params: { - worldId: json.id - } - }); - } - }); - - API.applyWorld = function (json) { - var ref = this.cachedWorlds.get(json.id); - if (ref === undefined) { - ref = { - id: '', - name: '', - description: '', - authorId: '', - authorName: '', - capacity: 0, - tags: [], - releaseStatus: '', - imageUrl: '', - thumbnailImageUrl: '', - assetUrl: '', - assetUrlObject: {}, - pluginUrl: '', - pluginUrlObject: {}, - unityPackageUrl: '', - unityPackageUrlObject: {}, - unityPackages: [], - version: 0, - favorites: 0, - created_at: '', - updated_at: '', - publicationDate: '', - labsPublicationDate: '', - visits: 0, - popularity: 0, - heat: 0, - publicOccupants: 0, - privateOccupants: 0, - occupants: 0, - instances: [], - // VRCX - $isLabs: false, - // - ...json - }; - this.cachedWorlds.set(ref.id, ref); - } else { - Object.assign(ref, json); - } - ref.$isLabs = ref.tags.includes('system_labs'); - return ref; - }; - - /* - params: { - worldId: string - } - */ - API.getWorld = function (params) { - return this.call(`worlds/${params.worldId}`, { - method: 'GET' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('WORLD', args); - return args; - }); - }; - - /* - params: { - worldId: string - } - */ - API.getCachedWorld = function (params) { - return new Promise((resolve, reject) => { - var ref = this.cachedWorlds.get(params.worldId); - if (ref === undefined) { - this.getWorld(params).catch(reject).then(resolve); - } else { - resolve({ - cache: true, - json: ref, - params, - ref - }); - } - }); - }; - - /* - params: { - 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 - }, - option: string - */ - API.getWorlds = function (params, option) { - var endpoint = 'worlds'; - if (option !== undefined) { - endpoint = `worlds/${option}`; - } - return this.call(endpoint, { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('WORLD:LIST', args); - return args; - }); - }; - - // API: Friend - - API.friends200 = new Set(); - API.friends404 = new Map(); - API.isFriendsLoading = false; - - API.$on('LOGIN', function () { - this.friends200.clear(); - this.friends404.clear(); - this.isFriendsLoading = false; - }); - - API.$on('FRIEND:LIST', function (args) { - for (var json of args.json) { - this.$emit('USER', { - json, - params: { - userId: json.id - } - }); - this.friends200.add(json.id); - this.friends404.delete(json.id); - } - }); - - API.isAllFriendsRetrived = function (flag) { - if (flag) { - for (var id of this.currentUser.friends) { - if (this.friends200.has(id) === false) { - var n = this.friends404.get(id) || 0; - if (n < 2) { - this.friends404.set(id, n + 1); - } - } - } - } else { - for (var id of this.currentUser.friends) { - if (this.friends200.has(id) === false || - this.friends404.get(id) < 2) { - return false; - } - } - } - return true; - }; - - API.refreshFriends = function () { - var params = { - n: 100, - offset: 0, - offline: false - }; - var N = this.currentUser.onlineFriends.length; - if (N === 0) { - N = this.currentUser.friends.length; - if (N === 0 || - this.isAllFriendsRetrived(false)) { - return; - } - params.offline = true; - } - if (this.isFriendsLoading) { - return; - } - this.isFriendsLoading = true; - this.bulk({ - fn: 'getFriends', - N, - params, - done(ok, options) { - if (this.isAllFriendsRetrived(params.offline)) { - this.isFriendsLoading = false; - return; - } - var { length } = this.currentUser.friends; - options.N = length - params.offset; - if (options.N <= 0) { - options.N = length; - } - params.offset = 0; - params.offline = true; - this.bulk(options); - } - }); - }; - - /* - params: { - n: number, - offset: number, - offline: boolean - } - */ - API.getFriends = function (params) { - return this.call('auth/user/friends', { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FRIEND:LIST', args); - return args; - }); - }; - - /* - params: { - userId: string - } - */ - API.deleteFriend = function (params) { - return this.call(`auth/user/friends/${params.userId}`, { - method: 'DELETE' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FRIEND:DELETE', args); - return args; - }); - }; - - /* - params: { - userId: string - } - */ - API.sendFriendRequest = function (params) { - return this.call(`user/${params.userId}/friendRequest`, { - method: 'POST' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FRIEND:REQUEST', args); - return args; - }); - }; - - /* - params: { - userId: string - } - */ - API.cancelFriendRequest = function (params) { - return this.call(`user/${params.userId}/friendRequest`, { - method: 'DELETE' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FRIEND:REQUEST:CANCEL', args); - return args; - }); - }; - - /* - params: { - userId: string - } - */ - API.getFriendStatus = function (params) { - return this.call(`user/${params.userId}/friendStatus`, { - method: 'GET' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FRIEND:STATUS', args); - return args; - }); - }; - - // API: Avatar - - API.cachedAvatars = new Map(); - - API.$on('AVATAR', function (args) { - args.ref = this.applyAvatar(args.json); - }); - - API.$on('AVATAR:LIST', function (args) { - for (var json of args.json) { - this.$emit('AVATAR', { - json, - params: { - avatarId: json.id - } - }); - } - }); - - API.$on('AVATAR:SELECT', function (args) { - this.$emit('USER:CURRENT', args); - }); - - API.applyAvatar = function (json) { - var ref = this.cachedAvatars.get(json.id); - if (ref === undefined) { - ref = { - id: '', - name: '', - description: '', - authorId: '', - authorName: '', - tags: [], - assetUrl: '', - assetUrlObject: {}, - imageUrl: '', - thumbnailImageUrl: '', - releaseStatus: '', - version: 0, - unityPackages: [], - unityPackageUrl: '', - unityPackageUrlObject: {}, - created_at: '', - updated_at: '', - ...json - }; - this.cachedAvatars.set(ref.id, ref); - } else { - Object.assign(ref, json); - } - return ref; - }; - - /* - params: { - avatarId: string - } - */ - API.getAvatar = function (params) { - return this.call(`avatars/${params.avatarId}`, { - method: 'GET' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('AVATAR', args); - return args; - }); - }; - - /* - params: { - avatarId: string - } - */ - API.getCachedAvatar = function (params) { - return new Promise((resolve, reject) => { - var ref = this.cachedAvatars.get(params.avatarId); - if (ref === undefined) { - this.getAvatar(params).catch(reject).then(resolve); - } else { - resolve({ - cache: true, - json: ref, - params, - ref - }); - } - }); - }; - - /* - params: { - 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 (params) { - return this.call('avatars', { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('AVATAR:LIST', args); - return args; - }); - }; - - /* - params: { - avatarId: string - } - */ - API.selectAvatar = function (params) { - return this.call(`avatars/${params.avatarId}/select`, { - method: 'PUT', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('AVATAR:SELECT', args); - return args; - }); - }; - - // API: Notification - - API.cachedNotifications = new Map(); - API.isNotificationsLoading = false; - - API.$on('LOGIN', function () { - this.cachedNotifications.clear(); - this.isNotificationsLoading = false; - }); - - API.$on('NOTIFICATION', function (args) { - args.ref = this.applyNotification(args.json); - }); - - API.$on('NOTIFICATION:LIST', function (args) { - for (var json of args.json) { - this.$emit('NOTIFICATION', { - json, - params: { - notificationId: json.id - } - }); - } - }); - - API.$on('NOTIFICATION:ACCEPT', function (args) { - var ref = this.cachedNotifications.get(args.params.notificationId); - if (ref === undefined || - ref.$isDeleted) { - return; - } - args.ref = ref; - ref.$isDeleted = true; - this.$emit('NOTIFICATION:@DELETE', { - ref, - params: { - notificationId: ref.id - } - }); - this.$emit('FRIEND:ADD', { - params: { - userId: ref.senderUserId - } - }); - }); - - API.$on('NOTIFICATION:HIDE', function (args) { - var ref = this.cachedNotifications.get(args.params.notificationId); - if (ref === undefined && - ref.$isDeleted) { - return; - } - args.ref = ref; - ref.$isDeleted = true; - this.$emit('NOTIFICATION:@DELETE', { - ref, - params: { - notificationId: ref.id - } - }); - }); - - API.applyNotification = function (json) { - var ref = this.cachedNotifications.get(json.id); - if (ref === undefined) { - ref = { - id: '', - senderUserId: '', - senderUsername: '', - type: '', - message: '', - details: {}, - seen: false, - created_at: '', - // VRCX - $isDeleted: false, - $isExpired: false, - // - ...json - }; - this.cachedNotifications.set(ref.id, ref); - } else { - Object.assign(ref, json); - ref.$isExpired = false; - } - if (ref.details !== Object(ref.details)) { - var details = {}; - if (ref.details !== '{}') { - try { - var object = JSON.parse(ref.details); - if (object === Object(object)) { - details = object; - } - } catch (err) { - } - } - ref.details = details; - } - return ref; - }; - - API.expireNotifications = function () { - for (var ref of this.cachedNotifications.values()) { - ref.$isExpired = true; - } - }; - - API.deleteExpiredNotifcations = function () { - for (var ref of this.cachedNotifications.values()) { - if (ref.$isDeleted || - ref.$isExpired === false) { - continue; - } - ref.$isDeleted = true; - this.$emit('NOTIFICATION:@DELETE', { - ref, - params: { - notificationId: ref.id - } - }); - } - }; - - API.refreshNotifications = function () { - // NOTE : 캐시 때문에 after=~ 로는 갱신이 안됨. 그래서 첨부터 불러옴 - if (this.isNotificationsLoading) { - return; - } - this.isNotificationsLoading = true; - this.expireNotifications(); - this.bulk({ - fn: 'getNotifications', - N: -1, - params: { - n: 100, - offset: 0 - }, - done(ok) { - if (ok) { - this.deleteExpiredNotifcations(); - } - this.isNotificationsLoading = false; - } - }); - }; - - /* - params: { - n: number, - offset: number, - sent: boolean, - type: string, - after: string (ISO8601 or 'five_minutes_ago') - } - */ - API.getNotifications = function (params) { - return this.call('auth/user/notifications', { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('NOTIFICATION:LIST', args); - return args; - }); - }; - - API.clearNotifications = function () { - return this.call('auth/user/notifications/clear', { - method: 'PUT' - }).then((json) => { - var args = { - json - }; - // FIXME: NOTIFICATION:CLEAR 핸들링 - this.$emit('NOTIFICATION:CLEAR', args); - return args; - }); - }; - - /* - params: { - receiverUserId: string, - type: string, - message: string, - seen: boolean, - details: json-string - } - */ - API.sendNotification = function (params) { - return this.call(`user/${params.receiverUserId}/notification`, { - method: 'POST', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('NOTIFICATION:SEND', args); - return args; - }); - }; - - /* - params: { - notificationId: string - } - */ - API.acceptNotification = function (params) { - return this.call(`auth/user/notifications/${params.notificationId}/accept`, { - method: 'PUT' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('NOTIFICATION:ACCEPT', args); - return args; - }); - }; - - /* - params: { - notificationId: string - } - */ - API.hideNotification = function (params) { - return this.call(`auth/user/notifications/${params.notificationId}/hide`, { - method: 'PUT' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('NOTIFICATION:HIDE', args); - return args; - }); - }; - - API.getFriendRequest = function (userId) { - for (var ref of this.cachedNotifications.values()) { - if (ref.$isDeleted === false && - ref.type === 'friendRequest' && - ref.senderUserId === userId) { - return ref.id; - } - } - return ''; - }; - - API.parseInviteLocation = function (ref) { - try { - var L = API.parseLocation(ref.details.worldId); - if (L.worldId && L.instanceId) { - return `${ref.details.worldName} #${L.instanceName} ${L.accessType}`; - } - return ref.message || - ref.details.worldId || - ref.details.worldName; - } catch (err) { - return ''; - } - }; - - // API: PlayerModeration - - API.cachedPlayerModerations = new Map(); - API.isPlayerModerationsLoading = false; - - API.$on('LOGIN', function () { - this.cachedPlayerModerations.clear(); - this.isPlayerModerationsLoading = false; - }); - - API.$on('PLAYER-MODERATION', function (args) { - args.ref = this.applyPlayerModeration(args.json); - }); - - API.$on('PLAYER-MODERATION:LIST', function (args) { - for (var json of args.json) { - this.$emit('PLAYER-MODERATION', { - json, - params: { - playerModerationId: json.id - } - }); - } - }); - - API.$on('PLAYER-MODERATION:SEND', function (args) { - this.$emit('PLAYER-MODERATION', { - json: args.json, - params: { - playerModerationId: args.json.id - } - }); - }); - - API.$on('PLAYER-MODERATION:DELETE', function (args) { - var { type, moderated } = args.param; - var userId = this.currentUser.id; - for (var ref of this.cachedPlayerModerations.values()) { - if (ref.$isDeleted === false && - ref.type === type && - ref.targetUserId === moderated && - ref.sourceUserId === userId) { - ref.$isDeleted = true; - this.$emit('PLAYER-MODERATION:@DELETE', { - ref, - params: { - playerModerationId: ref.id - } - }); - } - } - }); - - API.applyPlayerModeration = function (json) { - var ref = this.cachedPlayerModerations.get(json.id); - if (ref === undefined) { - ref = { - id: '', - type: '', - sourceUserId: '', - sourceDisplayName: '', - targetUserId: '', - targetDisplayName: '', - created: '', - // VRCX - $isDeleted: false, - $isExpired: false, - // - ...json - }; - this.cachedPlayerModerations.set(ref.id, ref); - } else { - Object.assign(ref, json); - ref.$isExpired = false; - } - return ref; - }; - - API.expirePlayerModerations = function () { - for (var ref of this.cachedPlayerModerations.values()) { - ref.$isExpired = true; - } - }; - - API.deleteExpiredPlayerModerations = function () { - for (var ref of this.cachedPlayerModerations.values()) { - if (ref.$isDeleted || - ref.$isExpired === false) { - continue; - } - ref.$isDeleted = true; - this.$emit('PLAYER-MODERATION:@DELETE', { - ref, - params: { - playerModerationId: ref.id - } - }); - } - }; - - API.refreshPlayerModerations = function () { - if (this.isPlayerModerationsLoading) { - return; - } - this.isPlayerModerationsLoading = true; - this.expirePlayerModerations(); - Promise.all([ - this.getPlayerModerations(), - this.getPlayerModerationsAgainstMe() - ]).finally(() => { - this.isPlayerModerationsLoading = false; - }).then(() => { - this.deleteExpiredPlayerModerations(); - }); - }; - - 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; - }); - }; - - /* - params: { - moderated: string, - type: string - } - */ - // old-way: POST auth/user/blocks {blocked:userId} - API.sendPlayerModeration = function (params) { - return this.call('auth/user/playermoderations', { - method: 'POST', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('PLAYER-MODERATION:SEND', args); - return args; - }); - }; - - /* - params: { - moderated: string, - type: string - } - */ - // old-way: PUT auth/user/unblocks {blocked:userId} - API.deletePlayerModeration = function (params) { - return this.call('auth/user/unplayermoderate', { - method: 'PUT', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('PLAYER-MODERATION:DELETE', args); - return args; - }); - }; - - // API: Favorite - - API.cachedFavorites = new Map(); - API.cachedFavoritesByObjectId = new Map(); - API.cachedFavoriteGroups = new Map(); - API.cachedFavoriteGroupsByTypeName = new Map(); - API.favoriteFriendGroups = []; - API.favoriteWorldGroups = []; - API.favoriteAvatarGroups = []; - API.isFavoriteLoading = false; - API.isFavoriteGroupLoading = false; - - API.$on('LOGIN', function () { - this.cachedFavorites.clear(); - this.cachedFavoritesByObjectId.clear(); - this.cachedFavoriteGroups.clear(); - this.cachedFavoriteGroupsByTypeName.clear(); - this.favoriteFriendGroups = []; - this.favoriteWorldGroups = []; - this.favoriteAvatarGroups = []; - this.isFavoriteLoading = false; - this.isFavoriteGroupLoading = false; - this.refreshFavorites(); - }); - - API.$on('FAVORITE', function (args) { - var ref = this.applyFavorite(args.json); - if (ref.$isDeleted) { - return; - } - args.ref = ref; - }); - - API.$on('FAVORITE:@DELETE', function (args) { - var { ref } = args; - if (ref.$groupRef !== null) { - --ref.$groupRef.count; - } - }); - - API.$on('FAVORITE:LIST', function (args) { - for (var json of args.json) { - this.$emit('FAVORITE', { - json, - params: { - favoriteId: json.id - } - }); - } - }); - - API.$on('FAVORITE:ADD', function (args) { - this.$emit('FAVORITE', { - json: args.json, - params: { - favoriteId: args.json.id - } - }); - }); - - API.$on('FAVORITE:DELETE', function (args) { - var ref = this.cachedFavoritesByObjectId.get(args.params.objectId); - if (ref === undefined) { - return; - } - // 애초에 $isDeleted인데 여기로 올 수 가 있나..? - this.cachedFavoritesByObjectId.delete(args.params.objectId); - if (ref.$isDeleted) { - return; - } - args.ref = ref; - ref.$isDeleted = true; - API.$emit('FAVORITE:@DELETE', { - ref, - params: { - favoriteId: ref.id - } - }); - }); - - API.$on('FAVORITE:GROUP', function (args) { - var ref = this.applyFavoriteGroup(args.json); - if (ref.$isDeleted) { - return; - } - args.ref = ref; - if (ref.$groupRef !== null) { - ref.$groupRef.displayName = ref.displayName; - } - }); - - API.$on('FAVORITE:GROUP:LIST', function (args) { - for (var json of args.json) { - this.$emit('FAVORITE:GROUP', { - json, - params: { - favoriteGroupId: json.id - } - }); - } - }); - - API.$on('FAVORITE:GROUP:SAVE', function (args) { - this.$emit('FAVORITE:GROUP', { - json: args.json, - params: { - favoriteGroupId: args.json.id - } - }); - }); - - API.$on('FAVORITE:GROUP:CLEAR', function (args) { - var key = `${args.params.type}:${args.params.group}`; - for (var ref of this.cachedFavorites.values()) { - if (ref.$isDeleted || - ref.$groupKey !== key) { - continue; - } - this.cachedFavoritesByObjectId.delete(ref.favoriteId); - ref.$isDeleted = true; - API.$emit('FAVORITE:@DELETE', { - ref, - params: { - favoriteId: ref.id - } - }); - } - }); - - API.$on('FAVORITE:FRIEND:LIST', function (args) { - for (var json of args.json) { - this.$emit('USER', { - json, - params: { - userId: json.id - } - }); - } - }); - - API.$on('FAVORITE:WORLD:LIST', function (args) { - for (var json of args.json) { - if (json.id === '???') { - // FIXME - // json.favoriteId로 따로 불러와야 하나? - // 근데 ???가 많으면 과다 요청이 될듯 - continue; - } - this.$emit('WORLD', { - json, - params: { - worldId: json.id - } - }); - } - }); - - API.$on('FAVORITE:AVATAR:LIST', function (args) { - for (var json of args.json) { - if (json.releaseStatus === 'hidden') { - // NOTE: 얘는 또 더미 데이터로 옴 - continue; - } - this.$emit('AVATAR', { - json, - params: { - avatarId: json.id - } - }); - } - }); - - API.applyFavorite = function (json) { - var ref = this.cachedFavorites.get(json.id); - if (ref === undefined) { - ref = { - id: '', - type: '', - favoriteId: '', - tags: [], - // VRCX - $isDeleted: false, - $isExpired: false, - $groupKey: '', - $groupRef: null, - // - ...json - }; - this.cachedFavorites.set(ref.id, ref); - this.cachedFavoritesByObjectId.set(ref.favoriteId, ref); - } else { - Object.assign(ref, json); - ref.$isExpired = false; - } - ref.$groupKey = `${ref.type}:${String(ref.tags[0])}`; - if (ref.$isDeleted === false && - ref.$groupRef === null) { - var group = this.cachedFavoriteGroupsByTypeName.get(ref.$groupKey); - if (group !== undefined) { - ref.$groupRef = group; - ++group.count; - } - } - return ref; - }; - - API.expireFavorites = function () { - for (var ref of this.cachedFavorites.values()) { - ref.$isExpired = true; - } - }; - - API.deleteExpiredFavorites = function () { - for (var ref of this.cachedFavorites.values()) { - if (ref.$isDeleted || - ref.$isExpired === false) { - continue; - } - ref.$isDeleted = true; - this.$emit('FAVORITE:@DELETE', { - ref, - params: { - favoriteId: ref.id - } - }); - } - }; - - API.refreshFavoriteItems = function () { - var types = { - 'friend': [0, 'getFavoriteFriends'], - 'world': [0, 'getFavoriteWorlds'], - 'avatar': [0, 'getFavoriteAvatars'] - }; - for (var ref of this.cachedFavorites.values()) { - if (ref.$isDeleted) { - continue; - } - var type = types[ref.type]; - if (type === undefined) { - continue; - } - ++type[0]; - } - for (var type in types) { - var [N, fn] = types[type]; - if (N > 0) { - this.bulk({ - fn, - N, - params: { - n: 100, - offset: 0 - } - }); - } - } - }; - - API.refreshFavorites = function () { - if (this.isFavoriteLoading) { - return; - } - this.isFavoriteLoading = true; - this.expireFavorites(); - this.bulk({ - fn: 'getFavorites', - N: -1, - params: { - n: 100, - offset: 0 - }, - done(ok) { - if (ok) { - this.deleteExpiredFavorites(); - } - this.refreshFavoriteItems(); - this.refreshFavoriteGroups(); - this.isFavoriteLoading = false; - } - }); - }; - - API.applyFavoriteGroup = function (json) { - var ref = this.cachedFavoriteGroups.get(json.id); - if (ref === undefined) { - ref = { - id: '', - ownerId: '', - ownerDisplayName: '', - name: '', - displayName: '', - type: '', - visibility: '', - tags: [], - // VRCX - $isDeleted: false, - $isExpired: false, - $groupRef: null, - // - ...json - }; - this.cachedFavoriteGroups.set(ref.id, ref); - } else { - Object.assign(ref, json); - ref.$isExpired = false; - } - return ref; - }; - - API.buildFavoriteGroups = function () { - // 96 = ['group_0', 'group_1', 'group_2'] x 32 - this.favoriteFriendGroups = []; - for (var i = 0; i < 3; ++i) { - this.favoriteFriendGroups.push({ - assign: false, - key: `friend:group_${i}`, - type: 'friend', - name: `group_${i}`, - displayName: `Group ${i + 1}`, - capacity: 32, - count: 0 - }); - } - // 128 = ['worlds1', 'worlds2', 'worlds3', 'worlds4'] x 32 - this.favoriteWorldGroups = []; - for (var i = 0; i < 4; ++i) { - this.favoriteWorldGroups.push({ - assign: false, - key: `world:worlds${i + 1}`, - type: 'world', - name: `worlds${i + 1}`, - displayName: `Group ${i + 1}`, - capacity: 32, - count: 0 - }); - } - // 16 = ['avatars1'] x 16 - this.favoriteAvatarGroups = []; - for (var i = 0; i < 1; ++i) { - this.favoriteAvatarGroups.push({ - assign: false, - key: `avatar:avatars${i + 1}`, - type: 'avatar', - name: `avatars${i + 1}`, - displayName: `Group ${i + 1}`, - capacity: 16, - count: 0 - }); - } - var types = { - 'friend': this.favoriteFriendGroups, - 'world': this.favoriteWorldGroups, - 'avatar': this.favoriteAvatarGroups - }; - var assigns = new Set(); - // assign the same name first - for (var ref of this.cachedFavoriteGroups.values()) { - if (ref.$isDeleted) { - continue; - } - var groups = types[ref.type]; - if (groups === undefined) { - continue; - } - for (var group of groups) { - if (group.assign === false && - group.name === ref.name) { - group.assign = true; - group.displayName = ref.displayName; - ref.$groupRef = group; - assigns.add(ref.id); - break; - } - } - } - // assign the rest - // FIXME - // The order (cachedFavoriteGroups) is very important. It should be - // processed in the order in which the server responded. But since we - // used Map(), the order would be a mess. So we need something to solve - // this. - for (var ref of this.cachedFavoriteGroups.values()) { - if (ref.$isDeleted || - assigns.has(ref.id)) { - continue; - } - var groups = types[ref.type]; - if (groups === undefined) { - continue; - } - for (var group of groups) { - if (group.assign === false) { - group.assign = true; - group.key = `${group.type}:${ref.name}`; - group.name = ref.name; - group.displayName = ref.displayName; - ref.$groupRef = group; - assigns.add(ref.id); - break; - } - } - } - // update favorites - this.cachedFavoriteGroupsByTypeName.clear(); - for (var type in types) { - for (var group of types[type]) { - this.cachedFavoriteGroupsByTypeName.set(group.key, group); - } - } - for (var ref of this.cachedFavorites.values()) { - ref.$groupRef = null; - if (ref.$isDeleted) { - continue; - } - var group = this.cachedFavoriteGroupsByTypeName.get(ref.$groupKey); - if (group === undefined) { - continue; - } - ref.$groupRef = group; - ++group.count; - } - }; - - API.expireFavoriteGroups = function () { - for (var ref of this.cachedFavoriteGroups.values()) { - ref.$isExpired = true; - } - }; - - API.deleteExpiredFavoriteGroups = function () { - for (var ref of this.cachedFavoriteGroups.values()) { - if (ref.$isDeleted || - ref.$isExpired === false) { - continue; - } - ref.$isDeleted = true; - this.$emit('FAVORITE:GROUP:@DELETE', { - ref, - params: { - favoriteGroupId: ref.id - } - }); - } - }; - - API.refreshFavoriteGroups = function () { - if (this.isFavoriteGroupLoading) { - return; - } - this.isFavoriteGroupLoading = true; - this.expireFavoriteGroups(); - this.bulk({ - fn: 'getFavoriteGroups', - N: -1, - params: { - n: 100, - offset: 0 - }, - done(ok) { - if (ok) { - this.deleteExpiredFavoriteGroups(); - this.buildFavoriteGroups(); - } - this.isFavoriteGroupLoading = false; - } - }); - }; - - /* - params: { - n: number, - offset: number, - type: string, - tag: string - } - */ - API.getFavorites = function (params) { - return this.call('favorites', { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FAVORITE:LIST', args); - return args; - }); - }; - - /* - params: { - type: string, - favoriteId: string (objectId), - tags: string - } - */ - API.addFavorite = function (params) { - return this.call('favorites', { - method: 'POST', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FAVORITE:ADD', args); - return args; - }); - }; - - /* - params: { - objectId: string - } - */ - API.deleteFavorite = function (params) { - return this.call(`favorites/${params.objectId}`, { - method: 'DELETE' - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FAVORITE:DELETE', args); - return args; - }); - }; - - /* - params: { - n: number, - offset: number, - type: string - } - */ - API.getFavoriteGroups = function (params) { - return this.call('favorite/groups', { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FAVORITE:GROUP:LIST', args); - return args; - }); - }; - - /* - params: { - type: string, - group: string (name), - displayName: string, - visibility: string - } - */ - API.saveFavoriteGroup = function (params) { - return this.call(`favorite/group/${params.type}/${params.group}/${this.currentUser.id}`, { - method: 'PUT', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FAVORITE:GROUP:SAVE', args); - return args; - }); - }; - - /* - params: { - type: string, - group: string (name) - } - */ - API.clearFavoriteGroup = function (params) { - return this.call(`favorite/group/${params.type}/${params.group}/${this.currentUser.id}`, { - method: 'DELETE', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FAVORITE:GROUP:CLEAR', args); - return args; - }); - }; - - /* - params: { - n: number, - offset: number - } - */ - API.getFavoriteFriends = function (params) { - return this.call('auth/user/friends/favorite', { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FAVORITE:FRIEND:LIST', args); - return args; - }); - }; - - /* - params: { - n: number, - offset: number - } - */ - API.getFavoriteWorlds = function (params) { - return this.call('worlds/favorites', { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FAVORITE:WORLD:LIST', args); - return args; - }); - }; - - /* - params: { - n: number, - offset: number - } - */ - API.getFavoriteAvatars = function (params) { - return this.call('avatars/favorites', { - method: 'GET', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('FAVORITE:AVATAR:LIST', args); - return args; - }); - }; - - // API: WebSocket - - API.webSocket = null; - - API.$on('LOGOUT', function () { - this.closeWebSocket(); - }); - - API.$on('USER:CURRENT', function () { - if (this.webSocket === null) { - this.getAuth(); - } - }); - - API.$on('AUTH', function (args) { - if (args.json.ok) { - this.connectWebSocket(args.json.token); - } - }); - - API.$on('PIPELINE', function (args) { - var { type, content } = args.json; - switch (type) { - case 'notification': - this.$emit('NOTIFICATION', { - json: content, - params: { - notificationId: content.id - } - }); - break; - - case 'friend-add': - this.$emit('USER', { - json: content.user, - params: { - userId: content.userId - } - }); - this.$emit('FRIEND:ADD', { - params: { - userId: content.userId - } - }); - break; - - case 'friend-delete': - this.$emit('FRIEND:DELETE', { - params: { - userId: content.userId - } - }); - break; - - case 'friend-online': - if (content.location !== 'private') { - this.$emit('WORLD', { - json: content.world, - params: { - worldId: content.world.id - } - }); - } - this.$emit('USER', { - json: { - location: content.location, - ...content.user - }, - params: { - userId: content.userId - } - }); - this.$emit('FRIEND:STATE', { - json: { - state: 'online' - }, - params: { - userId: content.userId - } - }); - break; - - case 'friend-active': - this.$emit('USER', { - json: content.user, - params: { - userId: content.userId - } - }); - this.$emit('FRIEND:STATE', { - json: { - state: 'active' - }, - params: { - userId: content.userId - } - }); - break; - - case 'friend-offline': - this.$emit('FRIEND:STATE', { - json: { - state: 'offline' - }, - params: { - userId: content.userId - } - }); - break; - - case 'friend-update': - this.$emit('USER', { - json: content.user, - params: { - userId: content.userId - } - }); - break; - - case 'friend-location': - // it seems to only come when a friend is in a private world :/ - if (content.location !== 'private') { - this.$emit('WORLD', { - json: content.world, - params: { - worldId: content.world.id - } - }); - } - if (content.userId === this.currentUser.id) { - this.$emit('USER', { - json: content.user, - params: { - userId: content.userId - } - }); - } else { - this.$emit('USER', { - json: { - location: content.location, - ...content.user - }, - params: { - userId: content.userId - } - }); - } - break; - - case 'user-update': - this.$emit('USER:CURRENT', { - json: content.user, - params: { - userId: content.userId - } - }); - break; - - case 'user-location': - if (content.world === Object(content.world)) { - this.$emit('WORLD', { - json: content.world, - params: { - worldId: content.world.id - } - }); - } - this.$emit('USER', { - json: { - id: content.userId, - location: content.location - }, - params: { - userId: content.userId - } - }); - break; - - default: - 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 === null) { - var socket = new WebSocket(`wss://pipeline.vrchat.cloud/?auth=${token}`); - socket.onclose = () => { - if (this.webSocket === socket) { - this.webSocket = null; - } - try { - socket.close(); - } catch (err) { - } - }; - socket.onerror = socket.onclose; - socket.onmessage = ({ data }) => { - try { - var json = JSON.parse(data); - json.content = JSON.parse(json.content); - this.$emit('PIPELINE', { - json - }); - } catch (err) { - console.error(err); - } - }; - this.webSocket = socket; - } - }; - - API.closeWebSocket = function () { - var socket = this.webSocket; - if (socket === null) { - return; - } - this.webSocket = null; - try { - socket.close(); - } catch (err) { - } - }; - - // API: Visit - - API.getVisits = function () { - return this.call('visits', { - method: 'GET' - }).then((json) => { - var args = { - json - }; - this.$emit('VISITS', 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 (Array.isArray(value)) { - node.push({ - children: value.map((val, idx) => { - if (val === Object(val)) { - return { - children: buildTreeData(val), - key: idx - }; - } - return { - key: idx, - value: val - }; - }), - key - }); - } else if (value === Object(value)) { - node.push({ - children: buildTreeData(value), - key - }); - } else { - node.push({ - key, - value: String(value) - }); - } - } - node.sort(function (a, b) { - 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: '', - 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(function () { - for (var $timer of $timers) { - $timer.update(); - } - }, 5000); - - // initialise - - var $app = { - data: { - API, - VRCX, - nextRefresh: 0, - isGameRunning: false, - appVersion: 'VRCX 2020.03.21', - 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(function () { - 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.openExternalLink = function (link) { - this.$confirm(`${link}`, 'Open External Link', { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info', - callback: (action) => { - if (action === 'confirm') { - VRCX.OpenLink(link); - } - } - }); - }; - - $app.methods.languageClass = function (language) { - var style = {}; - var mapping = languageMappings[language]; - if (mapping !== undefined) { - style[mapping] = true; - } - return style; - }; - - $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 === Object(json) && - json.name && - json.published_at) { - this.latestAppVersion = `${json.name} (${formatDate(json.published_at, 'YYYY-MM-DD HH24:MI:SS')})`; - if (json.name > this.appVersion) { - new Noty({ - type: 'info', - text: `Update available!!
${this.latestAppVersion}`, - timeout: 60000, - callbacks: { - onClick: () => VRCX.OpenLink('https://github.com/pypy-vrc/VRCX/releases') - } - }).show(); - this.notifyMenu('more'); - } - } else { - this.latestAppVersion = 'Error occured'; - } - }); - }; - - $app.methods.update = function () { - if (API.isLoggedIn === false) { - return; - } - 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.checkActiveFriends(); - this.refreshGameLog(); - VRCX.IsGameRunning().then((running) => { - if (running !== this.isGameRunning) { - this.isGameRunning = running; - Discord.SetTimestamps(Date.now(), 0); - } - this.updateDiscord(); - this.updateOpenVR(); - }); - }; - - $app.methods.updateSharedFeed = function () { - var arr = []; - // FIXME - // 여러 개 켠다면 gameLogTable의 데이터가 시간순이 아닐 수도 있음 - var { data } = this.gameLogTable; - var i = data.length; - var j = 0; - while (j < 25) { - if (i <= 0) { - break; - } - var ctx = data[--i]; - // Location, OnPlayerJoined, OnPlayerLeft - if (ctx.type) { - // FIXME: 이거 존나 느릴거 같은데 - var isFriend = false; - var isFavorite = false; - for (var ref of API.cachedUsers.values()) { - if (ref.displayName === ctx.data) { - isFriend = this.friends.has(ref.id); - isFavorite = API.cachedFavoritesByObjectId.has(ref.id); - break; - } - } - arr.push({ - ...ctx, - isFriend, - isFavorite - }); - } else { - arr.push(ctx); - } - ++j; - } - var { data } = this.feedTable; - var i = data.length; - var j = 0; - while (j < 25) { - if (i <= 0) { - break; - } - var ctx = data[--i]; - // GPS, Online, Offline, Status, Avatar - if (ctx.type !== 'Avatar') { - arr.push({ - ...ctx, - isFriend: this.friends.has(ctx.userId), - isFavorite: API.cachedFavoritesByObjectId.has(ctx.userId) - }); - ++j; - } - } - arr.sort(function (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) { - var { menu } = this.$refs; - if (menu.activeIndex !== index) { - var item = 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.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) => { - 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) => { - this.promptOTP(); - throw err; - }).then((args) => { - API.getCurrentUser(); - return args; - }); - } else if (action === 'cancel') { - this.promptTOTP(); - } - } - }); - }; - - API.$on('USER:2FA', function () { - $app.promptTOTP(); - }); - - API.$on('LOGOUT', function () { - new Noty({ - type: 'success', - text: `See you again, ${escapeTag(this.currentUser.displayName)}!` - }).show(); - }); - - API.$on('LOGIN', function (args) { - new Noty({ - type: 'success', - text: `Hello there, ${escapeTag(args.ref.displayName)}!` - }).show(); - $app.$refs.menu.activeIndex = 'feed'; - }); - - $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) { - var key = `memo_${id}`; - return VRCXStorage.Get(key); - }; - - $app.methods.saveMemo = function (id, memo) { - var key = `memo_${id}`; - if (memo) { - VRCXStorage.Set(key, String(memo)); - } else { - VRCXStorage.Remove(key); - } - var ref = this.friends.get(id); - if (ref) { - ref.memo = String(memo || ''); - } - }; - - // App: Friends - - $app.data.friends = new Map(); - $app.data.pendingActiveFriends = new Set(); - $app.data.friendsNo = 0; - $app.data.isFriendsGroup0 = true; - $app.data.isFriendsGroup1 = true; - $app.data.isFriendsGroup2 = true; - $app.data.isFriendsGroup3 = false; - $app.data.friendsGroup0_ = []; - $app.data.friendsGroup1_ = []; - $app.data.friendsGroup2_ = []; - $app.data.friendsGroup3_ = []; - $app.data.friendsGroupA_ = []; - $app.data.friendsGroupB_ = []; - $app.data.friendsGroupC_ = []; - $app.data.friendsGroupD_ = []; - $app.data.sortFriendsGroup0 = false; - $app.data.sortFriendsGroup1 = false; - $app.data.sortFriendsGroup2 = false; - $app.data.sortFriendsGroup3 = false; - $app.data.orderFriendsGroup0 = VRCXStorage.GetBool('orderFriendGroup0'); - $app.data.orderFriendsGroup1 = VRCXStorage.GetBool('orderFriendGroup1'); - $app.data.orderFriendsGroup2 = VRCXStorage.GetBool('orderFriendGroup2'); - $app.data.orderFriendsGroup3 = VRCXStorage.GetBool('orderFriendGroup3'); - var saveOrderFriendGroup = function () { - VRCXStorage.SetBool('orderFriendGroup0', this.orderFriendsGroup0); - VRCXStorage.SetBool('orderFriendGroup1', this.orderFriendsGroup1); - VRCXStorage.SetBool('orderFriendGroup2', this.orderFriendsGroup2); - VRCXStorage.SetBool('orderFriendGroup3', this.orderFriendsGroup3); - }; - $app.watch.orderFriendsGroup0 = saveOrderFriendGroup; - $app.watch.orderFriendsGroup1 = saveOrderFriendGroup; - $app.watch.orderFriendsGroup2 = saveOrderFriendGroup; - $app.watch.orderFriendsGroup3 = saveOrderFriendGroup; - - $app.methods.fetchActiveFriend = function (userId) { - this.pendingActiveFriends.add(userId); - // FIXME: handle error - return API.getUser({ - userId - }).then((args) => { - this.pendingActiveFriends.delete(userId); - return args; - }); - }; - - $app.methods.checkActiveFriends = function () { - if (Array.isArray(API.currentUser.activeFriends) === false) { - return; - } - for (var userId of API.currentUser.activeFriends) { - if (this.pendingActiveFriends.has(userId)) { - continue; - } - var user = API.cachedUsers.get(userId); - if (user !== undefined && - user.status !== 'offline') { - continue; - } - if (this.pendingActiveFriends.size >= 5) { - break; - } - this.fetchActiveFriend(userId); - } - }; - - API.$on('LOGIN', function () { - $app.friends.clear(); - $app.pendingActiveFriends.clear(); - $app.friendsNo = 0; - $app.isFriendsGroup0 = true; - $app.isFriendsGroup1 = true; - $app.isFriendsGroup2 = true; - $app.isFriendsGroup3 = false; - $app.friendsGroup0_ = []; - $app.friendsGroup1_ = []; - $app.friendsGroup2_ = []; - $app.friendsGroup3_ = []; - $app.friendsGroupA_ = []; - $app.friendsGroupB_ = []; - $app.friendsGroupC_ = []; - $app.friendsGroupD_ = []; - $app.sortFriendsGroup0 = false; - $app.sortFriendsGroup1 = false; - $app.sortFriendsGroup2 = false; - $app.sortFriendsGroup3 = false; - }); - - API.$on('USER:CURRENT', function (args) { - // initFriendship()이 LOGIN에서 처리되기 때문에 - // USER:CURRENT에서 처리를 함 - $app.refreshFriends(args.ref, args.origin); - }); - - API.$on('USER', function (args) { - $app.updateFriend(args.ref.id); - }); - - API.$on('FRIEND:ADD', function (args) { - $app.addFriend(args.params.userId); - }); - - API.$on('FRIEND:DELETE', function (args) { - $app.deleteFriend(args.params.userId); - }); - - API.$on('FRIEND:STATE', function (args) { - $app.updateFriend(args.params.userId, args.json.state); - }); - - API.$on('FAVORITE', function (args) { - $app.updateFriend(args.ref.favoriteId); - }); - - API.$on('FAVORITE:@DELETE', function (args) { - $app.updateFriend(args.ref.favoriteId); - }); - - $app.methods.refreshFriends = function (ref, origin) { - var map = new Map(); - for (var id of ref.friends) { - map.set(id, 'offline'); - } - for (var id of ref.offlineFriends) { - map.set(id, 'offline'); - } - for (var id of ref.activeFriends) { - map.set(id, 'active'); - } - for (var id of ref.onlineFriends) { - map.set(id, 'online'); - } - for (var [id, state] of map) { - if (this.friends.has(id)) { - this.updateFriend(id, state, origin); - } else { - this.addFriend(id, state); - } - } - for (var id of this.friends.keys()) { - if (map.has(id) === false) { - this.deleteFriend(id); - } - } - // called from API.login(), API.loginWithSteam(), API.getCurrentUser() - if (origin) { - API.refreshFriends(); - } - }; - - $app.methods.addFriend = function (id, state) { - if (this.friends.has(id)) { - return; - } - var ref = API.cachedUsers.get(id); - var isVIP = API.cachedFavoritesByObjectId.has(id); - var ctx = { - id, - state: state || 'offline', - isVIP, - ref, - name: '', - no: ++this.friendsNo, - memo: this.loadMemo(id) - }; - if (ref === undefined) { - ref = this.friendLog[id]; - if (ref !== undefined && - ref.displayName) { - ctx.name = ref.displayName; - } - } else { - ctx.name = ref.name; - } - this.friends.set(id, ctx); - if (ctx.state === 'online') { - if (ctx.isVIP) { - this.sortFriendsGroup0 = true; - this.friendsGroup0_.push(ctx); - this.friendsGroupA_.unshift(ctx); - } else { - this.sortFriendsGroup1 = true; - this.friendsGroup1_.push(ctx); - this.friendsGroupB_.unshift(ctx); - } - } else if (ctx.state === 'active') { - this.sortFriendsGroup2 = true; - this.friendsGroup2_.push(ctx); - this.friendsGroupC_.unshift(ctx); - } else { - this.sortFriendsGroup3 = true; - this.friendsGroup3_.push(ctx); - this.friendsGroupD_.unshift(ctx); - } - }; - - $app.methods.deleteFriend = function (id) { - var ctx = this.friends.get(id); - if (ctx === undefined) { - return; - } - this.friends.delete(id); - if (ctx.state === 'online') { - if (ctx.isVIP) { - removeFromArray(this.friendsGroup0_, ctx); - removeFromArray(this.friendsGroupA_, ctx); - } else { - removeFromArray(this.friendsGroup1_, ctx); - removeFromArray(this.friendsGroupB_, ctx); - } - } else if (ctx.state === 'active') { - removeFromArray(this.friendsGroup2_, ctx); - removeFromArray(this.friendsGroupC_, ctx); - } else { - removeFromArray(this.friendsGroup3_, ctx); - removeFromArray(this.friendsGroupD_, ctx); - } - }; - - $app.methods.updateFriend = function (id, state, origin) { - var ctx = this.friends.get(id); - if (ctx === undefined) { - return; - } - var ref = API.cachedUsers.get(id); - var isVIP = API.cachedFavoritesByObjectId.has(id); - if (state === undefined || - ctx.state === state) { - // this is should be: undefined -> user - if (ctx.ref !== ref) { - ctx.ref = ref; - // NOTE - // AddFriend (CurrentUser) 이후, - // 서버에서 오는 순서라고 보면 될 듯. - if (ctx.state === 'online') { - if (ctx.isVIP) { - removeFromArray(this.friendsGroupA_, ctx); - this.friendsGroupA_.push(ctx); - } else { - removeFromArray(this.friendsGroupB_, ctx); - this.friendsGroupB_.push(ctx); - } - } else if (ctx.state === 'active') { - removeFromArray(this.friendsGroupC_, ctx); - this.friendsGroupC_.push(ctx); - } else { - removeFromArray(this.friendsGroupD_, ctx); - this.friendsGroupD_.push(ctx); - } - } - if (ctx.isVIP !== isVIP) { - ctx.isVIP = isVIP; - if (ctx.state === 'online') { - if (ctx.isVIP) { - removeFromArray(this.friendsGroup1_, ctx); - removeFromArray(this.friendsGroupB_, ctx); - this.sortFriendsGroup0 = true; - this.friendsGroup0_.push(ctx); - this.friendsGroupA_.unshift(ctx); - } else { - removeFromArray(this.friendsGroup0_, ctx); - removeFromArray(this.friendsGroupA_, ctx); - this.sortFriendsGroup1 = true; - this.friendsGroup1_.push(ctx); - this.friendsGroupB_.unshift(ctx); - } - } - } - if (ref !== undefined && - ctx.name !== ref.displayName) { - ctx.name = ref.displayName; - if (ctx.state === 'online') { - if (ctx.isVIP) { - this.sortFriendsGroup0 = true; - } else { - this.sortFriendsGroup1 = true; - } - } else if (ctx.state === 'active') { - this.sortFriendsGroup2 = true; - } else { - this.sortFriendsGroup3 = true; - } - } - // FIXME: 도배 가능성 있음 - if (origin && - ctx.state !== 'online' && - ref !== undefined && - ref.location !== '' && - ref.location !== 'offline') { - API.getUser({ - userId: id - }); - } - } else { - if (ctx.state === 'online') { - if (ctx.isVIP) { - removeFromArray(this.friendsGroup0_, ctx); - removeFromArray(this.friendsGroupA_, ctx); - } else { - removeFromArray(this.friendsGroup1_, ctx); - removeFromArray(this.friendsGroupB_, ctx); - } - } else if (ctx.state === 'active') { - removeFromArray(this.friendsGroup2_, ctx); - removeFromArray(this.friendsGroupC_, ctx); - } else { - removeFromArray(this.friendsGroup3_, ctx); - removeFromArray(this.friendsGroupD_, ctx); - } - // changing property triggers Vue - // so, we need compare and set - if (ctx.state !== state) { - ctx.state = state; - } - if (ctx.isVIP !== isVIP) { - ctx.isVIP = isVIP; - } - if (ref !== undefined) { - if (ctx.ref !== ref) { - ctx.ref = ref; - } - if (ctx.name !== ref.displayName) { - ctx.name = ref.displayName; - } - } - if (ctx.state === 'online') { - if (ctx.isVIP) { - this.sortFriendsGroup0 = true; - this.friendsGroup0_.push(ctx); - this.friendsGroupA_.unshift(ctx); - } else { - this.sortFriendsGroup1 = true; - this.friendsGroup1_.push(ctx); - this.friendsGroupB_.unshift(ctx); - } - } else if (ctx.state === 'active') { - this.sortFriendsGroup2 = true; - this.friendsGroup2_.push(ctx); - this.friendsGroupC_.unshift(ctx); - } else { - this.sortFriendsGroup3 = true; - this.friendsGroup3_.push(ctx); - this.friendsGroupD_.unshift(ctx); - } - } - }; - - // ascending - var compareByName = function (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; - }; - - // descending - var compareByUpdatedAt = function (a, b) { - var A = String(a.updated_at).toUpperCase(); - var B = String(b.updated_at).toUpperCase(); - if (A < B) { - return 1; - } - if (A > B) { - return -1; - } - return 0; - }; - - // ascending - var compareByDisplayName = function (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; - }; - - // VIP friends - $app.computed.friendsGroup0 = function () { - if (this.orderFriendsGroup0) { - return this.friendsGroupA_; - } - if (this.sortFriendsGroup0) { - this.sortFriendsGroup0 = false; - this.friendsGroup0_.sort(compareByName); - } - return this.friendsGroup0_; - }; - - // Online friends - $app.computed.friendsGroup1 = function () { - if (this.orderFriendsGroup1) { - return this.friendsGroupB_; - } - if (this.sortFriendsGroup1) { - this.sortFriendsGroup1 = false; - this.friendsGroup1_.sort(compareByName); - } - return this.friendsGroup1_; - }; - - // Active friends - $app.computed.friendsGroup2 = function () { - if (this.orderFriendsGroup2) { - return this.friendsGroupC_; - } - if (this.sortFriendsGroup2) { - this.sortFriendsGroup2 = false; - this.friendsGroup2_.sort(compareByName); - } - return this.friendsGroup2_; - }; - - // Offline friends - $app.computed.friendsGroup3 = function () { - if (this.orderFriendsGroup3) { - return this.friendsGroupD_; - } - if (this.sortFriendsGroup3) { - this.sortFriendsGroup3 = false; - this.friendsGroup3_.sort(compareByName); - } - return this.friendsGroup3_; - }; - - $app.methods.userStatusClass = function (user) { - var style = {}; - if (user !== undefined) { - if (user.location === 'offline') { - // Offline - style.offline = true; - } else if (user.status === 'active') { - // Online - style.active = true; - } else if (user.status === 'join me') { - // Join Me - style.joinme = true; - } else if (user.status === 'ask me') { - // Ask Me - style.askme = true; - } else if (user.status === 'busy') { - // Do Not Disturb - style.busy = true; - } - } - return style; - }; - - $app.methods.confirmDeleteFriend = function (id) { - this.$confirm('Continue? Unfriend', '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) { - var results = []; - if (query) { - var QUERY = query.toUpperCase(); - for (var ctx of this.friends.values()) { - if (ctx.ref === undefined) { - continue; - } - 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) { - results.push({ - value: ctx.id, - label: ctx.name, - ref: ctx.ref, - NAME - }); - } - } - results.sort(function (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 (results.length > 4) { - results.length = 4; - } - results.push({ - value: `search:${query}`, - label: query - }); - } - this.quickSearchItems = results; - }; - - $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 || - API.cachedFavoritesByObjectId.has(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', function (args) { - $app.feedTable.data = VRCXStorage.GetArray(`${args.ref.id}_feedTable`); - $app.sweepFeed(); - }); - - API.$on('USER:UPDATE', function (args) { - var { ref, props } = args; - if ($app.friends.has(ref.id) === false) { - return; - } - if (props.location) { - if (props.location[0] === 'offline') { - $app.addFeed('Offline', ref, { - location: props.location[1], - time: props.location[2] - }); - } else if (props.location[1] === 'offline') { - $app.addFeed('Online', ref, { - location: props.location[0] - }); - } else { - $app.addFeed('GPS', ref, { - location: [ - props.location[0], - props.location[1] - ], - time: props.location[2] - }); - } - } - if (props.currentAvatarThumbnailImageUrl) { - $app.addFeed('Avatar', ref, { - avatar: props.currentAvatarThumbnailImageUrl - }); - } - if (props.status || - props.statusDescription) { - $app.addFeed('Status', ref, { - status: [ - { - status: props.status - ? props.status[0] - : ref.status, - statusDescription: props.statusDescription - ? props.statusDescription[0] - : ref.statusDescription - }, - { - status: props.status - ? props.status[1] - : ref.status, - statusDescription: props.statusDescription - ? props.statusDescription[1] - : ref.statusDescription - } - ] - }); - } - }); - - var saveFeedTimer = null; - $app.methods.saveFeed = function () { - if (saveFeedTimer !== null) { - return; - } - saveFeedTimer = setTimeout(() => { - saveFeedTimer = null; - 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 { data } = this.feedTable; - // 로그는 3일까지만 남김 - var limit = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toJSON(); - var i = 0; - var j = data.length; - while (i < j && - data[i].created_at < limit) { - ++i; - } - if (i === j) { - this.feedTable.data = []; - } else if (i) { - data.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.Get().then((logs) => { - if (logs.length) { - var { data } = this.gameLogTable; - for (var log of logs) { - var ctx = { - created_at: String(log[0]), - type: String(log[1]), - data: String(log[2]) - }; - if (ctx.type === 'Location') { - var tag = ctx.data; - if (tag.endsWith(':')) { - tag = tag.substr(0, tag.length - 1); - ctx.data = tag; - } - this.lastLocation = tag; - } - data.push(ctx); - } - this.sweepGameLog(); - // sweepGameLog로 기록이 삭제되면 - // 아무 것도 없는데 알림이 떠서 이상함 - if (data.length) { - this.notifyMenu('gameLog'); - } - } - this.updateSharedFeed(); - }); - }; - - $app.methods.sweepGameLog = function () { - var { data } = this.gameLogTable; - // 로그는 3일까지만 남김 - var limit = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toJSON(); - var i = 0; - var j = data.length; - while (i < j && - data[i].created_at < limit) { - ++i; - } - if (i === j) { - this.gameLogTable.data = []; - } else if (i) { - data.splice(0, i); - } - }; - - $app.methods.updateDiscord = function () { - if (this.isGameRunning === false || - this.lastLocation === '') { - Discord.SetActive(false); - return; - } - 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.cachedWorlds.get(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); - }; - - $app.methods.lookupUser = function (name) { - for (var ref of API.cachedUsers.values()) { - if (ref.displayName === name) { - this.showUserDialog(ref.id); - return; - } - } - this.searchText = name; - this.search(); - this.$refs.menu.activeIndex = 'search'; - this.$refs.searchTab.currentName = '0'; - }; - - // App: Search - - $app.data.searchText = ''; - $app.data.searchUserResults = []; - $app.data.searchUserParams = {}; - $app.data.searchWorldResults = []; - $app.data.searchWorldOption = ''; - $app.data.searchWorldParams = {}; - $app.data.searchAvatarResults = []; - $app.data.searchAvatarParams = {}; - $app.data.isSearchUserLoading = false; - $app.data.isSearchWorldLoading = false; - $app.data.isSearchAvatarLoading = false; - - API.$on('LOGIN', function () { - $app.searchText = ''; - $app.searchUserResults = []; - $app.searchUserParams = {}; - $app.searchWorldResults = []; - $app.searchWorldOption = ''; - $app.searchWorldParams = {}; - $app.searchAvatarResults = []; - $app.searchAvatarParams = {}; - $app.isSearchUserLoading = false; - $app.isSearchWorldLoading = false; - $app.isSearchAvatarLoading = false; - }); - - $app.methods.clearSearch = function () { - this.searchUserResults = []; - this.searchWorldResults = []; - this.searchAvatarResults = []; - }; - - $app.methods.search = function () { - this.searchUser(); - this.searchWorld({}); - }; - - $app.methods.searchUser = function () { - this.searchUserParams = { - n: 10, - offset: 0, - search: this.searchText - }; - this.moreSearchUser(); - }; - - $app.methods.moreSearchUser = function (go) { - var params = this.searchUserParams; - if (go) { - params.offset += params.n * go; - if (params.offset < 0) { - params.offset = 0; - } - } - this.isSearchUserLoading = true; - API.getUsers(params).finally(() => { - this.isSearchUserLoading = false; - }).then((args) => { - var map = new Map(); - for (var json of args.json) { - var ref = API.cachedUsers.get(json.id); - if (ref !== undefined) { - map.set(ref.id, ref); - } - } - this.searchUserResults = Array.from(map.values()); - return args; - }); - }; - - $app.methods.searchWorld = function (ref) { - this.searchWorldOption = ''; - var params = { - n: 10, - offset: 0 - }; - switch (ref.sortHeading) { - case 'featured': - params.sort = 'order'; - params.featured = 'true'; - break; - case 'trending': - params.sort = 'popularity'; - params.featured = 'false'; - break; - case 'updated': - params.sort = 'updated'; - break; - case 'created': - params.sort = 'created'; - break; - case 'publication': - params.sort = 'publicationDate'; - break; - case 'shuffle': - params.sort = 'shuffle'; - break; - case 'active': - this.searchWorldOption = 'active'; - break; - case 'recent': - this.searchWorldOption = 'recent'; - break; - case 'favorite': - this.searchWorldOption = 'favorites'; - break; - case 'labs': - params.sort = 'labsPublicationDate'; - break; - case 'heat': - params.sort = 'heat'; - params.featured = 'false'; - break; - default: - params.sort = 'popularity'; - params.search = this.searchText; - break; - } - params.order = ref.sortOrder || 'descending'; - if (ref.sortOwnership === 'mine') { - params.user = 'me'; - params.releaseStatus = 'all'; - } - if (ref.tag) { - params.tag = ref.tag; - } - // TODO: option.platform - this.searchWorldParams = params; - this.moreSearchWorld(); - }; - - $app.methods.moreSearchWorld = function (go) { - var params = this.searchWorldParams; - if (go) { - params.offset += params.n * go; - if (params.offset < 0) { - params.offset = 0; - } - } - this.isSearchWorldLoading = true; - API.getWorlds(params, this.searchWorldOption).finally(() => { - this.isSearchWorldLoading = false; - }).then((args) => { - var map = new Map(); - for (var json of args.json) { - var ref = API.cachedWorlds.get(json.id); - if (ref !== undefined) { - map.set(ref.id, ref); - } - } - this.searchWorldResults = Array.from(map.values()); - return args; - }); - }; - - $app.methods.searchAvatar = function (option) { - var params = { - n: 10, - offset: 0 - }; - switch (option) { - case 'updated': - params.sort = 'updated'; - break; - case 'created': - params.sort = 'created'; - break; - case 'mine': - params.user = 'me'; - params.releaseStatus = 'all'; - break; - default: - params.sort = 'popularity'; - params.search = this.searchText; - break; - } - params.order = 'descending'; - // TODO: option.platform - this.searchAvatarParams = params; - this.moreSearchAvatar(); - }; - - $app.methods.moreSearchAvatar = function (go) { - var params = this.searchAvatarParams; - if (go) { - params.offset += params.n * go; - if (params.offset < 0) { - params.offset = 0; - } - } - this.isSearchAvatarLoading = true; - API.getAvatars(params).finally(() => { - this.isSearchAvatarLoading = false; - }).then((args) => { - var map = new Map(); - for (var json of args.json) { - var ref = API.cachedAvatars.get(json.id); - if (ref !== undefined) { - map.set(ref.id, ref); - } - } - this.searchAvatarResults = Array.from(map.values()); - return args; - }); - }; - - // App: Favorite - - $app.data.favoriteObjects = new Map(); - $app.data.favoriteFriends_ = []; - $app.data.favoriteWorlds_ = []; - $app.data.favoriteAvatars_ = []; - $app.data.sortFavoriteFriends = false; - $app.data.sortFavoriteWorlds = false; - $app.data.sortFavoriteAvatars = false; - - API.$on('LOGIN', function () { - $app.favoriteObjects.clear(); - $app.favoriteFriends_ = []; - $app.favoriteWorlds_ = []; - $app.favoriteAvatars_ = []; - $app.sortFavoriteFriends = false; - $app.sortFavoriteWorlds = false; - $app.sortFavoriteAvatars = false; - }); - - API.$on('FAVORITE', function (args) { - $app.applyFavorite(args.ref.type, args.ref.favoriteId); - }); - - API.$on('FAVORITE:@DELETE', function (args) { - $app.applyFavorite(args.ref.type, args.ref.favoriteId); - }); - - API.$on('USER', function (args) { - $app.applyFavorite('friend', args.ref.id); - }); - - API.$on('WORLD', function (args) { - $app.applyFavorite('world', args.ref.id); - }); - - API.$on('AVATAR', function (args) { - $app.applyFavorite('avatar', args.ref.id); - }); - - $app.methods.applyFavorite = function (type, objectId) { - var favorite = API.cachedFavoritesByObjectId.get(objectId); - var ctx = this.favoriteObjects.get(objectId); - if (favorite !== undefined) { - var isTypeChanged = false; - if (ctx === undefined) { - ctx = { - id: objectId, - type, - groupKey: favorite.$groupKey, - ref: null, - name: '' - }; - this.favoriteObjects.set(objectId, ctx); - if (type === 'friend') { - var ref = API.cachedUsers.get(objectId); - if (ref === undefined) { - ref = this.friendLog[objectId]; - if (ref !== undefined && - ref.displayName) { - ctx.name = ref.displayName; - } - } else { - ctx.ref = ref; - ctx.name = ref.displayName; - } - } else if (type === 'world') { - var ref = API.cachedWorlds.get(objectId); - if (ref !== undefined) { - ctx.ref = ref; - ctx.name = ref.name; - } - } else if (type === 'avatar') { - var ref = API.cachedAvatars.get(objectId); - if (ref !== undefined) { - ctx.ref = ref; - ctx.name = ref.name; - } - } - isTypeChanged = true; - } else { - if (ctx.type !== type) { - // WTF??? - isTypeChanged = true; - if (type === 'friend') { - removeFromArray(this.favoriteFriends_, ctx); - } else if (type === 'world') { - removeFromArray(this.favoriteWorlds_, ctx); - } else if (type === 'avatar') { - removeFromArray(this.favoriteAvatars_, ctx); - } - } - if (type === 'friend') { - var ref = API.cachedUsers.get(objectId); - if (ref !== undefined) { - if (ctx.ref !== ref) { - ctx.ref = ref; - } - if (ctx.name !== ref.displayName) { - ctx.name = ref.displayName; - this.sortFavoriteFriends = true; - } - } - } else if (type === 'world') { - var ref = API.cachedWorlds.get(objectId); - if (ref !== undefined) { - if (ctx.ref !== ref) { - ctx.ref = ref; - } - if (ctx.name !== ref.name) { - ctx.name = ref.name; - this.sortFavoriteWorlds = true; - } - } - } else if (type === 'avatar') { - var ref = API.cachedAvatars.get(objectId); - if (ref !== undefined) { - if (ctx.ref !== ref) { - ctx.ref = ref; - } - if (ctx.name !== ref.name) { - ctx.name = ref.name; - this.sortFavoriteAvatars = true; - } - } - } - } - if (isTypeChanged) { - if (type === 'friend') { - this.favoriteFriends_.push(ctx); - this.sortFavoriteFriends = true; - } else if (type === 'world') { - this.favoriteWorlds_.push(ctx); - this.sortFavoriteWorlds = true; - } else if (type === 'avatar') { - this.favoriteAvatars_.push(ctx); - this.sortFavoriteAvatars = true; - } - } - } else if (ctx !== undefined) { - this.favoriteObjects.delete(objectId); - if (type === 'friend') { - removeFromArray(this.favoriteFriends_, ctx); - } else if (type === 'world') { - removeFromArray(this.favoriteWorlds_, ctx); - } else if (type === 'avatar') { - 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: (action) => { - if (action === 'confirm') { - API.clearFavoriteGroup({ - type: ctx.type, - group: ctx.name - }); - } - } - }); - }; - - $app.computed.favoriteFriends = function () { - if (this.sortFavoriteFriends) { - this.sortFavoriteFriends = false; - this.favoriteFriends_.sort(compareByName); - } - return this.favoriteFriends_; - }; - - $app.computed.favoriteWorlds = function () { - if (this.sortFavoriteWorlds) { - this.sortFavoriteWorlds = false; - this.favoriteWorlds_.sort(compareByName); - } - return this.favoriteWorlds_; - }; - - $app.computed.favoriteAvatars = function () { - if (this.sortFavoriteAvatars) { - this.sortFavoriteAvatars = false; - this.favoriteAvatars_.sort(compareByName); - } - 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', function (args) { - $app.initFriendship(args.ref); - }); - - API.$on('USER:CURRENT', function (args) { - $app.updateFriendships(args.ref); - }); - - API.$on('USER', function (args) { - $app.updateFriendship(args.ref); - }); - - API.$on('FRIEND:ADD', function (args) { - $app.addFriendship(args.params.userId); - }); - - API.$on('FRIEND:DELETE', function (args) { - $app.deleteFriendship(args.params.userId); - }); - - API.$on('FRIEND:REQUEST', function (args) { - var ref = this.cachedUsers.get(args.params.userId); - if (ref === undefined) { - return; - } - $app.friendLogTable.data.push({ - created_at: new Date().toJSON(), - type: 'FriendRequest', - userId: ref.id, - displayName: ref.displayName - }); - $app.saveFriendLog(); - }); - - API.$on('FRIEND:REQUEST:CANCEL', function (args) { - var ref = this.cachedUsers.get(args.params.userId); - if (ref === undefined) { - return; - } - $app.friendLogTable.data.push({ - created_at: new Date().toJSON(), - type: 'CancelFriendRequst', - userId: ref.id, - displayName: ref.displayName - }); - $app.saveFriendLog(); - }); - - var saveFriendLogTimer = null; - $app.methods.saveFriendLog = function () { - if (saveFriendLogTimer !== null) { - return; - } - saveFriendLogTimer = setTimeout(() => { - saveFriendLogTimer = null; - 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 { - var friendLog = {}; - for (var id of ref.friends) { - // DO NOT set displayName, - // it's flag about it's new friend - var ctx = { - id - }; - var user = API.cachedUsers.get(id); - if (user !== undefined) { - ctx.displayName = user.displayName; - ctx.trustLevel = user.$trustLevel; - } - friendLog[id] = ctx; - } - this.friendLog = friendLog; - this.friendLogTable.data = []; - this.saveFriendLog(); - } - }; - - $app.methods.addFriendship = function (id) { - if (this.friendLog[id] !== undefined) { - return; - } - var ctx = { - id, - displayName: null, - trustLevel: null - }; - Vue.set(this.friendLog, id, ctx); - var ref = API.cachedUsers.get(id); - if (ref !== undefined) { - ctx.displayName = ref.displayName; - ctx.trustLevel = ref.$trustLevel; - 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 === undefined) { - return; - } - Vue.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 set = new Set(); - for (var id of ref.friends) { - set.add(id); - this.addFriendship(id); - } - for (var id in this.friendLog) { - if (set.has(id) === false) { - this.deleteFriendship(id); - } - } - }; - - $app.methods.updateFriendship = function (ref) { - var ctx = this.friendLog[ref.id]; - if (ctx === undefined) { - return; - } - if (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'); - } - if (ref.$trustLevel && - ctx.trustLevel !== ref.$trustLevel) { - if (ctx.trustLevel) { - this.friendLogTable.data.push({ - created_at: new Date().toJSON(), - type: 'TrustLevel', - userId: ref.id, - displayName: ref.displayName, - trustLevel: ref.$trustLevel, - previousTrustLevel: ctx.trustLevel - }); - } - ctx.trustLevel = ref.$trustLevel; - 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' && - 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', function () { - $app.playerModerationTable.data = []; - }); - - API.$on('PLAYER-MODERATION', function (args) { - var { ref } = args; - var array = $app.playerModerationTable.data; - var { length } = array; - for (var i = 0; i < length; ++i) { - if (array[i].id === ref.id) { - if (ref.$isDeleted) { - array.splice(i, 1); - } else { - Vue.set(array, i, ref); - } - return; - } - } - if (ref.$isDeleted === false) { - $app.playerModerationTable.data.push(ref); - $app.notifyMenu('moderation'); - } - }); - - API.$on('PLAYER-MODERATION:@DELETE', function (args) { - var { ref } = args; - var array = $app.playerModerationTable.data; - var { length } = array; - for (var i = 0; i < length; ++i) { - if (array[i].id === ref.id) { - array.splice(i, 1); - return; - } - } - }); - - $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', function () { - $app.notificationTable.data = []; - }); - - API.$on('NOTIFICATION', function (args) { - var { ref } = args; - var array = $app.notificationTable.data; - var { length } = array; - for (var i = 0; i < length; ++i) { - if (array[i].id === ref.id) { - if (ref.$isDeleted) { - array.splice(i, 1); - } else { - Vue.set(array, i, ref); - } - return; - } - } - if (ref.$isDeleted === false) { - $app.notificationTable.data.push(ref); - $app.notifyMenu('notification'); - } - }); - - API.$on('NOTIFICATION:@DELETE', function (args) { - var { ref } = args; - var array = $app.notificationTable.data; - var { length } = array; - for (var i = 0; i < length; ++i) { - if (array[i].id === ref.id) { - array.splice(i, 1); - 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.userLanguageVisible = 0; - $app.data.userLanguageSelected = ''; - $app.data.userLanguages = (function () { - var data = []; - for (var key in subsetOfLanguages) { - var value = subsetOfLanguages[key]; - data.push({ - key, - value - }); - } - return data; - }()); - $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; - $app.data.isDarkMode = VRCXStorage.GetBool('isDarkMode'); - $appDarkStyle.disabled = $app.data.isDarkMode === false; - $app.watch.isDarkMode = function () { - VRCXStorage.SetBool('isDarkMode', this.isDarkMode); - $appDarkStyle.disabled = this.isDarkMode === false; - }; - $app.data.isStartAtWindowsStartup = VRCXStorage.GetBool('VRCX_StartAtWindowsStartup'); - $app.data.isStartAsMinimizedState = VRCXStorage.GetBool('VRCX_StartAsMinimizedState'); - $app.data.isCloseToTray = VRCXStorage.GetBool('VRCX_CloseToTray'); - var saveVRCXWindowOption = function () { - VRCXStorage.SetBool('VRCX_StartAtWindowsStartup', this.isStartAtWindowsStartup); - VRCXStorage.SetBool('VRCX_StartAsMinimizedState', this.isStartAsMinimizedState); - VRCXStorage.SetBool('VRCX_CloseToTray', this.isCloseToTray); - VRCX.SetStartup(this.isStartAtWindowsStartup); - }; - $app.watch.isStartAtWindowsStartup = saveVRCXWindowOption; - $app.watch.isStartAsMinimizedState = saveVRCXWindowOption; - $app.watch.isCloseToTray = saveVRCXWindowOption; - - API.$on('LOGIN', function () { - $app.currentUserTreeData = []; - $app.pastDisplayNameTable.data = []; - }); - - API.$on('USER:CURRENT', function (args) { - if (args.ref.pastDisplayNames) { - $app.pastDisplayNameTable.data = args.ref.pastDisplayNames; - } - }); - - API.$on('VISITS', function (args) { - $app.visits = args.json; - }); - - $app.methods.addUserLanguage = function (language) { - if (language !== String(language)) { - return; - } - API.addUserTags({ - tags: [`language_${language}`] - }); - }; - - $app.methods.removeUserLanguage = function (language) { - if (language !== String(language)) { - return; - } - API.removeUserTags({ - tags: [`language_${language}`] - }); - }; - - $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: 'OK', - 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: 'OK', - 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: 'OK', - 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, - - worldSorting: 'update', - avatarSorting: 'update', - - treeData: [], - memo: '' - }; - - $app.watch['userDialog.memo'] = function () { - var D = this.userDialog; - this.saveMemo(D.id, D.memo); - }; - - $app.methods.getFaviconUrl = function (resource) { - try { - var url = new URL(resource); - return `https://www.google.com/s2/favicons?domain=${url.origin}`; - } catch (err) { - return ''; - } - }; - - API.$on('LOGOUT', function () { - $app.userDialog.visible = false; - }); - - API.$on('USER', function (args) { - var { ref } = args; - var D = $app.userDialog; - if (D.visible === false || - D.id !== ref.id) { - return; - } - D.ref = ref; - $app.applyUserDialogLocation(); - }); - - API.$on('WORLD', function (args) { - var D = $app.userDialog; - if (D.visible === false || - D.$location.worldId !== args.ref.id) { - return; - } - $app.applyUserDialogLocation(); - }); - - API.$on('FRIEND:STATUS', function (args) { - var D = $app.userDialog; - if (D.visible === false || - D.id !== args.params.userId) { - return; - } - var { json } = args; - D.isFriend = json.isFriend; - D.incomingRequest = json.incomingRequest; - D.outgoingRequest = json.outgoingRequest; - }); - - API.$on('FRIEND:REQUEST', function (args) { - var D = $app.userDialog; - if (D.visible === false || - D.id !== args.params.userId) { - return; - } - if (args.json.success) { - D.isFriend = true; - } else { - D.outgoingRequest = true; - } - }); - - API.$on('FRIEND:REQUEST:CANCEL', function (args) { - var D = $app.userDialog; - if (D.visible === false || - D.id !== args.params.userId) { - return; - } - D.outgoingRequest = false; - }); - - API.$on('NOTIFICATION', function (args) { - var { ref } = args; - var D = $app.userDialog; - if (D.visible === false || - ref.$isDeleted || - ref.type !== 'friendRequest' || - ref.senderUserId !== D.id) { - return; - } - D.incomingRequest = true; - }); - - API.$on('NOTIFICATION:ACCEPT', function (args) { - var { ref } = args; - var D = $app.userDialog; - // 얘는 @DELETE가 오고나서 ACCEPT가 옴 - // 따라서 $isDeleted라면 ref가 undefined가 됨 - if (D.visible === false || - ref === undefined || - ref.type !== 'friendRequest' || - ref.senderUserId !== D.id) { - return; - } - D.isFriend = true; - }); - - API.$on('NOTIFICATION:@DELETE', function (args) { - var { ref } = args; - var D = $app.userDialog; - if (D.visible === false || - ref.type !== 'friendRequest' || - ref.senderUserId !== D.id) { - return; - } - D.incomingRequest = false; - }); - - API.$on('FRIEND:DELETE', function (args) { - var D = $app.userDialog; - if (D.visible === false || - D.id !== args.params.userId) { - return; - } - D.isFriend = false; - }); - - API.$on('PLAYER-MODERATION', function (args) { - var { ref } = args; - var D = $app.userDialog; - if (D.visible === false || - ref.$isDeleted || - ref.targetUserId !== D.id && - ref.sourceUserId !== this.currentUser.id) { - return; - } - if (ref.type === 'block') { - D.isBlock = true; - } else if (ref.type === 'mute') { - D.isMute = true; - } else if (ref.type === 'hideAvatar') { - D.isHideAvatar = true; - } - }); - - API.$on('PLAYER-MODERATION:@DELETE', function (args) { - var { ref } = args; - var D = $app.userDialog; - if (D.visible === false || - ref.targetUserId !== D.id || - ref.sourceUserId !== this.currentUser.id) { - return; - } - if (ref.type === 'block') { - D.isBlock = false; - } else if (ref.type === 'mute') { - D.isMute = false; - } else if (ref.type === 'hideAvatar') { - D.isHideAvatar = false; - } - }); - - API.$on('FAVORITE', function (args) { - var { ref } = args; - var D = $app.userDialog; - if (D.visible === false || - ref.$isDeleted || - ref.favoriteId !== D.id) { - return; - } - D.isFavorite = true; - }); - - API.$on('FAVORITE:@DELETE', function (args) { - var D = $app.userDialog; - if (D.visible === false || - D.id !== args.ref.favoriteId) { - return; - } - 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.friends.get(D.id); - D.isFriend = Boolean(D.friend); - D.incomingRequest = false; - D.outgoingRequest = false; - D.isBlock = false; - D.isMute = false; - D.isHideAvatar = false; - for (var ref of API.cachedPlayerModerations.values()) { - if (ref.$isDeleted === false && - ref.targetUserId === D.id && - ref.sourceUserId === API.currentUser.id) { - 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 = API.cachedFavoritesByObjectId.has(D.id); - this.applyUserDialogLocation(); - var worlds = []; - for (var ref of API.cachedWorlds.values()) { - if (ref.authorId === D.id) { - worlds.push(ref); - } - } - this.setUserDialogWorlds(worlds); - var avatars = []; - for (var ref of API.cachedAvatars.values()) { - if (ref.authorId === D.id) { - avatars.push(ref); - } - } - this.setUserDialogAvatars(avatars); - D.avatars = avatars; - D.isWorldsLoading = false; - D.isAvatarsLoading = false; - API.getFriendStatus({ - userId: D.id - }); - if (args.cache) { - API.getUser(args.params); - } - } - return args; - }); - }; - - $app.methods.applyUserDialogLocation = function () { - var D = this.userDialog; - var L = API.parseLocation(D.ref.location); - D.$location = L; - if (L.userId) { - var ref = API.cachedUsers.get(L.userId); - if (ref === undefined) { - API.getUser({ - userId: L.userId - }).then((args) => { - Vue.set(L, 'user', args.ref); - return args; - }); - } else { - L.user = ref; - } - } - var users = []; - if (L.isOffline === false) { - for (var { ref } of this.friends.values()) { - if (ref !== undefined && - ref.location === L.tag) { - users.push(ref); - } - } - } - if (this.isGameRunning && - this.lastLocation === L.tag) { - users.push(API.currentUser); - } - users.sort(compareByDisplayName); - D.users = users; - D.instance = {}; - if (L.worldId) { - var applyInstance = function (instances) { - for (var [id, occupants] of instances) { - if (id === L.instanceId) { - D.instance = { - id, - occupants - }; - break; - } - } - }; - var ref = API.cachedWorlds.get(L.worldId); - if (ref === undefined) { - API.getWorld({ - worldId: L.worldId - }).then((args) => { - if (args.ref.id === L.worldId) { - applyInstance(args.ref.instances); - } - return true; - }); - } else { - applyInstance(ref.instances); - } - } - }; - - $app.methods.setUserDialogWorlds = function (array) { - var D = this.userDialog; - if (D.worldSorting === 'update') { - array.sort(compareByUpdatedAt); - } else { - array.sort(compareByName); - } - D.worlds = array; - }; - - $app.methods.setUserDialogAvatars = function (array) { - var D = this.userDialog; - if (D.avatarSorting === 'update') { - array.sort(compareByUpdatedAt); - } else { - array.sort(compareByName); - } - D.avatars = array; - }; - - $app.methods.refreshUserDialogWorlds = function () { - var D = this.userDialog; - if (D.isWorldsLoading) { - return; - } - D.isWorldsLoading = true; - var params = { - n: 100, - offset: 0, - sort: 'updated', - order: 'descending', - user: 'friends', - userId: D.id, - releaseStatus: 'public' - }; - if (params.userId === API.currentUser.id) { - params.user = 'me'; - params.releaseStatus = 'all'; - } - var map = new Map(); - for (var ref of API.cachedWorlds.values()) { - if (ref.authorId === D.id) { - map.set(ref.id, ref); - } - } - API.bulk({ - fn: 'getWorlds', - N: -1, - params, - handle: (args) => { - for (var json of args.json) { - var $ref = API.cachedWorlds.get(json.id); - if ($ref !== undefined) { - map.set($ref.id, $ref); - } - } - }, - done: () => { - if (D.id === params.userId) { - var array = Array.from(map.values()); - this.setUserDialogWorlds(array); - } - D.isWorldsLoading = false; - } - }); - }; - - $app.methods.refreshUserDialogAvatars = function () { - var D = this.userDialog; - if (D.isAvatarsLoading) { - return; - } - D.isAvatarsLoading = true; - var params = { - n: 100, - offset: 0, - sort: 'updated', - order: 'descending', - user: 'friends', - userId: D.id, - releaseStatus: 'public' - }; - if (params.userId === API.currentUser.id) { - params.user = 'me'; - params.releaseStatus = 'all'; - } - var map = new Map(); - for (var ref of API.cachedAvatars.values()) { - if (ref.authorId === D.id) { - map.set(ref.id, ref); - } - } - API.bulk({ - fn: 'getAvatars', - N: -1, - params, - handle: (args) => { - for (var json of args.json) { - var $ref = API.cachedAvatars.get(json.id); - if ($ref !== undefined) { - map.set($ref.id, $ref); - } - } - }, - done: () => { - if (D.id === params.userId) { - var array = Array.from(map.values()); - this.setUserDialogAvatars(array); - } - D.isAvatarsLoading = false; - } - }); - }; - - var performUserDialogCommand = (command, userId) => { - switch (command) { - case 'Delete Favorite': - API.deleteFavorite({ - objectId: userId - }); - break; - case 'Accept Friend Request': - var key = API.getFriendRequest(userId); - if (key === '') { - API.sendFriendRequest({ - userId - }); - } else { - API.acceptNotification({ - notificationId: key - }); - } - break; - case 'Decline Friend Request': - var 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; - case 'Unfriend': - API.deleteFriend({ - userId - }); - break; - default: - break; - } - }; - - $app.methods.userDialogCommand = function (command) { - var D = this.userDialog; - if (D.visible === false) { - return; - } - 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 if (command === 'Show Avatar Author') { - var id = extractFileId(D.ref.currentAvatarImageUrl); - if (id) { - API.call(`file/${id}`).then((json) => { - if (json.ownerId === D.id) { - this.$message({ - message: 'It\'s personal (own) avatar', - type: 'warning' - }); - } else { - this.showUserDialog(json.ownerId); - } - }); - } else { - this.$message({ - message: 'Sorry, the author is unknown', - type: 'error' - }); - } - } 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.methods.changeUserDialogWorldSorting = function () { - var D = this.userDialog; - this.setUserDialogWorlds(D.worlds); - }; - - $app.methods.changeUserDialogAvatarSorting = function () { - var D = this.userDialog; - this.setUserDialogAvatars(D.avatars); - }; - - // App: World Dialog - - $app.data.worldDialog = { - visible: false, - loading: false, - id: '', - $location: {}, - ref: {}, - isFavorite: false, - rooms: [], - treeData: [], - fileCreatedAt: '', - fileSize: '' - }; - - API.$on('LOGOUT', function () { - $app.worldDialog.visible = false; - }); - - API.$on('WORLD', function (args) { - var { ref } = args; - var D = $app.worldDialog; - if (D.visible === false || - D.id !== ref.id) { - return; - } - D.ref = ref; - if (D.fileSize === 'Loading') { - var id = extractFileId(ref.assetUrl); - if (id) { - this.call(`file/${id}`).then(function (json) { - var ctx = json.versions[json.versions.length - 1]; - D.fileCreatedAt = ctx.created_at; - D.fileSize = `${(ctx.file.sizeInBytes / 1048576).toFixed(2)} MiB`; - }); - } - } - $app.applyWorldDialogInstances(); - }); - - API.$on('FAVORITE', function (args) { - var { ref } = args; - var D = $app.worldDialog; - if (D.visible === false || - ref.$isDeleted || - ref.favoriteId !== D.id) { - return; - } - D.isFavorite = true; - }); - - API.$on('FAVORITE:@DELETE', function (args) { - var D = $app.worldDialog; - if (D.visible === false || - D.id !== args.ref.favoriteId) { - return; - } - 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 === '') { - return; - } - 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 = API.cachedFavoritesByObjectId.has(D.id); - D.rooms = []; - this.applyWorldDialogInstances(); - if (args.cache) { - API.getWorld(args.params); - } - } - return args; - }); - }; - - $app.methods.applyWorldDialogInstances = function () { - var D = this.worldDialog; - var instances = {}; - for (var [id, occupants] of D.ref.instances) { - instances[id] = { - id, - occupants, - users: [] - }; - } - var { instanceId } = D.$location; - if (instanceId && - instances[instanceId] === undefined) { - instances[instanceId] = { - id: instanceId, - occupants: 0, - users: [] - }; - } - for (var { ref } of this.friends.values()) { - if (ref === undefined || - ref.$location === undefined || - ref.$location.worldId !== D.id) { - continue; - } - var { instanceId } = ref.$location; - var instance = instances[instanceId]; - if (instance === undefined) { - instance = { - id: instanceId, - occupants: 0, - users: [] - }; - instances[instanceId] = instance; - } - instance.users.push(ref); - } - if (this.isGameRunning) { - var lastLocation$ = API.parseLocation(this.lastLocation); - if (lastLocation$.worldId === D.id) { - var instance = instances[lastLocation$.instanceId]; - if (instance === undefined) { - instance = { - id: lastLocation$.instanceId, - occupants: 1, - users: [] - }; - instances[instance.id] = instance; - } - instance.users.push(API.currentUser); - } - } - var rooms = []; - for (var instance of Object.values(instances)) { - // due to references on callback of API.getUser() - // this should be block scope variable - const L = API.parseLocation(`${D.id}:${instance.id}`); - instance.location = L.tag; - instance.$location = L; - if (L.userId) { - var ref = API.cachedUsers.get(L.userId); - if (ref === undefined) { - API.getUser({ - userId: L.userId - }).then((args) => { - Vue.set(L, 'user', args.ref); - return args; - }); - } else { - L.user = ref; - } - } - instance.users.sort(compareByDisplayName); - rooms.push(instance); - } - // sort by more friends, occupants - rooms.sort(function (a, b) { - return b.users.length - a.users.length || - b.occupants - a.occupants; - }); - D.rooms = rooms; - }; - - $app.methods.worldDialogCommand = function (command) { - var D = this.worldDialog; - if (D.visible === false) { - return; - } - 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') { - return; - } - 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; - default: - 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) { - for (var unityPackage of ref.unityPackages) { - var platform = 'PC'; - if (unityPackage.platform === 'standalonewindows') { - platform = 'PC'; - } else if (unityPackage.platform === 'android') { - platform = 'Quest'; - } else if (unityPackage.platform) { - ({ platform } = unityPackage); - } - platforms.push(`${platform}/${unityPackage.unityVersion}`); - } - } - return platforms.join(', '); - }; - - // App: Avatar Dialog - - $app.data.avatarDialog = { - visible: false, - loading: false, - id: '', - ref: {}, - isFavorite: false, - treeData: [], - fileCreatedAt: '', - fileSize: '' - }; - - API.$on('LOGOUT', function () { - $app.avatarDialog.visible = false; - }); - - API.$on('AVATAR', function (args) { - var D = $app.avatarDialog; - if (D.visible === false || - D.id !== args.ref.id) { - return; - } - D.ref = args.ref; - if (D.fileSize === 'Loading') { - var id = extractFileId(args.ref.assetUrl); - if (id) { - this.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', function (args) { - var { ref } = args; - var D = $app.avatarDialog; - if (D.visible === false || - ref.$isDeleted || - ref.favoriteId !== D.id) { - return; - } - D.isFavorite = true; - }); - - API.$on('FAVORITE:@DELETE', function (args) { - var D = $app.avatarDialog; - if (D.visible === false || - D.id !== args.ref.favoriteId) { - return; - } - 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 = API.cachedFavoritesByObjectId.has(D.ref.id); - if (args.cache) { - API.getAvatar(args.params); - } - } - return args; - }); - }; - - $app.methods.avatarDialogCommand = function (command) { - var D = this.avatarDialog; - if (D.visible === false) { - return; - } - 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') { - return; - } - switch (command) { - case 'Delete Favorite': - API.deleteFavorite({ - objectId: D.id - }); - break; - case 'Select Avatar': - API.selectAvatar({ - avatarId: D.id - }); - break; - default: - break; - } - } - }); - } - }; - - $app.methods.refreshAvatarDialogTreeData = function () { - var D = this.avatarDialog; - D.treeData = buildTreeData(D.ref); - }; - - $app.computed.avatarDialogPlatform = function () { - var { ref } = this.avatarDialog; - var platforms = []; - if (ref.unityPackages) { - for (var unityPackage of ref.unityPackages) { - var platform = 'PC'; - if (unityPackage.platform === 'standalonewindows') { - platform = 'PC'; - } else if (unityPackage.platform === 'android') { - platform = 'Quest'; - } else if (unityPackage.platform) { - ({ platform } = unityPackage); - } - platforms.push(`${platform}/${unityPackage.unityVersion}`); - } - } - return platforms.join(', '); - }; - - // App: Favorite Dialog - - $app.data.favoriteDialog = { - visible: false, - loading: false, - type: '', - objectId: '', - groups: [] - }; - - API.$on('LOGOUT', function () { - $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', function () { - $app.inviteDialog.visible = false; - }); - - $app.methods.sendInvite = function () { - this.$confirm('Continue? Invite', 'Confirm', { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info', - callback: (action) => { - var D = this.inviteDialog; - if (action !== 'confirm' || - D.loading === true) { - return; - } - D.loading = true; - var params = { - receiverUserId: '', - type: 'invite', - message: 'This is a generated invite', - seen: false, - details: { - worldId: D.worldId, - worldName: D.worldName - } - }; - var inviteLoop = () => { - if (D.userIds.length > 0) { - params.receiverUserId = D.userIds.shift(); - API.sendNotification(params).finally(inviteLoop); - } else { - D.loading = false; - D.visible = false; - this.$message({ - message: 'Invite sent', - type: 'success' - }); - } - }; - inviteLoop(); - } - }); - }; - - $app.methods.showInviteDialog = function (tag) { - this.$nextTick(() => adjustDialogZ(this.$refs.inviteDialog.$el)); - var L = API.parseLocation(tag); - if (L.isOffline || - L.isPrivate || - L.worldId === '') { - return; - } - API.getCachedWorld({ - worldId: L.worldId - }).then((args) => { - var D = this.inviteDialog; - D.userIds = []; - D.worldId = L.tag; - D.worldName = args.ref.name; - D.visible = true; - }); - }; - - // App: Social Status Dialog - - $app.data.socialStatusDialog = { - visible: false, - loading: false, - status: '', - statusDescription: '' - }; - - API.$on('LOGOUT', function () { - $app.socialStatusDialog.visible = false; - }); - - $app.methods.saveSocialStatus = function () { - var D = this.socialStatusDialog; - if (D.loading) { - return; - } - 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: Bio Dialog - - $app.data.bioDialog = { - visible: false, - loading: false, - bio: '', - bioLinks: [] - }; - - API.$on('LOGOUT', function () { - $app.bioDialog.visible = false; - }); - - $app.methods.saveBio = function () { - var D = this.bioDialog; - if (D.loading) { - return; - } - D.loading = true; - API.saveCurrentUser({ - bio: D.bio, - bioLinks: D.bioLinks - }).finally(() => { - D.loading = false; - }).then((args) => { - D.visible = false; - this.$message({ - message: 'Bio updated', - type: 'success' - }); - return args; - }); - }; - - $app.methods.showBioDialog = function () { - this.$nextTick(() => adjustDialogZ(this.$refs.bioDialog.$el)); - var D = this.bioDialog; - D.bio = API.currentUser.bio; - D.bioLinks = API.currentUser.bioLinks.slice(); - D.visible = true; - }; - - // App: New Instance Dialog - - $app.data.newInstanceDialog = { - visible: false, - loading: false, - worldId: '', - instanceId: '', - accessType: '', - location: '', - url: '' - }; - - API.$on('LOGOUT', function () { - $app.newInstanceDialog.visible = false; - }); - - $app.methods.buildInstance = 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(${uuidv4()})`); - if (D.accessType === 'invite+') { - tags.push('~canRequestInvite'); - } - } - D.instanceId = tags.join(''); - }; - - var getLaunchURL = function (worldId, instanceId) { - if (instanceId) { - return `https://vrchat.net/launch?worldId=${encodeURIComponent(worldId)}&instanceId=${encodeURIComponent(instanceId)}`; - } - return `https://vrchat.net/launch?worldId=${encodeURIComponent(worldId)}`; - }; - - var updateLocationURL = function () { - var D = this.newInstanceDialog; - if (D.instanceId) { - D.location = `${D.worldId}:${D.instanceId}`; - } else { - D.location = D.worldId; - } - D.url = getLaunchURL(D.worldId, D.instanceId); - }; - $app.watch['newInstanceDialog.worldId'] = updateLocationURL; - $app.watch['newInstanceDialog.instanceId'] = updateLocationURL; - - $app.methods.showNewInstanceDialog = function (tag) { - this.$nextTick(() => adjustDialogZ(this.$refs.newInstanceDialog.$el)); - var L = API.parseLocation(tag); - if (L.isOffline || - L.isPrivate || - L.worldId === '') { - return; - } - var D = this.newInstanceDialog; - D.worldId = L.worldId; - D.accessType = 'public'; - this.buildInstance(); - 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') { - return; - } - API.saveCurrentUser({ - homeLocation: tag - }).then((args) => { - this.$message({ - message: 'Home world updated', - type: 'success' - }); - return args; - }); - } - }); - }; - - // App: Launch Dialog - - $app.data.launchDialog = { - visible: false, - loading: false, - desktop: VRCXStorage.GetBool('launchAsDesktop'), - location: '', - url: '' - }; - - $app.watch['launchDialog.desktop'] = function () { - VRCXStorage.SetBool('launchAsDesktop', this.launchDialog.desktop); - }; - - API.$on('LOGOUT', function () { - $app.launchDialog.visible = false; - }); - - $app.methods.showLaunchDialog = function (tag) { - this.$nextTick(() => adjustDialogZ(this.$refs.launchDialog.$el)); - var L = API.parseLocation(tag); - if (L.isOffline || - L.isPrivate || - L.worldId === '') { - return; - } - var D = this.launchDialog; - if (L.instanceId) { - D.location = `${L.worldId}:${L.instanceId}`; - } else { - D.location = L.worldId; - } - D.url = getLaunchURL(L.worldId, L.instanceId); - D.visible = true; - }; - - $app.methods.launchGame = function () { - var D = this.launchDialog; - VRCX.StartGame(D.location, D.desktop); - D.visible = false; - }; - - $app = new Vue($app); - window.$app = $app; -}); +!function(e){var t={};function r(i){if(t[i])return t[i].exports;var a=t[i]={i:i,l:!1,exports:{}};return e[i].call(a.exports,a,a.exports,r),a.l=!0,a.exports}r.m=e,r.c=t,r.d=function(e,t,i){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)r.d(i,a,function(t){return e[t]}.bind(null,a));return i},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){r(1),e.exports=r(2)},function(e,t){CefSharp.BindObjectAsync("VRCX","VRCXStorage","SQLite","LogWatcher","Discord").then((function(){document.addEventListener("keyup",(function(e){e.ctrlKey&&(e.shiftKey&&"KeyI"===e.code?VRCX.ShowDevTools():"KeyR"===e.code&&location.reload())})),VRCXStorage.GetBool=function(e){return"true"===this.Get(e)},VRCXStorage.SetBool=function(e,t){this.Set(e,t?"true":"false")},VRCXStorage.GetInt=function(e){return parseInt(this.Get(e),10)||0},VRCXStorage.SetInt=function(e,t){this.Set(e,String(t))},VRCXStorage.GetFloat=function(e){return parseFloat(this.Get(e),10)||0},VRCXStorage.SetFloat=function(e,t){this.Set(e,String(t))},VRCXStorage.GetArray=function(e){try{var t=JSON.parse(this.Get(e));if(Array.isArray(t))return t}catch(e){console.error(e)}return[]},VRCXStorage.SetArray=function(e,t){this.Set(e,JSON.stringify(t))},VRCXStorage.GetObject=function(e){try{var t=JSON.parse(this.Get(e));if(t===Object(t))return t}catch(e){console.error(e)}return{}},VRCXStorage.SetObject=function(e,t){this.Set(e,JSON.stringify(t))},setInterval((function(){VRCXStorage.Flush()}),3e5),Noty.overrideDefaults({layout:"bottomLeft",theme:"mint",timeout:6e3});var e=function(e,t){for(var{length:r}=e,i=0;i]/g,e=>`&#${e.charCodeAt(0)};`)};Vue.filter("escapeTag",t);Vue.filter("commaNumber",(function(e){return String(Number(e)).replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1,")}));var r=function(e,r){var i=new Date(e);if(isNaN(i))return t(e);var a=i.getHours(),s={YYYY:String(1e4+i.getFullYear()).substr(-4),MM:String(101+i.getMonth()).substr(-2),DD:String(100+i.getDate()).substr(-2),HH24:String(100+a).substr(-2),HH:String(100+(a>12?a-12:a)).substr(-2),MI:String(100+i.getMinutes()).substr(-2),SS:String(100+i.getSeconds()).substr(-2),AMPM:a>=12?"PM":"AM"};return r.replace(/YYYY|MM|DD|HH24|HH|MI|SS|AMPM/g,e=>s[e]||e)};Vue.filter("formatDate",r);Vue.filter("textToHex",(function(e){return String(e).split("").map(e=>e.charCodeAt(0).toString(16)).join(" ")}));var i=function(e){var r=Number(e);if(isNaN(r))return t(e);var i=[];return(r=Math.floor(r/1e3))<0&&(r=-r),r>=86400&&(i.push(`${Math.floor(r/86400)}d`),r%=86400),r>=3600&&(i.push(`${Math.floor(r/3600)}h`),r%=3600),r>=60&&(i.push(`${Math.floor(r/60)}m`),r%=60),(r||0===i.length)&&i.push(`${r}s`),i.join(" ")};Vue.filter("timeToText",i),Vue.use(VueLazyload,{preLoad:1,observer:!0,observerOptions:{rootMargin:"0px",threshold:.1}}),Vue.use(DataTables.DataTables),ELEMENT.locale(ELEMENT.lang.en);var a=document.createElement("link");a.disabled=!0,a.rel="stylesheet",a.href=`app.dark.css?_=${Date.now()}`,document.head.appendChild(a);var s={eng:"English",kor:"한국어",rus:"Русский",spa:"Español",por:"Português",zho:"中文",deu:"Deutsch",jpn:"日本語",fra:"Français",swe:"Svenska",nld:"Nederlands",pol:"Polski",dan:"Dansk",nor:"Norsk",ita:"Italiano",tha:"ภาษาไทย",fin:"Suomi",hun:"Magyar",ces:"Čeština",tur:"Türkçe",ara:"العربية"},o={eng:"us",kor:"kr",rus:"ru",spa:"es",por:"pt",zho:"cn",deu:"de",jpn:"jp",fra:"fr",swe:"se",nld:"nl",pol:"pl",dan:"dk",nor:"no",ita:"it",tha:"th",fin:"fi",hun:"hu",ces:"cz",tur:"tr",ara:"ae"},n={};n.eventHandlers=new Map,n.$emit=function(e,...t){var r=this.eventHandlers.get(e);if(void 0!==r)try{for(var i of r)i.apply(this,t)}catch(e){console.error(e)}},n.$on=function(e,t){var r=this.eventHandlers.get(e);void 0===r&&(r=[],this.eventHandlers.set(e,r)),r.push(t)},n.$off=function(e,t){var r=this.eventHandlers.get(e);if(void 0!==r)for(var{length:i}=r,a=0;a1?r.splice(a,1):this.eventHandlers.delete(e);break}},n.pendingGetRequests=new Map,n.call=function(e,r){var i=`https://api.vrchat.cloud/api/1/${e}`,a={method:"GET",mode:"cors",credentials:"include",cache:"no-cache",redirect:"follow",referrerPolicy:"no-referrer",...r},{params:s}=a,o="GET"===a.method;if(o){if(s===Object(s)){var n=new URL(i),{searchParams:d}=n;for(var l in s)d.set(l,s[l]);i=n.toString()}if(void 0!==(c=this.pendingGetRequests.get(i)))return c}else a.headers={"Content-Type":"application/json;charset=utf-8",...a.headers},a.body=s===Object(s)?JSON.stringify(s):"{}";var c=fetch(i,a).catch(e=>{this.$throw(0,e)}).then(e=>e.json().catch(()=>{e.ok&&this.$throw(0,"Invalid JSON response"),this.$throw(e.status)}).then(r=>e.ok?(r.success===Object(r.success)&&new Noty({type:"success",text:t(r.success.message)}).show(),r):(r===Object(r)&&(r.error===Object(r.error)?this.$throw(r.error.status_code||e.status,r.error.message,r.error.data):"string"==typeof r.error&&this.$throw(r.status_code||e.status,r.error)),this.$throw(e.status,r),r)));return o&&(c.finally(()=>{this.pendingGetRequests.delete(i)}),this.pendingGetRequests.set(i,c)),c},n.statusCodes={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",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"},n.$throw=function(e,r,i){var a=[];if(e>0){var s=this.statusCodes[e];void 0===s?a.push(`${e}`):a.push(`${e} ${s}`)}throw void 0!==r&&a.push(JSON.stringify(r)),void 0!==i&&a.push(JSON.stringify(i)),(a=a.map(e=>t(e)).join("
")).length&&new Noty({type:"error",text:a}).show(),new Error(a)},n.$bulk=function(e,t){return void 0!==e.handle&&e.handle.call(this,t,e),t.json.length>0&&(e.params.offset+=t.json.length,e.N>0?e.N>e.params.offset:e.N<0?t.json.length:e.params.n===t.json.length)?this.bulk(e):void 0!==e.done&&e.done.call(this,!0,e),t},n.bulk=function(e){this[e.fn](e.params).catch(t=>{throw void 0!==e.done&&e.done.call(this,!1,e),t}).then(t=>this.$bulk(e,t))},n.cachedConfig={},n.$on("CONFIG",(function(e){e.ref=this.applyConfig(e.json)})),n.applyConfig=function(e){var t={clientApiKey:"",...e};return this.cachedConfig=t,t},n.getConfig=function(){return this.call("config",{method:"GET"}).then(e=>{var t={json:e};return this.$emit("CONFIG",t),t})},n.parseLocation=function(e){var t={tag:e=String(e||""),isOffline:!1,isPrivate:!1,worldId:"",instanceId:"",instanceName:"",accessType:"",userId:null,hiddenId:null,privateId:null,friendsId:null,canRequestInvite:!1};if("offline"===e)t.isOffline=!0;else if("private"===e)t.isPrivate=!0;else if(!1===e.startsWith("local")){var r=e.indexOf(":");r>=0?(t.worldId=e.substr(0,r),t.instanceId=e.substr(r+1),t.instanceId.split("~").forEach((e,r)=>{if(r){var i=e.indexOf("("),a=i>=0?e.lastIndexOf(")"):-1,s=a>=0?e.substr(0,i):e,o=i',props:{location:String},methods:{parse(){var e=n.parseLocation(this.location);this.$el.style.display=e.isOffline||e.isPrivate?"none":""},confirm(){n.$emit("SHOW_LAUNCH_DIALOG",this.location)}},watch:{location(){this.parse()}},mounted(){this.parse()}}),Vue.component("location",{template:'{{ text }}',props:{location:String,link:{type:Boolean,default:!0}},data(){return{text:this.location}},methods:{parse(){var e=n.parseLocation(this.location);if(e.isOffline)this.text="Offline";else if(e.isPrivate)this.text="Private";else if(e.worldId){var t=n.cachedWorlds.get(e.worldId);void 0===t?n.getWorld({worldId:e.worldId}).then(t=>(e.tag===this.location&&(e.instanceId?this.text=`${t.json.name} #${e.instanceName} ${e.accessType}`:this.text=t.json.name),t)):e.instanceId?this.text=`${t.name} #${e.instanceName} ${e.accessType}`:this.text=t.name}},showWorldDialog(){this.link&&n.$emit("SHOW_WORLD_DIALOG",this.location)}},watch:{location(){this.parse()}},created(){this.parse()}}),n.isLoggedIn=!1,n.cachedUsers=new Map,n.currentUser={},n.$on("LOGOUT",(function(){VRCX.DeleteAllCookies(),this.isLoggedIn=!1})),n.$on("USER:CURRENT",(function(e){e.ref=this.applyCurrentUser(e.json)})),n.$on("USER:CURRENT:SAVE",(function(e){this.$emit("USER:CURRENT",e)})),n.$on("USER",(function(e){e.ref=this.applyUser(e.json)})),n.$on("USER:LIST",(function(e){for(var t of e.json)this.$emit("USER",{json:t,params:{userId:t.id}})})),n.logout=function(){return this.call("logout",{method:"PUT"}).finally(()=>{this.$emit("LOGOUT")})},n.login=function(e){var t=`${e.username}:${e.password}`;return t=(t=(t=encodeURIComponent(t)).replace(/%([0-9A-F]{2})/g,(e,t)=>String.fromCharCode(parseInt(t,16)))).replace("%","%25"),t=btoa(t),this.call(`auth/user?apiKey=${this.cachedConfig.clientApiKey}`,{method:"GET",headers:{Authorization:`Basic ${t}`}}).then(t=>{var r={json:t,params:e,origin:!0};return t.requiresTwoFactorAuth?this.$emit("USER:2FA",r):this.$emit("USER:CURRENT",r),r})},n.loginWithSteam=function(e){return this.call(`auth/steam?apiKey=${this.cachedConfig.clientApiKey}`,{method:"POST",params:e}).then(t=>{var r={json:t,params:e,origin:!0};return t.requiresTwoFactorAuth?this.$emit("USER:2FA",r):this.$emit("USER:CURRENT",r),r})},n.verifyOTP=function(e){return this.call("auth/twofactorauth/otp/verify",{method:"POST",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("OTP",r),r})},n.verifyTOTP=function(e){return this.call("auth/twofactorauth/totp/verify",{method:"POST",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("TOTP",r),r})},n.applyUserTrustLevel=function(e){e.$isModerator=e.developerType&&"none"!==e.developerType,e.$isTroll=!1;var{tags:t}=e;t.includes("admin_moderator")&&(e.$isModerator=!0),(t.includes("system_troll")||t.includes("system_probable_troll"))&&(e.$isTroll=!0),t.includes("system_legend")?(e.$trustLevel="Legendary User",e.$trustClass="x-tag-legendary"):t.includes("system_trust_legend")?(e.$trustLevel="Veteran User",e.$trustClass="x-tag-legend"):t.includes("system_trust_veteran")?(e.$trustLevel="Trusted User",e.$trustClass="x-tag-veteran"):t.includes("system_trust_trusted")?(e.$trustLevel="Known User",e.$trustClass="x-tag-trusted"):t.includes("system_trust_known")?(e.$trustLevel="User",e.$trustClass="x-tag-known"):t.includes("system_trust_basic")?(e.$trustLevel="New User",e.$trustClass="x-tag-basic"):(e.$trustLevel="Visitor",e.$trustClass="x-tag-untrusted"),e.$isModerator?(e.$trustLevel="VRChat Team",e.$trustClass="x-tag-vip"):e.$isTroll&&(e.$trustLevel="Nuisance",e.$trustClass="x-tag-troll")},n.applyUserLanguage=function(e){e.$languages=[];var{tags:t}=e;for(var r of t)if(!1!==r.startsWith("language_")){var i=r.substr(9),a=s[i];void 0!==a&&e.$languages.push({key:i,value:a})}},n.applyCurrentUser=function(e){var t=this.currentUser;return this.isLoggedIn?(Object.assign(t,e),t.homeLocation!==t.$homeLocation.tag&&(t.$homeLocation=this.parseLocation(t.homeLocation)),this.applyUserTrustLevel(t),this.applyUserLanguage(t)):((t={id:"",username:"",displayName:"",bio:"",bioLinks:[],pastDisplayNames:[],friends:[],currentAvatarImageUrl:"",currentAvatarThumbnailImageUrl:"",currentAvatar:"",homeLocation:"",twoFactorAuthEnabled:!1,status:"",statusDescription:"",state:"",tags:[],developerType:"",last_login:"",last_platform:"",allowAvatarCopying:!1,friendKey:"",onlineFriends:[],activeFriends:[],offlineFriends:[],$homeLocation:{},$isModerator:!1,$isTroll:!1,$trustLevel:"Visitor",$trustClass:"x-tag-untrusted",$languages:[],...e}).$homeLocation=this.parseLocation(t.homeLocation),this.applyUserTrustLevel(t),this.applyUserLanguage(t),this.currentUser=t,this.isLoggedIn=!0,this.$emit("LOGIN",{json:e,ref:t})),VRCXStorage.SetObject("currentUser",t),t},n.getCurrentUser=function(){return this.call(`auth/user?apiKey=${this.cachedConfig.clientApiKey}`,{method:"GET"}).then(e=>{var t={json:e,origin:!0};return e.requiresTwoFactorAuth?this.$emit("USER:2FA",t):this.$emit("USER:CURRENT",t),t})};var d=[],l=null;n.applyUser=function(e){var t,r=this.cachedUsers.get(e.id);if(void 0===r)(r={id:"",username:"",displayName:"",bio:"",bioLinks:[],currentAvatarImageUrl:"",currentAvatarThumbnailImageUrl:"",status:"",statusDescription:"",state:"",tags:[],developerType:"",last_login:"",last_platform:"",allowAvatarCopying:!1,isFriend:!1,friendKey:"",location:"",worldId:"",instanceId:"",$location:{},$location_at:Date.now(),$isModerator:!1,$isTroll:!1,$trustLevel:"Visitor",$trustClass:"x-tag-untrusted",$languages:[],...e}).$location=this.parseLocation(r.location),this.applyUserTrustLevel(r),this.applyUserLanguage(r),this.cachedUsers.set(r.id,r);else{var i={};for(var a in r)r[a]!==Object(r[a])&&(i[a]=!0);var s={...r};for(var a in Object.assign(r,e),r.location!==r.$location.tag&&(r.$location=this.parseLocation(r.location)),this.applyUserTrustLevel(r),this.applyUserLanguage(r),r)r[a]!==Object(r[a])&&(i[a]=!0);var o=!1;for(var a in i){var c=s[a],u=r[a];c===u?delete i[a]:(o=!0,i[a]=[u,c])}if(o&&"offline"!==r.status&&"offline"!==s.status){if(i.location){var h=Date.now();i.location.push(h-r.$location_at),r.$location_at=h}t={ref:r,props:i},d.push(t),null===l&&(l=setTimeout((function(){l=null;for(var{length:e}=d,t=0;t{var r={json:t,params:e};return this.$emit("USER",r),r})},n.getCachedUser=function(e){return new Promise((t,r)=>{var i=this.cachedUsers.get(e.userId);void 0===i?this.getUser(e).catch(r).then(t):t({cache:!0,json:i,params:e,ref:i})})},n.getUsers=function(e){return this.call("users",{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("USER:LIST",r),r})},n.saveCurrentUser=function(e){return this.call(`users/${this.currentUser.id}`,{method:"PUT",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("USER:CURRENT:SAVE",r),r})},n.addUserTags=function(e){return this.call(`users/${this.currentUser.id}/addTags`,{method:"POST",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("USER:CURRENT:SAVE",r),r})},n.removeUserTags=function(e){return this.call(`users/${this.currentUser.id}/removeTags`,{method:"POST",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("USER:CURRENT:SAVE",r),r})},n.cachedWorlds=new Map,n.$on("WORLD",(function(e){e.ref=this.applyWorld(e.json)})),n.$on("WORLD:LIST",(function(e){for(var t of e.json)this.$emit("WORLD",{json:t,params:{worldId:t.id}})})),n.applyWorld=function(e){var t=this.cachedWorlds.get(e.id);return void 0===t?(t={id:"",name:"",description:"",authorId:"",authorName:"",capacity:0,tags:[],releaseStatus:"",imageUrl:"",thumbnailImageUrl:"",assetUrl:"",assetUrlObject:{},pluginUrl:"",pluginUrlObject:{},unityPackageUrl:"",unityPackageUrlObject:{},unityPackages:[],version:0,favorites:0,created_at:"",updated_at:"",publicationDate:"",labsPublicationDate:"",visits:0,popularity:0,heat:0,publicOccupants:0,privateOccupants:0,occupants:0,instances:[],$isLabs:!1,...e},this.cachedWorlds.set(t.id,t)):Object.assign(t,e),t.$isLabs=t.tags.includes("system_labs"),t},n.getWorld=function(e){return this.call(`worlds/${e.worldId}`,{method:"GET"}).then(t=>{var r={json:t,params:e};return this.$emit("WORLD",r),r})},n.getCachedWorld=function(e){return new Promise((t,r)=>{var i=this.cachedWorlds.get(e.worldId);void 0===i?this.getWorld(e).catch(r).then(t):t({cache:!0,json:i,params:e,ref:i})})},n.getWorlds=function(e,t){var r="worlds";return void 0!==t&&(r=`worlds/${t}`),this.call(r,{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("WORLD:LIST",r),r})},n.friends200=new Set,n.friends404=new Map,n.isFriendsLoading=!1,n.$on("LOGIN",(function(){this.friends200.clear(),this.friends404.clear(),this.isFriendsLoading=!1})),n.$on("FRIEND:LIST",(function(e){for(var t of e.json)this.$emit("USER",{json:t,params:{userId:t.id}}),this.friends200.add(t.id),this.friends404.delete(t.id)})),n.isAllFriendsRetrived=function(e){if(e){for(var t of this.currentUser.friends)if(!1===this.friends200.has(t)){var r=this.friends404.get(t)||0;r<2&&this.friends404.set(t,r+1)}}else for(var t of this.currentUser.friends)if(!1===this.friends200.has(t)||this.friends404.get(t)<2)return!1;return!0},n.refreshFriends=function(){var e={n:100,offset:0,offline:!1},t=this.currentUser.onlineFriends.length;if(0===t){if(0===(t=this.currentUser.friends.length)||this.isAllFriendsRetrived(!1))return;e.offline=!0}this.isFriendsLoading||(this.isFriendsLoading=!0,this.bulk({fn:"getFriends",N:t,params:e,done(t,r){if(this.isAllFriendsRetrived(e.offline))this.isFriendsLoading=!1;else{var{length:i}=this.currentUser.friends;r.N=i-e.offset,r.N<=0&&(r.N=i),e.offset=0,e.offline=!0,this.bulk(r)}}}))},n.getFriends=function(e){return this.call("auth/user/friends",{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("FRIEND:LIST",r),r})},n.deleteFriend=function(e){return this.call(`auth/user/friends/${e.userId}`,{method:"DELETE"}).then(t=>{var r={json:t,params:e};return this.$emit("FRIEND:DELETE",r),r})},n.sendFriendRequest=function(e){return this.call(`user/${e.userId}/friendRequest`,{method:"POST"}).then(t=>{var r={json:t,params:e};return this.$emit("FRIEND:REQUEST",r),r})},n.cancelFriendRequest=function(e){return this.call(`user/${e.userId}/friendRequest`,{method:"DELETE"}).then(t=>{var r={json:t,params:e};return this.$emit("FRIEND:REQUEST:CANCEL",r),r})},n.getFriendStatus=function(e){return this.call(`user/${e.userId}/friendStatus`,{method:"GET"}).then(t=>{var r={json:t,params:e};return this.$emit("FRIEND:STATUS",r),r})},n.cachedAvatars=new Map,n.$on("AVATAR",(function(e){e.ref=this.applyAvatar(e.json)})),n.$on("AVATAR:LIST",(function(e){for(var t of e.json)this.$emit("AVATAR",{json:t,params:{avatarId:t.id}})})),n.$on("AVATAR:SELECT",(function(e){this.$emit("USER:CURRENT",e)})),n.applyAvatar=function(e){var t=this.cachedAvatars.get(e.id);return void 0===t?(t={id:"",name:"",description:"",authorId:"",authorName:"",tags:[],assetUrl:"",assetUrlObject:{},imageUrl:"",thumbnailImageUrl:"",releaseStatus:"",version:0,unityPackages:[],unityPackageUrl:"",unityPackageUrlObject:{},created_at:"",updated_at:"",...e},this.cachedAvatars.set(t.id,t)):Object.assign(t,e),t},n.getAvatar=function(e){return this.call(`avatars/${e.avatarId}`,{method:"GET"}).then(t=>{var r={json:t,params:e};return this.$emit("AVATAR",r),r})},n.getCachedAvatar=function(e){return new Promise((t,r)=>{var i=this.cachedAvatars.get(e.avatarId);void 0===i?this.getAvatar(e).catch(r).then(t):t({cache:!0,json:i,params:e,ref:i})})},n.getAvatars=function(e){return this.call("avatars",{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("AVATAR:LIST",r),r})},n.selectAvatar=function(e){return this.call(`avatars/${e.avatarId}/select`,{method:"PUT",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("AVATAR:SELECT",r),r})},n.cachedNotifications=new Map,n.isNotificationsLoading=!1,n.$on("LOGIN",(function(){this.cachedNotifications.clear(),this.isNotificationsLoading=!1})),n.$on("NOTIFICATION",(function(e){e.ref=this.applyNotification(e.json)})),n.$on("NOTIFICATION:LIST",(function(e){for(var t of e.json)this.$emit("NOTIFICATION",{json:t,params:{notificationId:t.id}})})),n.$on("NOTIFICATION:ACCEPT",(function(e){var t=this.cachedNotifications.get(e.params.notificationId);void 0===t||t.$isDeleted||(e.ref=t,t.$isDeleted=!0,this.$emit("NOTIFICATION:@DELETE",{ref:t,params:{notificationId:t.id}}),this.$emit("FRIEND:ADD",{params:{userId:t.senderUserId}}))})),n.$on("NOTIFICATION:HIDE",(function(e){var t=this.cachedNotifications.get(e.params.notificationId);void 0===t&&t.$isDeleted||(e.ref=t,t.$isDeleted=!0,this.$emit("NOTIFICATION:@DELETE",{ref:t,params:{notificationId:t.id}}))})),n.applyNotification=function(e){var t=this.cachedNotifications.get(e.id);if(void 0===t?(t={id:"",senderUserId:"",senderUsername:"",type:"",message:"",details:{},seen:!1,created_at:"",$isDeleted:!1,$isExpired:!1,...e},this.cachedNotifications.set(t.id,t)):(Object.assign(t,e),t.$isExpired=!1),t.details!==Object(t.details)){var r={};if("{}"!==t.details)try{var i=JSON.parse(t.details);i===Object(i)&&(r=i)}catch(e){}t.details=r}return t},n.expireNotifications=function(){for(var e of this.cachedNotifications.values())e.$isExpired=!0},n.deleteExpiredNotifcations=function(){for(var e of this.cachedNotifications.values())e.$isDeleted||!1===e.$isExpired||(e.$isDeleted=!0,this.$emit("NOTIFICATION:@DELETE",{ref:e,params:{notificationId:e.id}}))},n.refreshNotifications=function(){this.isNotificationsLoading||(this.isNotificationsLoading=!0,this.expireNotifications(),this.bulk({fn:"getNotifications",N:-1,params:{n:100,offset:0},done(e){e&&this.deleteExpiredNotifcations(),this.isNotificationsLoading=!1}}))},n.getNotifications=function(e){return this.call("auth/user/notifications",{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("NOTIFICATION:LIST",r),r})},n.clearNotifications=function(){return this.call("auth/user/notifications/clear",{method:"PUT"}).then(e=>{var t={json:e};return this.$emit("NOTIFICATION:CLEAR",t),t})},n.sendNotification=function(e){return this.call(`user/${e.receiverUserId}/notification`,{method:"POST",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("NOTIFICATION:SEND",r),r})},n.acceptNotification=function(e){return this.call(`auth/user/notifications/${e.notificationId}/accept`,{method:"PUT"}).then(t=>{var r={json:t,params:e};return this.$emit("NOTIFICATION:ACCEPT",r),r})},n.hideNotification=function(e){return this.call(`auth/user/notifications/${e.notificationId}/hide`,{method:"PUT"}).then(t=>{var r={json:t,params:e};return this.$emit("NOTIFICATION:HIDE",r),r})},n.getFriendRequest=function(e){for(var t of this.cachedNotifications.values())if(!1===t.$isDeleted&&"friendRequest"===t.type&&t.senderUserId===e)return t.id;return""},n.parseInviteLocation=function(e){try{var t=n.parseLocation(e.details.worldId);return t.worldId&&t.instanceId?`${e.details.worldName} #${t.instanceName} ${t.accessType}`:e.message||e.details.worldId||e.details.worldName}catch(e){return""}},n.cachedPlayerModerations=new Map,n.isPlayerModerationsLoading=!1,n.$on("LOGIN",(function(){this.cachedPlayerModerations.clear(),this.isPlayerModerationsLoading=!1})),n.$on("PLAYER-MODERATION",(function(e){e.ref=this.applyPlayerModeration(e.json)})),n.$on("PLAYER-MODERATION:LIST",(function(e){for(var t of e.json)this.$emit("PLAYER-MODERATION",{json:t,params:{playerModerationId:t.id}})})),n.$on("PLAYER-MODERATION:SEND",(function(e){this.$emit("PLAYER-MODERATION",{json:e.json,params:{playerModerationId:e.json.id}})})),n.$on("PLAYER-MODERATION:DELETE",(function(e){var{type:t,moderated:r}=e.param,i=this.currentUser.id;for(var a of this.cachedPlayerModerations.values())!1===a.$isDeleted&&a.type===t&&a.targetUserId===r&&a.sourceUserId===i&&(a.$isDeleted=!0,this.$emit("PLAYER-MODERATION:@DELETE",{ref:a,params:{playerModerationId:a.id}}))})),n.applyPlayerModeration=function(e){var t=this.cachedPlayerModerations.get(e.id);return void 0===t?(t={id:"",type:"",sourceUserId:"",sourceDisplayName:"",targetUserId:"",targetDisplayName:"",created:"",$isDeleted:!1,$isExpired:!1,...e},this.cachedPlayerModerations.set(t.id,t)):(Object.assign(t,e),t.$isExpired=!1),t},n.expirePlayerModerations=function(){for(var e of this.cachedPlayerModerations.values())e.$isExpired=!0},n.deleteExpiredPlayerModerations=function(){for(var e of this.cachedPlayerModerations.values())e.$isDeleted||!1===e.$isExpired||(e.$isDeleted=!0,this.$emit("PLAYER-MODERATION:@DELETE",{ref:e,params:{playerModerationId:e.id}}))},n.refreshPlayerModerations=function(){this.isPlayerModerationsLoading||(this.isPlayerModerationsLoading=!0,this.expirePlayerModerations(),Promise.all([this.getPlayerModerations(),this.getPlayerModerationsAgainstMe()]).finally(()=>{this.isPlayerModerationsLoading=!1}).then(()=>{this.deleteExpiredPlayerModerations()}))},n.getPlayerModerations=function(){return this.call("auth/user/playermoderations",{method:"GET"}).then(e=>{var t={json:e};return this.$emit("PLAYER-MODERATION:LIST",t),t})},n.getPlayerModerationsAgainstMe=function(){return this.call("auth/user/playermoderated",{method:"GET"}).then(e=>{var t={json:e};return this.$emit("PLAYER-MODERATION:LIST",t),t})},n.sendPlayerModeration=function(e){return this.call("auth/user/playermoderations",{method:"POST",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("PLAYER-MODERATION:SEND",r),r})},n.deletePlayerModeration=function(e){return this.call("auth/user/unplayermoderate",{method:"PUT",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("PLAYER-MODERATION:DELETE",r),r})},n.cachedFavorites=new Map,n.cachedFavoritesByObjectId=new Map,n.cachedFavoriteGroups=new Map,n.cachedFavoriteGroupsByTypeName=new Map,n.favoriteFriendGroups=[],n.favoriteWorldGroups=[],n.favoriteAvatarGroups=[],n.isFavoriteLoading=!1,n.isFavoriteGroupLoading=!1,n.$on("LOGIN",(function(){this.cachedFavorites.clear(),this.cachedFavoritesByObjectId.clear(),this.cachedFavoriteGroups.clear(),this.cachedFavoriteGroupsByTypeName.clear(),this.favoriteFriendGroups=[],this.favoriteWorldGroups=[],this.favoriteAvatarGroups=[],this.isFavoriteLoading=!1,this.isFavoriteGroupLoading=!1,this.refreshFavorites()})),n.$on("FAVORITE",(function(e){var t=this.applyFavorite(e.json);t.$isDeleted||(e.ref=t)})),n.$on("FAVORITE:@DELETE",(function(e){var{ref:t}=e;null!==t.$groupRef&&--t.$groupRef.count})),n.$on("FAVORITE:LIST",(function(e){for(var t of e.json)this.$emit("FAVORITE",{json:t,params:{favoriteId:t.id}})})),n.$on("FAVORITE:ADD",(function(e){this.$emit("FAVORITE",{json:e.json,params:{favoriteId:e.json.id}})})),n.$on("FAVORITE:DELETE",(function(e){var t=this.cachedFavoritesByObjectId.get(e.params.objectId);void 0!==t&&(this.cachedFavoritesByObjectId.delete(e.params.objectId),t.$isDeleted||(e.ref=t,t.$isDeleted=!0,n.$emit("FAVORITE:@DELETE",{ref:t,params:{favoriteId:t.id}})))})),n.$on("FAVORITE:GROUP",(function(e){var t=this.applyFavoriteGroup(e.json);t.$isDeleted||(e.ref=t,null!==t.$groupRef&&(t.$groupRef.displayName=t.displayName))})),n.$on("FAVORITE:GROUP:LIST",(function(e){for(var t of e.json)this.$emit("FAVORITE:GROUP",{json:t,params:{favoriteGroupId:t.id}})})),n.$on("FAVORITE:GROUP:SAVE",(function(e){this.$emit("FAVORITE:GROUP",{json:e.json,params:{favoriteGroupId:e.json.id}})})),n.$on("FAVORITE:GROUP:CLEAR",(function(e){var t=`${e.params.type}:${e.params.group}`;for(var r of this.cachedFavorites.values())r.$isDeleted||r.$groupKey!==t||(this.cachedFavoritesByObjectId.delete(r.favoriteId),r.$isDeleted=!0,n.$emit("FAVORITE:@DELETE",{ref:r,params:{favoriteId:r.id}}))})),n.$on("FAVORITE:FRIEND:LIST",(function(e){for(var t of e.json)this.$emit("USER",{json:t,params:{userId:t.id}})})),n.$on("FAVORITE:WORLD:LIST",(function(e){for(var t of e.json)"???"!==t.id&&this.$emit("WORLD",{json:t,params:{worldId:t.id}})})),n.$on("FAVORITE:AVATAR:LIST",(function(e){for(var t of e.json)"hidden"!==t.releaseStatus&&this.$emit("AVATAR",{json:t,params:{avatarId:t.id}})})),n.applyFavorite=function(e){var t=this.cachedFavorites.get(e.id);if(void 0===t?(t={id:"",type:"",favoriteId:"",tags:[],$isDeleted:!1,$isExpired:!1,$groupKey:"",$groupRef:null,...e},this.cachedFavorites.set(t.id,t),this.cachedFavoritesByObjectId.set(t.favoriteId,t)):(Object.assign(t,e),t.$isExpired=!1),t.$groupKey=`${t.type}:${String(t.tags[0])}`,!1===t.$isDeleted&&null===t.$groupRef){var r=this.cachedFavoriteGroupsByTypeName.get(t.$groupKey);void 0!==r&&(t.$groupRef=r,++r.count)}return t},n.expireFavorites=function(){for(var e of this.cachedFavorites.values())e.$isExpired=!0},n.deleteExpiredFavorites=function(){for(var e of this.cachedFavorites.values())e.$isDeleted||!1===e.$isExpired||(e.$isDeleted=!0,this.$emit("FAVORITE:@DELETE",{ref:e,params:{favoriteId:e.id}}))},n.refreshFavoriteItems=function(){var e={friend:[0,"getFavoriteFriends"],world:[0,"getFavoriteWorlds"],avatar:[0,"getFavoriteAvatars"]};for(var t of this.cachedFavorites.values()){if(!t.$isDeleted)void 0!==(r=e[t.type])&&++r[0]}for(var r in e){var[i,a]=e[r];i>0&&this.bulk({fn:a,N:i,params:{n:100,offset:0}})}},n.refreshFavorites=function(){this.isFavoriteLoading||(this.isFavoriteLoading=!0,this.expireFavorites(),this.bulk({fn:"getFavorites",N:-1,params:{n:100,offset:0},done(e){e&&this.deleteExpiredFavorites(),this.refreshFavoriteItems(),this.refreshFavoriteGroups(),this.isFavoriteLoading=!1}}))},n.applyFavoriteGroup=function(e){var t=this.cachedFavoriteGroups.get(e.id);return void 0===t?(t={id:"",ownerId:"",ownerDisplayName:"",name:"",displayName:"",type:"",visibility:"",tags:[],$isDeleted:!1,$isExpired:!1,$groupRef:null,...e},this.cachedFavoriteGroups.set(t.id,t)):(Object.assign(t,e),t.$isExpired=!1),t},n.buildFavoriteGroups=function(){this.favoriteFriendGroups=[];for(var e=0;e<3;++e)this.favoriteFriendGroups.push({assign:!1,key:`friend:group_${e}`,type:"friend",name:`group_${e}`,displayName:`Group ${e+1}`,capacity:32,count:0});this.favoriteWorldGroups=[];for(e=0;e<4;++e)this.favoriteWorldGroups.push({assign:!1,key:`world:worlds${e+1}`,type:"world",name:`worlds${e+1}`,displayName:`Group ${e+1}`,capacity:32,count:0});this.favoriteAvatarGroups=[];for(e=0;e<1;++e)this.favoriteAvatarGroups.push({assign:!1,key:`avatar:avatars${e+1}`,type:"avatar",name:`avatars${e+1}`,displayName:`Group ${e+1}`,capacity:16,count:0});var t={friend:this.favoriteFriendGroups,world:this.favoriteWorldGroups,avatar:this.favoriteAvatarGroups},r=new Set;for(var i of this.cachedFavoriteGroups.values()){if(!i.$isDeleted)if(void 0!==(s=t[i.type]))for(var a of s)if(!1===a.assign&&a.name===i.name){a.assign=!0,a.displayName=i.displayName,i.$groupRef=a,r.add(i.id);break}}for(var i of this.cachedFavoriteGroups.values()){var s;if(!i.$isDeleted&&!r.has(i.id))if(void 0!==(s=t[i.type]))for(var a of s)if(!1===a.assign){a.assign=!0,a.key=`${a.type}:${i.name}`,a.name=i.name,a.displayName=i.displayName,i.$groupRef=a,r.add(i.id);break}}for(var o in this.cachedFavoriteGroupsByTypeName.clear(),t)for(var a of t[o])this.cachedFavoriteGroupsByTypeName.set(a.key,a);for(var i of this.cachedFavorites.values()){if(i.$groupRef=null,!i.$isDeleted)void 0!==(a=this.cachedFavoriteGroupsByTypeName.get(i.$groupKey))&&(i.$groupRef=a,++a.count)}},n.expireFavoriteGroups=function(){for(var e of this.cachedFavoriteGroups.values())e.$isExpired=!0},n.deleteExpiredFavoriteGroups=function(){for(var e of this.cachedFavoriteGroups.values())e.$isDeleted||!1===e.$isExpired||(e.$isDeleted=!0,this.$emit("FAVORITE:GROUP:@DELETE",{ref:e,params:{favoriteGroupId:e.id}}))},n.refreshFavoriteGroups=function(){this.isFavoriteGroupLoading||(this.isFavoriteGroupLoading=!0,this.expireFavoriteGroups(),this.bulk({fn:"getFavoriteGroups",N:-1,params:{n:100,offset:0},done(e){e&&(this.deleteExpiredFavoriteGroups(),this.buildFavoriteGroups()),this.isFavoriteGroupLoading=!1}}))},n.getFavorites=function(e){return this.call("favorites",{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("FAVORITE:LIST",r),r})},n.addFavorite=function(e){return this.call("favorites",{method:"POST",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("FAVORITE:ADD",r),r})},n.deleteFavorite=function(e){return this.call(`favorites/${e.objectId}`,{method:"DELETE"}).then(t=>{var r={json:t,params:e};return this.$emit("FAVORITE:DELETE",r),r})},n.getFavoriteGroups=function(e){return this.call("favorite/groups",{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("FAVORITE:GROUP:LIST",r),r})},n.saveFavoriteGroup=function(e){return this.call(`favorite/group/${e.type}/${e.group}/${this.currentUser.id}`,{method:"PUT",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("FAVORITE:GROUP:SAVE",r),r})},n.clearFavoriteGroup=function(e){return this.call(`favorite/group/${e.type}/${e.group}/${this.currentUser.id}`,{method:"DELETE",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("FAVORITE:GROUP:CLEAR",r),r})},n.getFavoriteFriends=function(e){return this.call("auth/user/friends/favorite",{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("FAVORITE:FRIEND:LIST",r),r})},n.getFavoriteWorlds=function(e){return this.call("worlds/favorites",{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("FAVORITE:WORLD:LIST",r),r})},n.getFavoriteAvatars=function(e){return this.call("avatars/favorites",{method:"GET",params:e}).then(t=>{var r={json:t,params:e};return this.$emit("FAVORITE:AVATAR:LIST",r),r})},n.webSocket=null,n.$on("LOGOUT",(function(){this.closeWebSocket()})),n.$on("USER:CURRENT",(function(){null===this.webSocket&&this.getAuth()})),n.$on("AUTH",(function(e){e.json.ok&&this.connectWebSocket(e.json.token)})),n.$on("PIPELINE",(function(e){var{type:t,content:r}=e.json;switch(t){case"notification":this.$emit("NOTIFICATION",{json:r,params:{notificationId:r.id}});break;case"friend-add":this.$emit("USER",{json:r.user,params:{userId:r.userId}}),this.$emit("FRIEND:ADD",{params:{userId:r.userId}});break;case"friend-delete":this.$emit("FRIEND:DELETE",{params:{userId:r.userId}});break;case"friend-online":"private"!==r.location&&this.$emit("WORLD",{json:r.world,params:{worldId:r.world.id}}),this.$emit("USER",{json:{location:r.location,...r.user},params:{userId:r.userId}}),this.$emit("FRIEND:STATE",{json:{state:"online"},params:{userId:r.userId}});break;case"friend-active":this.$emit("USER",{json:r.user,params:{userId:r.userId}}),this.$emit("FRIEND:STATE",{json:{state:"active"},params:{userId:r.userId}});break;case"friend-offline":this.$emit("FRIEND:STATE",{json:{state:"offline"},params:{userId:r.userId}});break;case"friend-update":this.$emit("USER",{json:r.user,params:{userId:r.userId}});break;case"friend-location":"private"!==r.location&&this.$emit("WORLD",{json:r.world,params:{worldId:r.world.id}}),r.userId===this.currentUser.id?this.$emit("USER",{json:r.user,params:{userId:r.userId}}):this.$emit("USER",{json:{location:r.location,...r.user},params:{userId:r.userId}});break;case"user-update":this.$emit("USER:CURRENT",{json:r.user,params:{userId:r.userId}});break;case"user-location":r.world===Object(r.world)&&this.$emit("WORLD",{json:r.world,params:{worldId:r.world.id}}),this.$emit("USER",{json:{id:r.userId,location:r.location},params:{userId:r.userId}})}})),n.getAuth=function(){return this.call("auth",{method:"GET"}).then(e=>{var t={json:e};return this.$emit("AUTH",t),t})},n.connectWebSocket=function(e){if(null===this.webSocket){var t=new WebSocket(`wss://pipeline.vrchat.cloud/?auth=${e}`);t.onclose=()=>{this.webSocket===t&&(this.webSocket=null);try{t.close()}catch(e){}},t.onerror=t.onclose,t.onmessage=({data:e})=>{try{var t=JSON.parse(e);t.content=JSON.parse(t.content),this.$emit("PIPELINE",{json:t})}catch(e){console.error(e)}},this.webSocket=t}},n.closeWebSocket=function(){var e=this.webSocket;if(null!==e){this.webSocket=null;try{e.close()}catch(e){}}},n.getVisits=function(){return this.call("visits",{method:"GET"}).then(e=>{var t={json:e};return this.$emit("VISITS",t),t})};var c=e=>{var t=String(e).match(/file_[0-9A-Za-z-]+/);return t?t[0]:""},u=e=>{var t=[];for(var r in e){var i=e[r];Array.isArray(i)?t.push({children:i.map((e,t)=>e===Object(e)?{children:u(e),key:t}:{key:t,value:e}),key:r}):i===Object(i)?t.push({children:u(i),key:r}):t.push({key:r,value:String(i)})}return t.sort((function(e,t){var r=String(e.key).toUpperCase(),i=String(t.key).toUpperCase();return ri?1:0})),t},h=[];Vue.component("timer",{template:'',props:{epoch:{type:Number,default:()=>Date.now()}},data:()=>({text:""}),methods:{update(){this.text=i(Date.now()-this.epoch)}},watch:{date(){this.update()}},mounted(){h.push(this),this.update()},destroyed(){e(h,this)}}),setInterval((function(){for(var e of h)e.update()}),5e3);var f={data:{API:n,VRCX:VRCX,nextRefresh:0,isGameRunning:!1,appVersion:"VRCX 2020.04.07",latestAppVersion:"",ossDialog:!1},computed:{},methods:{},watch:{},el:"#x-app",mounted(){LogWatcher.Reset().then(()=>{n.$on("SHOW_WORLD_DIALOG",e=>this.showWorldDialog(e)),n.$on("SHOW_LAUNCH_DIALOG",e=>this.showLaunchDialog(e)),setInterval(()=>this.update(),1e3),this.update(),this.$nextTick((function(){this.$el.style.display="",this.loginForm.loading=!0,n.getConfig().catch(e=>{throw this.loginForm.loading=!1,e}).then(e=>(n.getCurrentUser().finally(()=>{this.loginForm.loading=!1}),e))}))}),this.checkAppVersion()}};f.methods.openExternalLink=function(e){this.$confirm(`${e}`,"Open External Link",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:t=>{"confirm"===t&&VRCX.OpenLink(e)}})},f.methods.languageClass=function(e){var t={},r=o[e];return void 0!==r&&(t[r]=!0),t},f.methods.checkAppVersion=function(){fetch("https://api.github.com/repos/pypy-vrc/VRCX/releases/latest").then(e=>e.json()).then(e=>{e===Object(e)&&e.name&&e.published_at?(this.latestAppVersion=`${e.name} (${r(e.published_at,"YYYY-MM-DD HH24:MI:SS")})`,e.name>this.appVersion&&(new Noty({type:"info",text:`Update available!!
${this.latestAppVersion}`,timeout:6e4,callbacks:{onClick:()=>VRCX.OpenLink("https://github.com/pypy-vrc/VRCX/releases")}}).show(),this.notifyMenu("more"))):this.latestAppVersion="Error occured"})},f.methods.update=function(){!1!==n.isLoggedIn&&(--this.nextRefresh<=0&&(this.nextRefresh=60,n.getCurrentUser().catch(e=>{throw 401===e.status_code&&n.getConfig().then(e=>(n.login({username:this.loginForm.username,password:this.loginForm.password}).catch(e=>{throw 401===e.status_code&&n.logout(),e}),e)),e})),this.checkActiveFriends(),this.refreshGameLog(),VRCX.IsGameRunning().then(e=>{e!==this.isGameRunning&&(this.isGameRunning=e,Discord.SetTimestamps(Date.now(),0)),this.updateDiscord(),this.updateOpenVR()}))},f.methods.updateSharedFeed=function(){for(var e=[],{data:t}=this.gameLogTable,r=t.length,i=0;i<25&&!(r<=0);){if((d=t[--r]).type){var a=!1,s=!1;for(var o of n.cachedUsers.values())if(o.displayName===d.data){a=this.friends.has(o.id),s=n.cachedFavoritesByObjectId.has(o.id);break}e.push({...d,isFriend:a,isFavorite:s})}else e.push(d);++i}var{data:t}=this.feedTable;for(r=t.length,i=0;i<25&&!(r<=0);){var d;"Avatar"!==(d=t[--r]).type&&(e.push({...d,isFriend:this.friends.has(d.userId),isFavorite:n.cachedFavoritesByObjectId.has(d.userId)}),++i)}e.sort((function(e,t){return e.created_att.created_at?-1:0})),e.length>25&&(e.length=25),VRCXStorage.SetArray("sharedFeeds",e)},f.methods.notifyMenu=function(e){var{menu:t}=this.$refs;if(t.activeIndex!==e){var r=t.items[e];r&&r.$el.classList.add("notify")}},f.methods.selectMenu=function(e){document.querySelectorAll('[role="tooltip"]').forEach(e=>{e.remove()});var t=this.$refs.menu.items[e];t&&t.$el.classList.remove("notify")},f.methods.promptTOTP=function(){this.$prompt("Enter a numeric code from your authenticator app","Two-factor Authentication",{distinguishCancelAndClose:!0,cancelButtonText:"Use OTP",confirmButtonText:"Verify",inputPlaceholder:"Code",inputPattern:/^[0-9]{6}$/,inputErrorMessage:"Invalid Code",callback:(e,t)=>{"confirm"===e?n.verifyTOTP({code:t.inputValue}).catch(e=>{throw this.promptTOTP(),e}).then(e=>(n.getCurrentUser(),e)):"cancel"===e&&this.promptOTP()}})},f.methods.promptOTP=function(){this.$prompt("Enter one of your saved recovery codes","Two-factor Authentication",{distinguishCancelAndClose:!0,cancelButtonText:"Use TOTP",confirmButtonText:"Verify",inputPlaceholder:"Code",inputPattern:/^[a-z0-9]{4}-[a-z0-9]{4}$/,inputErrorMessage:"Invalid Code",callback:(e,t)=>{"confirm"===e?n.verifyOTP({code:t.inputValue}).catch(e=>{throw this.promptOTP(),e}).then(e=>(n.getCurrentUser(),e)):"cancel"===e&&this.promptTOTP()}})},n.$on("USER:2FA",(function(){f.promptTOTP()})),n.$on("LOGOUT",(function(){new Noty({type:"success",text:`See you again, ${t(this.currentUser.displayName)}!`}).show()})),n.$on("LOGIN",(function(e){new Noty({type:"success",text:`Hello there, ${t(e.ref.displayName)}!`}).show(),f.$refs.menu.activeIndex="feed"})),f.data.loginForm={loading:!0,username:"",password:"",rules:{username:[{required:!0,trigger:"blur"}],password:[{required:!0,trigger:"blur"}]}},f.methods.login=function(){this.$refs.loginForm.validate(e=>{e&&!this.loginForm.loading&&(this.loginForm.loading=!0,n.getConfig().catch(e=>{throw this.loginForm.loading=!1,e}).then(e=>(n.login({username:this.loginForm.username,password:this.loginForm.password}).finally(()=>{this.loginForm.loading=!1}),e)))})},f.methods.loginWithSteam=function(){this.loginForm.loading||(this.loginForm.loading=!0,VRCX.LoginWithSteam().catch(e=>{throw this.loginForm.loading=!1,e}).then(e=>{e?n.getConfig().catch(e=>{throw this.loginForm.loading=!1,e}).then(t=>(n.loginWithSteam({steamTicket:e}).finally(()=>{this.loginForm.loading=!1}),t)):(this.loginForm.loading=!1,this.$message({message:"It only works when VRChat is running.",type:"error"}))}))},f.methods.loadMemo=function(e){var t=`memo_${e}`;return VRCXStorage.Get(t)},f.methods.saveMemo=function(e,t){var r=`memo_${e}`;t?VRCXStorage.Set(r,String(t)):VRCXStorage.Remove(r);var i=this.friends.get(e);i&&(i.memo=String(t||""))},f.data.friends=new Map,f.data.pendingActiveFriends=new Set,f.data.friendsNo=0,f.data.isFriendsGroup0=!0,f.data.isFriendsGroup1=!0,f.data.isFriendsGroup2=!0,f.data.isFriendsGroup3=!1,f.data.friendsGroup0_=[],f.data.friendsGroup1_=[],f.data.friendsGroup2_=[],f.data.friendsGroup3_=[],f.data.friendsGroupA_=[],f.data.friendsGroupB_=[],f.data.friendsGroupC_=[],f.data.friendsGroupD_=[],f.data.sortFriendsGroup0=!1,f.data.sortFriendsGroup1=!1,f.data.sortFriendsGroup2=!1,f.data.sortFriendsGroup3=!1,f.data.orderFriendsGroup0=VRCXStorage.GetBool("orderFriendGroup0"),f.data.orderFriendsGroup1=VRCXStorage.GetBool("orderFriendGroup1"),f.data.orderFriendsGroup2=VRCXStorage.GetBool("orderFriendGroup2"),f.data.orderFriendsGroup3=VRCXStorage.GetBool("orderFriendGroup3");var p=function(){VRCXStorage.SetBool("orderFriendGroup0",this.orderFriendsGroup0),VRCXStorage.SetBool("orderFriendGroup1",this.orderFriendsGroup1),VRCXStorage.SetBool("orderFriendGroup2",this.orderFriendsGroup2),VRCXStorage.SetBool("orderFriendGroup3",this.orderFriendsGroup3)};f.watch.orderFriendsGroup0=p,f.watch.orderFriendsGroup1=p,f.watch.orderFriendsGroup2=p,f.watch.orderFriendsGroup3=p,f.methods.fetchActiveFriend=function(e){return this.pendingActiveFriends.add(e),n.getUser({userId:e}).then(t=>(this.pendingActiveFriends.delete(e),t))},f.methods.checkActiveFriends=function(){if(!1!==Array.isArray(n.currentUser.activeFriends))for(var e of n.currentUser.activeFriends)if(!this.pendingActiveFriends.has(e)){var t=n.cachedUsers.get(e);if(void 0===t||"offline"===t.status){if(this.pendingActiveFriends.size>=5)break;this.fetchActiveFriend(e)}}},n.$on("LOGIN",(function(){f.friends.clear(),f.pendingActiveFriends.clear(),f.friendsNo=0,f.isFriendsGroup0=!0,f.isFriendsGroup1=!0,f.isFriendsGroup2=!0,f.isFriendsGroup3=!1,f.friendsGroup0_=[],f.friendsGroup1_=[],f.friendsGroup2_=[],f.friendsGroup3_=[],f.friendsGroupA_=[],f.friendsGroupB_=[],f.friendsGroupC_=[],f.friendsGroupD_=[],f.sortFriendsGroup0=!1,f.sortFriendsGroup1=!1,f.sortFriendsGroup2=!1,f.sortFriendsGroup3=!1})),n.$on("USER:CURRENT",(function(e){f.refreshFriends(e.ref,e.origin)})),n.$on("USER",(function(e){f.updateFriend(e.ref.id)})),n.$on("FRIEND:ADD",(function(e){f.addFriend(e.params.userId)})),n.$on("FRIEND:DELETE",(function(e){f.deleteFriend(e.params.userId)})),n.$on("FRIEND:STATE",(function(e){f.updateFriend(e.params.userId,e.json.state)})),n.$on("FAVORITE",(function(e){f.updateFriend(e.ref.favoriteId)})),n.$on("FAVORITE:@DELETE",(function(e){f.updateFriend(e.ref.favoriteId)})),f.methods.refreshFriends=function(e,t){var r=new Map;for(var i of e.friends)r.set(i,"offline");for(var i of e.offlineFriends)r.set(i,"offline");for(var i of e.activeFriends)r.set(i,"active");for(var i of e.onlineFriends)r.set(i,"online");for(var[i,a]of r)this.friends.has(i)?this.updateFriend(i,a,t):this.addFriend(i,a);for(var i of this.friends.keys())!1===r.has(i)&&this.deleteFriend(i);t&&n.refreshFriends()},f.methods.addFriend=function(e,t){if(!this.friends.has(e)){var r=n.cachedUsers.get(e),i={id:e,state:t||"offline",isVIP:n.cachedFavoritesByObjectId.has(e),ref:r,name:"",no:++this.friendsNo,memo:this.loadMemo(e)};void 0===r?void 0!==(r=this.friendLog[e])&&r.displayName&&(i.name=r.displayName):i.name=r.name,this.friends.set(e,i),"online"===i.state?i.isVIP?(this.sortFriendsGroup0=!0,this.friendsGroup0_.push(i),this.friendsGroupA_.unshift(i)):(this.sortFriendsGroup1=!0,this.friendsGroup1_.push(i),this.friendsGroupB_.unshift(i)):"active"===i.state?(this.sortFriendsGroup2=!0,this.friendsGroup2_.push(i),this.friendsGroupC_.unshift(i)):(this.sortFriendsGroup3=!0,this.friendsGroup3_.push(i),this.friendsGroupD_.unshift(i))}},f.methods.deleteFriend=function(t){var r=this.friends.get(t);void 0!==r&&(this.friends.delete(t),"online"===r.state?r.isVIP?(e(this.friendsGroup0_,r),e(this.friendsGroupA_,r)):(e(this.friendsGroup1_,r),e(this.friendsGroupB_,r)):"active"===r.state?(e(this.friendsGroup2_,r),e(this.friendsGroupC_,r)):(e(this.friendsGroup3_,r),e(this.friendsGroupD_,r)))},f.methods.updateFriend=function(t,r,i){var a=this.friends.get(t);if(void 0!==a){var s=n.cachedUsers.get(t),o=n.cachedFavoritesByObjectId.has(t);void 0===r||a.state===r?(a.ref!==s&&(a.ref=s,"online"===a.state?a.isVIP?(e(this.friendsGroupA_,a),this.friendsGroupA_.push(a)):(e(this.friendsGroupB_,a),this.friendsGroupB_.push(a)):"active"===a.state?(e(this.friendsGroupC_,a),this.friendsGroupC_.push(a)):(e(this.friendsGroupD_,a),this.friendsGroupD_.push(a))),a.isVIP!==o&&(a.isVIP=o,"online"===a.state&&(a.isVIP?(e(this.friendsGroup1_,a),e(this.friendsGroupB_,a),this.sortFriendsGroup0=!0,this.friendsGroup0_.push(a),this.friendsGroupA_.unshift(a)):(e(this.friendsGroup0_,a),e(this.friendsGroupA_,a),this.sortFriendsGroup1=!0,this.friendsGroup1_.push(a),this.friendsGroupB_.unshift(a)))),void 0!==s&&a.name!==s.displayName&&(a.name=s.displayName,"online"===a.state?a.isVIP?this.sortFriendsGroup0=!0:this.sortFriendsGroup1=!0:"active"===a.state?this.sortFriendsGroup2=!0:this.sortFriendsGroup3=!0),i&&"online"!==a.state&&void 0!==s&&""!==s.location&&"offline"!==s.location&&n.getUser({userId:t})):("online"===a.state?a.isVIP?(e(this.friendsGroup0_,a),e(this.friendsGroupA_,a)):(e(this.friendsGroup1_,a),e(this.friendsGroupB_,a)):"active"===a.state?(e(this.friendsGroup2_,a),e(this.friendsGroupC_,a)):(e(this.friendsGroup3_,a),e(this.friendsGroupD_,a)),a.state!==r&&(a.state=r),a.isVIP!==o&&(a.isVIP=o),void 0!==s&&(a.ref!==s&&(a.ref=s),a.name!==s.displayName&&(a.name=s.displayName)),"online"===a.state?a.isVIP?(this.sortFriendsGroup0=!0,this.friendsGroup0_.push(a),this.friendsGroupA_.unshift(a)):(this.sortFriendsGroup1=!0,this.friendsGroup1_.push(a),this.friendsGroupB_.unshift(a)):"active"===a.state?(this.sortFriendsGroup2=!0,this.friendsGroup2_.push(a),this.friendsGroupC_.unshift(a)):(this.sortFriendsGroup3=!0,this.friendsGroup3_.push(a),this.friendsGroupD_.unshift(a)))}};var v=function(e,t){var r=String(e.name).toUpperCase(),i=String(t.name).toUpperCase();return ri?1:0},m=function(e,t){var r=String(e.updated_at).toUpperCase(),i=String(t.updated_at).toUpperCase();return ri?-1:0},g=function(e,t){var r=String(e.displayName).toUpperCase(),i=String(t.displayName).toUpperCase();return ri?1:0};f.computed.friendsGroup0=function(){return this.orderFriendsGroup0?this.friendsGroupA_:(this.sortFriendsGroup0&&(this.sortFriendsGroup0=!1,this.friendsGroup0_.sort(v)),this.friendsGroup0_)},f.computed.friendsGroup1=function(){return this.orderFriendsGroup1?this.friendsGroupB_:(this.sortFriendsGroup1&&(this.sortFriendsGroup1=!1,this.friendsGroup1_.sort(v)),this.friendsGroup1_)},f.computed.friendsGroup2=function(){return this.orderFriendsGroup2?this.friendsGroupC_:(this.sortFriendsGroup2&&(this.sortFriendsGroup2=!1,this.friendsGroup2_.sort(v)),this.friendsGroup2_)},f.computed.friendsGroup3=function(){return this.orderFriendsGroup3?this.friendsGroupD_:(this.sortFriendsGroup3&&(this.sortFriendsGroup3=!1,this.friendsGroup3_.sort(v)),this.friendsGroup3_)},f.methods.userStatusClass=function(e){var t={};return void 0!==e&&("offline"===e.location?t.offline=!0:"active"===e.status?t.active=!0:"join me"===e.status?t.joinme=!0:"ask me"===e.status?t.askme=!0:"busy"===e.status&&(t.busy=!0)),t},f.methods.confirmDeleteFriend=function(e){this.$confirm("Continue? Unfriend","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:t=>{"confirm"===t&&n.deleteFriend({userId:e})}})},f.data.quickSearch="",f.data.quickSearchItems=[],f.methods.quickSearchRemoteMethod=function(e){var t=[];if(e){var r=e.toUpperCase();for(var i of this.friends.values())if(void 0!==i.ref){var a=i.name.toUpperCase(),s=a.includes(r);if(!s){var o=String(i.ref.username);s=o.toUpperCase().includes(r)&&!o.startsWith("steam_")}!s&&i.memo&&(s=String(i.memo).toUpperCase().includes(r)),s&&t.push({value:i.id,label:i.name,ref:i.ref,NAME:a})}t.sort((function(e,t){var i=e.NAME.startsWith(r),a=t.NAME.startsWith(r);if(i!==a){if(i)return-1;if(a)return 1}return e.NAMEt.NAME?1:0})),t.length>4&&(t.length=4),t.push({value:`search:${e}`,label:e})}this.quickSearchItems=t},f.methods.quickSearchChange=function(e){e&&(e.startsWith("search:")?(this.searchText=e.substr(7),this.search(),this.$refs.menu.activeIndex="search"):this.showUserDialog(e))},f.methods.quickSearchVisibleChange=function(e){e&&(this.quickSearch="")},f.data.feedTable={data:[],filters:[{prop:"type",value:[],filterFn:(e,t)=>t.value.some(t=>t===e.type)},{prop:"displayName",value:""},{prop:"userId",value:!1,filterFn:(e,t)=>!t.value||n.cachedFavoritesByObjectId.has(e.userId)}],tableProps:{stripe:!0,size:"mini",defaultSort:{prop:"created_at",order:"descending"}},pageSize:10,paginationProps:{small:!0,layout:"sizes,prev,pager,next,total",pageSizes:[10,25,50,100]}},n.$on("LOGIN",(function(e){f.feedTable.data=VRCXStorage.GetArray(`${e.ref.id}_feedTable`),f.sweepFeed()})),n.$on("USER:UPDATE",(function(e){var{ref:t,props:r}=e;!1!==f.friends.has(t.id)&&(r.location&&("offline"===r.location[0]?f.addFeed("Offline",t,{location:r.location[1],time:r.location[2]}):"offline"===r.location[1]?f.addFeed("Online",t,{location:r.location[0]}):f.addFeed("GPS",t,{location:[r.location[0],r.location[1]],time:r.location[2]})),r.currentAvatarThumbnailImageUrl&&f.addFeed("Avatar",t,{avatar:r.currentAvatarThumbnailImageUrl}),(r.status||r.statusDescription)&&f.addFeed("Status",t,{status:[{status:r.status?r.status[0]:t.status,statusDescription:r.statusDescription?r.statusDescription[0]:t.statusDescription},{status:r.status?r.status[1]:t.status,statusDescription:r.statusDescription?r.statusDescription[1]:t.statusDescription}]}))}));var I=null;f.methods.saveFeed=function(){null===I&&(I=setTimeout(()=>{I=null,VRCXStorage.SetArray(`${n.currentUser.id}_feedTable`,this.feedTable.data)},1))},f.methods.addFeed=function(e,t,r){this.feedTable.data.push({created_at:(new Date).toJSON(),type:e,userId:t.id,displayName:t.displayName,...r}),this.sweepFeed(),this.saveFeed(),this.notifyMenu("feed")},f.methods.clearFeed=function(){this.$confirm("Continue? Clear Feed","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:e=>{if("confirm"===e){var t=this.feedTable;t.data=t.data.filter(e=>!t.filters.every(t=>{if(t.value){if(!Array.isArray(t.value))return t.filterFn?t.filterFn(e,t):String(e[t.prop]).toUpperCase().includes(String(t.value).toUpperCase());if(t.value.length){if(t.filterFn)return t.filterFn(e,t);var r=String(e[t.prop]).toUpperCase();return t.value.some(e=>r.includes(String(e).toUpperCase()))}}return!0}))}}})},f.methods.sweepFeed=function(){for(var{data:e}=this.feedTable,t=new Date(Date.now()-2592e5).toJSON(),r=0,i=e.length;rt.value.some(t=>t===e.type)},{prop:"data",value:""}],tableProps:{stripe:!0,size:"mini",defaultSort:{prop:"created_at",order:"descending"}},pageSize:10,paginationProps:{small:!0,layout:"sizes,prev,pager,next,total",pageSizes:[10,25,50,100]}},f.methods.resetGameLog=function(){LogWatcher.Reset().then(()=>{this.gameLogTable.data=[]})},f.methods.refreshGameLog=function(){LogWatcher.Get().then(e=>{if(e.length){var{data:t}=this.gameLogTable;for(var r of e){var i={created_at:String(r[0]),type:String(r[1]),data:String(r[2])};if("Location"===i.type){var a=i.data;a.endsWith(":")&&(a=a.substr(0,a.length-1),i.data=a),this.lastLocation=a}t.push(i)}this.sweepGameLog(),t.length&&this.notifyMenu("gameLog")}this.updateSharedFeed()})},f.methods.sweepGameLog=function(){for(var{data:e}=this.gameLogTable,t=new Date(Date.now()-2592e5).toJSON(),r=0,i=e.length;r(e.worldName=t.ref.name,t))}}var r=this.lastLocation$;r.worldName.length<2&&(r.worldName+="ᅠ".repeat(2-r.worldName.length)),this.discordInstance?Discord.SetText(r.worldName,`#${r.instanceName} ${r.accessType}`):Discord.SetText(r.worldName,""),Discord.SetActive(this.discordActive)}else Discord.SetActive(!1)},f.methods.lookupUser=function(e){for(var t of n.cachedUsers.values())if(t.displayName===e)return void this.showUserDialog(t.id);this.searchText=e,this.search(),this.$refs.menu.activeIndex="search",this.$refs.searchTab.currentName="0"},f.data.searchText="",f.data.searchUserResults=[],f.data.searchUserParams={},f.data.searchWorldResults=[],f.data.searchWorldOption="",f.data.searchWorldParams={},f.data.searchAvatarResults=[],f.data.searchAvatarParams={},f.data.isSearchUserLoading=!1,f.data.isSearchWorldLoading=!1,f.data.isSearchAvatarLoading=!1,n.$on("LOGIN",(function(){f.searchText="",f.searchUserResults=[],f.searchUserParams={},f.searchWorldResults=[],f.searchWorldOption="",f.searchWorldParams={},f.searchAvatarResults=[],f.searchAvatarParams={},f.isSearchUserLoading=!1,f.isSearchWorldLoading=!1,f.isSearchAvatarLoading=!1})),f.methods.clearSearch=function(){this.searchUserResults=[],this.searchWorldResults=[],this.searchAvatarResults=[]},f.methods.search=function(){this.searchUser(),this.searchWorld({})},f.methods.searchUser=function(){this.searchUserParams={n:10,offset:0,search:this.searchText},this.moreSearchUser()},f.methods.moreSearchUser=function(e){var t=this.searchUserParams;e&&(t.offset+=t.n*e,t.offset<0&&(t.offset=0)),this.isSearchUserLoading=!0,n.getUsers(t).finally(()=>{this.isSearchUserLoading=!1}).then(e=>{var t=new Map;for(var r of e.json){var i=n.cachedUsers.get(r.id);void 0!==i&&t.set(i.id,i)}return this.searchUserResults=Array.from(t.values()),e})},f.methods.searchWorld=function(e){this.searchWorldOption="";var t={n:10,offset:0};switch(e.sortHeading){case"featured":t.sort="order",t.featured="true";break;case"trending":t.sort="popularity",t.featured="false";break;case"updated":t.sort="updated";break;case"created":t.sort="created";break;case"publication":t.sort="publicationDate";break;case"shuffle":t.sort="shuffle";break;case"active":this.searchWorldOption="active";break;case"recent":this.searchWorldOption="recent";break;case"favorite":this.searchWorldOption="favorites";break;case"labs":t.sort="labsPublicationDate";break;case"heat":t.sort="heat",t.featured="false";break;default:t.sort="popularity",t.search=this.searchText}t.order=e.sortOrder||"descending","mine"===e.sortOwnership&&(t.user="me",t.releaseStatus="all"),e.tag&&(t.tag=e.tag),this.searchWorldParams=t,this.moreSearchWorld()},f.methods.moreSearchWorld=function(e){var t=this.searchWorldParams;e&&(t.offset+=t.n*e,t.offset<0&&(t.offset=0)),this.isSearchWorldLoading=!0,n.getWorlds(t,this.searchWorldOption).finally(()=>{this.isSearchWorldLoading=!1}).then(e=>{var t=new Map;for(var r of e.json){var i=n.cachedWorlds.get(r.id);void 0!==i&&t.set(i.id,i)}return this.searchWorldResults=Array.from(t.values()),e})},f.methods.searchAvatar=function(e){var t={n:10,offset:0};switch(e){case"updated":t.sort="updated";break;case"created":t.sort="created";break;case"mine":t.user="me",t.releaseStatus="all";break;default:t.sort="popularity",t.search=this.searchText}t.order="descending",this.searchAvatarParams=t,this.moreSearchAvatar()},f.methods.moreSearchAvatar=function(e){var t=this.searchAvatarParams;e&&(t.offset+=t.n*e,t.offset<0&&(t.offset=0)),this.isSearchAvatarLoading=!0,n.getAvatars(t).finally(()=>{this.isSearchAvatarLoading=!1}).then(e=>{var t=new Map;for(var r of e.json){var i=n.cachedAvatars.get(r.id);void 0!==i&&t.set(i.id,i)}return this.searchAvatarResults=Array.from(t.values()),e})},f.data.favoriteObjects=new Map,f.data.favoriteFriends_=[],f.data.favoriteWorlds_=[],f.data.favoriteAvatars_=[],f.data.sortFavoriteFriends=!1,f.data.sortFavoriteWorlds=!1,f.data.sortFavoriteAvatars=!1,n.$on("LOGIN",(function(){f.favoriteObjects.clear(),f.favoriteFriends_=[],f.favoriteWorlds_=[],f.favoriteAvatars_=[],f.sortFavoriteFriends=!1,f.sortFavoriteWorlds=!1,f.sortFavoriteAvatars=!1})),n.$on("FAVORITE",(function(e){f.applyFavorite(e.ref.type,e.ref.favoriteId)})),n.$on("FAVORITE:@DELETE",(function(e){f.applyFavorite(e.ref.type,e.ref.favoriteId)})),n.$on("USER",(function(e){f.applyFavorite("friend",e.ref.id)})),n.$on("WORLD",(function(e){f.applyFavorite("world",e.ref.id)})),n.$on("AVATAR",(function(e){f.applyFavorite("avatar",e.ref.id)})),f.methods.applyFavorite=function(t,r){var i=n.cachedFavoritesByObjectId.get(r),a=this.favoriteObjects.get(r);if(void 0!==i){var s=!1;if(void 0===a){if(a={id:r,type:t,groupKey:i.$groupKey,ref:null,name:""},this.favoriteObjects.set(r,a),"friend"===t)void 0===(o=n.cachedUsers.get(r))?void 0!==(o=this.friendLog[r])&&o.displayName&&(a.name=o.displayName):(a.ref=o,a.name=o.displayName);else if("world"===t){void 0!==(o=n.cachedWorlds.get(r))&&(a.ref=o,a.name=o.name)}else if("avatar"===t){void 0!==(o=n.cachedAvatars.get(r))&&(a.ref=o,a.name=o.name)}s=!0}else{if(a.type!==t&&(s=!0,"friend"===t?e(this.favoriteFriends_,a):"world"===t?e(this.favoriteWorlds_,a):"avatar"===t&&e(this.favoriteAvatars_,a)),"friend"===t)void 0!==(o=n.cachedUsers.get(r))&&(a.ref!==o&&(a.ref=o),a.name!==o.displayName&&(a.name=o.displayName,this.sortFavoriteFriends=!0));else if("world"===t){void 0!==(o=n.cachedWorlds.get(r))&&(a.ref!==o&&(a.ref=o),a.name!==o.name&&(a.name=o.name,this.sortFavoriteWorlds=!0))}else if("avatar"===t){var o;void 0!==(o=n.cachedAvatars.get(r))&&(a.ref!==o&&(a.ref=o),a.name!==o.name&&(a.name=o.name,this.sortFavoriteAvatars=!0))}}s&&("friend"===t?(this.favoriteFriends_.push(a),this.sortFavoriteFriends=!0):"world"===t?(this.favoriteWorlds_.push(a),this.sortFavoriteWorlds=!0):"avatar"===t&&(this.favoriteAvatars_.push(a),this.sortFavoriteAvatars=!0))}else void 0!==a&&(this.favoriteObjects.delete(r),"friend"===t?e(this.favoriteFriends_,a):"world"===t?e(this.favoriteWorlds_,a):"avatar"===t&&e(this.favoriteAvatars_,a))},f.methods.deleteFavorite=function(e){this.$confirm("Continue? Delete Favorite","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:t=>{"confirm"===t&&n.deleteFavorite({objectId:e})}})},f.methods.changeFavoriteGroupName=function(e){this.$prompt("Enter a new name","Change Group Name",{distinguishCancelAndClose:!0,cancelButtonText:"Cancel",confirmButtonText:"Change",inputPlaceholder:"Name",inputValue:e.displayName,inputPattern:/\S+/,inputErrorMessage:"Name is required",callback:(t,r)=>{"confirm"===t&&n.saveFavoriteGroup({type:e.type,group:e.name,displayName:r.inputValue}).then(e=>(this.$message("Group updated!"),e))}})},f.methods.clearFavoriteGroup=function(e){this.$confirm("Continue? Clear Group","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:t=>{"confirm"===t&&n.clearFavoriteGroup({type:e.type,group:e.name})}})},f.computed.favoriteFriends=function(){return this.sortFavoriteFriends&&(this.sortFavoriteFriends=!1,this.favoriteFriends_.sort(v)),this.favoriteFriends_},f.computed.favoriteWorlds=function(){return this.sortFavoriteWorlds&&(this.sortFavoriteWorlds=!1,this.favoriteWorlds_.sort(v)),this.favoriteWorlds_},f.computed.favoriteAvatars=function(){return this.sortFavoriteAvatars&&(this.sortFavoriteAvatars=!1,this.favoriteAvatars_.sort(v)),this.favoriteAvatars_},f.data.friendLog={},f.data.friendLogTable={data:[],filters:[{prop:"type",value:[],filterFn:(e,t)=>t.value.some(t=>t===e.type)},{prop:"displayName",value:""}],tableProps:{stripe:!0,size:"mini",defaultSort:{prop:"created_at",order:"descending"}},pageSize:10,paginationProps:{small:!0,layout:"sizes,prev,pager,next,total",pageSizes:[10,25,50,100]}},n.$on("LOGIN",(function(e){f.initFriendship(e.ref)})),n.$on("USER:CURRENT",(function(e){f.updateFriendships(e.ref)})),n.$on("USER",(function(e){f.updateFriendship(e.ref)})),n.$on("FRIEND:ADD",(function(e){f.addFriendship(e.params.userId)})),n.$on("FRIEND:DELETE",(function(e){f.deleteFriendship(e.params.userId)})),n.$on("FRIEND:REQUEST",(function(e){var t=this.cachedUsers.get(e.params.userId);void 0!==t&&(f.friendLogTable.data.push({created_at:(new Date).toJSON(),type:"FriendRequest",userId:t.id,displayName:t.displayName}),f.saveFriendLog())})),n.$on("FRIEND:REQUEST:CANCEL",(function(e){var t=this.cachedUsers.get(e.params.userId);void 0!==t&&(f.friendLogTable.data.push({created_at:(new Date).toJSON(),type:"CancelFriendRequst",userId:t.id,displayName:t.displayName}),f.saveFriendLog())}));var y=null;f.methods.saveFriendLog=function(){null===y&&(y=setTimeout(()=>{y=null,VRCXStorage.SetObject(`${n.currentUser.id}_friendLog`,this.friendLog),VRCXStorage.SetArray(`${n.currentUser.id}_friendLogTable`,this.friendLogTable.data),VRCXStorage.Set(`${n.currentUser.id}_friendLogUpdatedAt`,(new Date).toJSON())},1))},f.methods.initFriendship=function(e){if(VRCXStorage.Get(`${e.id}_friendLogUpdatedAt`))this.friendLog=VRCXStorage.GetObject(`${e.id}_friendLog`),this.friendLogTable.data=VRCXStorage.GetArray(`${e.id}_friendLogTable`);else{var t={};for(var r of e.friends){var i={id:r},a=n.cachedUsers.get(r);void 0!==a&&(i.displayName=a.displayName,i.trustLevel=a.$trustLevel),t[r]=i}this.friendLog=t,this.friendLogTable.data=[],this.saveFriendLog()}},f.methods.addFriendship=function(e){if(void 0===this.friendLog[e]){var t={id:e,displayName:null,trustLevel:null};Vue.set(this.friendLog,e,t);var r=n.cachedUsers.get(e);void 0!==r&&(t.displayName=r.displayName,t.trustLevel=r.$trustLevel,this.friendLogTable.data.push({created_at:(new Date).toJSON(),type:"Friend",userId:r.id,displayName:t.displayName})),this.saveFriendLog(),this.notifyMenu("friendLog")}},f.methods.deleteFriendship=function(e){var t=this.friendLog[e];void 0!==t&&(Vue.delete(this.friendLog,e),this.friendLogTable.data.push({created_at:(new Date).toJSON(),type:"Unfriend",userId:e,displayName:t.displayName}),this.saveFriendLog(),this.notifyMenu("friendLog"))},f.methods.updateFriendships=function(e){var t=new Set;for(var r of e.friends)t.add(r),this.addFriendship(r);for(var r in this.friendLog)!1===t.has(r)&&this.deleteFriendship(r)},f.methods.updateFriendship=function(e){var t=this.friendLog[e.id];void 0!==t&&(t.displayName!==e.displayName&&(t.displayName?this.friendLogTable.data.push({created_at:(new Date).toJSON(),type:"DisplayName",userId:e.id,displayName:e.displayName,previousDisplayName:t.displayName}):null===t.displayName&&this.friendLogTable.data.push({created_at:(new Date).toJSON(),type:"Friend",userId:e.id,displayName:e.displayName}),t.displayName=e.displayName,this.saveFriendLog(),this.notifyMenu("friendLog")),e.$trustLevel&&t.trustLevel!==e.$trustLevel&&(t.trustLevel&&this.friendLogTable.data.push({created_at:(new Date).toJSON(),type:"TrustLevel",userId:e.id,displayName:e.displayName,trustLevel:e.$trustLevel,previousTrustLevel:t.trustLevel}),t.trustLevel=e.$trustLevel,this.saveFriendLog(),this.notifyMenu("friendLog")))},f.methods.deleteFriendLog=function(t){this.$confirm("Continue? Delete Log","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:r=>{"confirm"===r&&e(this.friendLogTable.data,t)&&this.saveFriendLog()}})},f.data.playerModerationTable={data:[],filters:[{prop:"type",value:[],filterFn:(e,t)=>t.value.some(t=>t===e.type)},{prop:["sourceDisplayName","targetDisplayName"],value:""}],tableProps:{stripe:!0,size:"mini",defaultSort:{prop:"created",order:"descending"}},pageSize:10,paginationProps:{small:!0,layout:"sizes,prev,pager,next,total",pageSizes:[10,25,50,100]}},n.$on("LOGIN",(function(){f.playerModerationTable.data=[]})),n.$on("PLAYER-MODERATION",(function(e){for(var{ref:t}=e,r=f.playerModerationTable.data,{length:i}=r,a=0;a{"confirm"===t&&n.deletePlayerModeration({moderated:e.targetUserId,type:e.type})}})},f.data.notificationTable={data:[],filters:[{prop:"type",value:[],filterFn:(e,t)=>t.value.some(t=>t===e.type)},{prop:"senderUsername",value:""}],tableProps:{stripe:!0,size:"mini",defaultSort:{prop:"created_at",order:"descending"}},pageSize:10,paginationProps:{small:!0,layout:"sizes,prev,pager,next,total",pageSizes:[10,25,50,100]}},n.$on("LOGIN",(function(){f.notificationTable.data=[]})),n.$on("NOTIFICATION",(function(e){for(var{ref:t}=e,r=f.notificationTable.data,{length:i}=r,a=0;a{"confirm"===t&&n.acceptNotification({notificationId:e.id})}})},f.methods.hideNotification=function(e){this.$confirm("Continue? Delete Notification","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:t=>{"confirm"===t&&n.hideNotification({notificationId:e.id})}})},f.data.userLanguageVisible=0,f.data.userLanguageSelected="",f.data.userLanguages=function(){var e=[];for(var t in s){var r=s[t];e.push({key:t,value:r})}return e}(),f.data.currentUserTreeData=[],f.data.pastDisplayNameTable={data:[],tableProps:{stripe:!0,size:"mini",defaultSort:{prop:"updated_at",order:"descending"}},pageSize:10,paginationProps:{small:!0,layout:"sizes,prev,pager,next,total",pageSizes:[10,25,50,100]}},f.data.visits=0,f.data.openVR=VRCXStorage.GetBool("openVR"),f.data.openVRAlways=VRCXStorage.GetBool("openVRAlways");var T=function(){VRCXStorage.SetBool("openVR",this.openVR),VRCXStorage.SetBool("openVRAlways",this.openVRAlways)};f.watch.openVR=T,f.watch.openVRAlways=T,f.data.isDarkMode=VRCXStorage.GetBool("isDarkMode"),a.disabled=!1===f.data.isDarkMode,f.watch.isDarkMode=function(){VRCXStorage.SetBool("isDarkMode",this.isDarkMode),a.disabled=!1===this.isDarkMode},f.data.isStartAtWindowsStartup=VRCXStorage.GetBool("VRCX_StartAtWindowsStartup"),f.data.isStartAsMinimizedState=VRCXStorage.GetBool("VRCX_StartAsMinimizedState"),f.data.isCloseToTray=VRCXStorage.GetBool("VRCX_CloseToTray");var F=function(){VRCXStorage.SetBool("VRCX_StartAtWindowsStartup",this.isStartAtWindowsStartup),VRCXStorage.SetBool("VRCX_StartAsMinimizedState",this.isStartAsMinimizedState),VRCXStorage.SetBool("VRCX_CloseToTray",this.isCloseToTray),VRCX.SetStartup(this.isStartAtWindowsStartup)};f.watch.isStartAtWindowsStartup=F,f.watch.isStartAsMinimizedState=F,f.watch.isCloseToTray=F,n.$on("LOGIN",(function(){f.currentUserTreeData=[],f.pastDisplayNameTable.data=[]})),n.$on("USER:CURRENT",(function(e){e.ref.pastDisplayNames&&(f.pastDisplayNameTable.data=e.ref.pastDisplayNames)})),n.$on("VISITS",(function(e){f.visits=e.json})),f.methods.addUserLanguage=function(e){e===String(e)&&n.addUserTags({tags:[`language_${e}`]})},f.methods.removeUserLanguage=function(e){e===String(e)&&n.removeUserTags({tags:[`language_${e}`]})},f.methods.logout=function(){this.$confirm("Continue? Logout","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:e=>{"confirm"===e&&n.logout()}})},f.methods.resetHome=function(){this.$confirm("Continue? Reset Home","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:e=>{"confirm"===e&&n.saveCurrentUser({homeLocation:""}).then(e=>(this.$message({message:"Home world has been reset",type:"success"}),e))}})},f.methods.updateOpenVR=function(){this.openVR&&(this.isGameRunning||this.openVRAlways)?VRCX.StartVR():VRCX.StopVR()},f.methods.refreshCurrentUserTreeData=function(){this.currentUserTreeData=u(n.currentUser)},f.methods.promptUserDialog=function(){this.$prompt("Enter a User ID (UUID)","Direct Access",{distinguishCancelAndClose:!0,confirmButtonText:"OK",cancelButtonText:"Cancel",inputPattern:/\S+/,inputErrorMessage:"User ID is required",callback:(e,t)=>{"confirm"===e&&t.inputValue&&this.showUserDialog(t.inputValue)}})},f.methods.promptWorldDialog=function(){this.$prompt("Enter a World ID (UUID)","Direct Access",{distinguishCancelAndClose:!0,confirmButtonText:"OK",cancelButtonText:"Cancel",inputPattern:/\S+/,inputErrorMessage:"World ID is required",callback:(e,t)=>{"confirm"===e&&t.inputValue&&this.showWorldDialog(t.inputValue)}})},f.methods.promptAvatarDialog=function(){this.$prompt("Enter a Avatar ID (UUID)","Direct Access",{distinguishCancelAndClose:!0,confirmButtonText:"OK",cancelButtonText:"Cancel",inputPattern:/\S+/,inputErrorMessage:"Avatar ID is required",callback:(e,t)=>{"confirm"===e&&t.inputValue&&this.showAvatarDialog(t.inputValue)}})};var S=e=>{var t=0;document.querySelectorAll(".v-modal,.el-dialog__wrapper").forEach(r=>{var i=Number(r.style.zIndex)||0;i&&i>t&&r!==e&&(t=i)}),t&&(e.style.zIndex=t+1)};f.data.userDialog={visible:!1,loading:!1,id:"",ref:{},friend:{},isFriend:!1,incomingRequest:!1,outgoingRequest:!1,isBlock:!1,isMute:!1,isHideAvatar:!1,isFavorite:!1,$location:{},users:[],instance:{},worlds:[],avatars:[],isWorldsLoading:!1,isAvatarsLoading:!1,worldSorting:"update",avatarSorting:"update",treeData:[],memo:""},f.watch["userDialog.memo"]=function(){var e=this.userDialog;this.saveMemo(e.id,e.memo)},f.methods.getFaviconUrl=function(e){try{return`https://www.google.com/s2/favicons?domain=${new URL(e).origin}`}catch(e){return""}},n.$on("LOGOUT",(function(){f.userDialog.visible=!1})),n.$on("USER",(function(e){var{ref:t}=e,r=f.userDialog;!1!==r.visible&&r.id===t.id&&(r.ref=t,f.applyUserDialogLocation())})),n.$on("WORLD",(function(e){var t=f.userDialog;!1!==t.visible&&t.$location.worldId===e.ref.id&&f.applyUserDialogLocation()})),n.$on("FRIEND:STATUS",(function(e){var t=f.userDialog;if(!1!==t.visible&&t.id===e.params.userId){var{json:r}=e;t.isFriend=r.isFriend,t.incomingRequest=r.incomingRequest,t.outgoingRequest=r.outgoingRequest}})),n.$on("FRIEND:REQUEST",(function(e){var t=f.userDialog;!1!==t.visible&&t.id===e.params.userId&&(e.json.success?t.isFriend=!0:t.outgoingRequest=!0)})),n.$on("FRIEND:REQUEST:CANCEL",(function(e){var t=f.userDialog;!1!==t.visible&&t.id===e.params.userId&&(t.outgoingRequest=!1)})),n.$on("NOTIFICATION",(function(e){var{ref:t}=e,r=f.userDialog;!1===r.visible||t.$isDeleted||"friendRequest"!==t.type||t.senderUserId!==r.id||(r.incomingRequest=!0)})),n.$on("NOTIFICATION:ACCEPT",(function(e){var{ref:t}=e,r=f.userDialog;!1!==r.visible&&void 0!==t&&"friendRequest"===t.type&&t.senderUserId===r.id&&(r.isFriend=!0)})),n.$on("NOTIFICATION:@DELETE",(function(e){var{ref:t}=e,r=f.userDialog;!1!==r.visible&&"friendRequest"===t.type&&t.senderUserId===r.id&&(r.incomingRequest=!1)})),n.$on("FRIEND:DELETE",(function(e){var t=f.userDialog;!1!==t.visible&&t.id===e.params.userId&&(t.isFriend=!1)})),n.$on("PLAYER-MODERATION",(function(e){var{ref:t}=e,r=f.userDialog;!1===r.visible||t.$isDeleted||t.targetUserId!==r.id&&t.sourceUserId!==this.currentUser.id||("block"===t.type?r.isBlock=!0:"mute"===t.type?r.isMute=!0:"hideAvatar"===t.type&&(r.isHideAvatar=!0))})),n.$on("PLAYER-MODERATION:@DELETE",(function(e){var{ref:t}=e,r=f.userDialog;!1!==r.visible&&t.targetUserId===r.id&&t.sourceUserId===this.currentUser.id&&("block"===t.type?r.isBlock=!1:"mute"===t.type?r.isMute=!1:"hideAvatar"===t.type&&(r.isHideAvatar=!1))})),n.$on("FAVORITE",(function(e){var{ref:t}=e,r=f.userDialog;!1===r.visible||t.$isDeleted||t.favoriteId!==r.id||(r.isFavorite=!0)})),n.$on("FAVORITE:@DELETE",(function(e){var t=f.userDialog;!1!==t.visible&&t.id===e.ref.favoriteId&&(t.isFavorite=!1)})),f.methods.showUserDialog=function(e){this.$nextTick(()=>S(this.$refs.userDialog.$el));var t=this.userDialog;t.id=e,t.treeData=[],t.memo=this.loadMemo(e),t.visible=!0,t.loading=!0,n.getCachedUser({userId:e}).catch(e=>{throw t.loading=!1,t.visible=!1,e}).then(e=>{if(e.ref.id===t.id){for(var r of(t.loading=!1,t.ref=e.ref,t.friend=this.friends.get(t.id),t.isFriend=Boolean(t.friend),t.incomingRequest=!1,t.outgoingRequest=!1,t.isBlock=!1,t.isMute=!1,t.isHideAvatar=!1,n.cachedPlayerModerations.values()))!1===r.$isDeleted&&r.targetUserId===t.id&&r.sourceUserId===n.currentUser.id&&("block"===r.type?t.isBlock=!0:"mute"===r.type?t.isMute=!0:"hideAvatar"===r.type&&(t.isHideAvatar=!0));t.isFavorite=n.cachedFavoritesByObjectId.has(t.id),this.applyUserDialogLocation();var i=[];for(var r of n.cachedWorlds.values())r.authorId===t.id&&i.push(r);this.setUserDialogWorlds(i);var a=[];for(var r of n.cachedAvatars.values())r.authorId===t.id&&a.push(r);this.setUserDialogAvatars(a),t.avatars=a,t.isWorldsLoading=!1,t.isAvatarsLoading=!1,n.getFriendStatus({userId:t.id}),e.cache&&n.getUser(e.params)}return e})},f.methods.applyUserDialogLocation=function(){var e=this.userDialog,t=n.parseLocation(e.ref.location);(e.$location=t,t.userId)&&(void 0===(i=n.cachedUsers.get(t.userId))?n.getUser({userId:t.userId}).then(e=>(Vue.set(t,"user",e.ref),e)):t.user=i);var r=[];if(!1===t.isOffline)for(var{ref:i}of this.friends.values())void 0!==i&&i.location===t.tag&&r.push(i);if(this.isGameRunning&&this.lastLocation===t.tag&&r.push(n.currentUser),r.sort(g),e.users=r,e.instance={},t.worldId){var i,a=function(r){for(var[i,a]of r)if(i===t.instanceId){e.instance={id:i,occupants:a};break}};void 0===(i=n.cachedWorlds.get(t.worldId))?n.getWorld({worldId:t.worldId}).then(e=>(e.ref.id===t.worldId&&a(e.ref.instances),!0)):a(i.instances)}},f.methods.setUserDialogWorlds=function(e){var t=this.userDialog;"update"===t.worldSorting?e.sort(m):e.sort(v),t.worlds=e},f.methods.setUserDialogAvatars=function(e){var t=this.userDialog;"update"===t.avatarSorting?e.sort(m):e.sort(v),t.avatars=e},f.methods.refreshUserDialogWorlds=function(){var e=this.userDialog;if(!e.isWorldsLoading){e.isWorldsLoading=!0;var t={n:100,offset:0,sort:"updated",order:"descending",userId:e.id,releaseStatus:"public"};t.userId===n.currentUser.id&&(t.user="me",t.releaseStatus="all");var r=new Map;for(var i of n.cachedWorlds.values())i.authorId===e.id&&r.set(i.id,i);n.bulk({fn:"getWorlds",N:-1,params:t,handle:e=>{for(var t of e.json){var i=n.cachedWorlds.get(t.id);void 0!==i&&r.set(i.id,i)}},done:()=>{if(e.id===t.userId){var i=Array.from(r.values());this.setUserDialogWorlds(i)}e.isWorldsLoading=!1}})}},f.methods.refreshUserDialogAvatars=function(){var e=this.userDialog;if(!e.isAvatarsLoading){e.isAvatarsLoading=!0;var t={n:100,offset:0,sort:"updated",order:"descending",userId:e.id,releaseStatus:"public"};t.userId===n.currentUser.id&&(t.user="me",t.releaseStatus="all");var r=new Map;for(var i of n.cachedAvatars.values())i.authorId===e.id&&r.set(i.id,i);n.bulk({fn:"getAvatars",N:-1,params:t,handle:e=>{for(var t of e.json){var i=n.cachedAvatars.get(t.id);void 0!==i&&r.set(i.id,i)}},done:()=>{if(e.id===t.userId){var i=Array.from(r.values());this.setUserDialogAvatars(i)}e.isAvatarsLoading=!1}})}};f.methods.userDialogCommand=function(e){var t=this.userDialog;if(!1!==t.visible)if("Add Favorite"===e)this.showFavoriteDialog("friend",t.id);else if("Message"===e)this.$prompt("Enter a message","Send Message",{distinguishCancelAndClose:!0,confirmButtonText:"Send",cancelButtonText:"Cancel",inputPattern:/\S+/,inputErrorMessage:"Message is required",callback:(e,r)=>{"confirm"===e&&r.inputValue&&n.sendNotification({receiverUserId:t.id,type:"message",message:r.inputValue,seen:!1,details:"{}"}).then(e=>(this.$message("Message sent"),e))}});else if("Show Avatar Author"===e){var{currentAvatarImageUrl:r}=t.ref,i=c(r);i?n.call(`file/${i}`).then(({ownerId:e})=>{for(var i of n.cachedAvatars.values())if(i.imageUrl===r)return void this.showAvatarDialog(i.id);t.loading=!0;var a={n:100,offset:0,sort:"updated",order:"descending",userId:e,releaseStatus:"public"};a.userId===n.currentUser.id&&(a.user="me",a.releaseStatus="all"),n.bulk({fn:"getAvatars",N:-1,params:a,done:()=>{for(var i of(t.loading=!1,n.cachedAvatars.values()))if(i.imageUrl===r)return void this.showAvatarDialog(i.id);e!==t.id?this.showUserDialog(e):this.$message({message:"It's personal (own) avatar",type:"warning"})}})}):this.$message({message:"Sorry, the author is unknown",type:"error"})}else this.$confirm(`Continue? ${e}`,"Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:r=>{"confirm"===r&&((e,t)=>{switch(e){case"Delete Favorite":n.deleteFavorite({objectId:t});break;case"Accept Friend Request":""===(r=n.getFriendRequest(t))?n.sendFriendRequest({userId:t}):n.acceptNotification({notificationId:r});break;case"Decline Friend Request":var r;""===(r=n.getFriendRequest(t))?n.cancelFriendRequest({userId:t}):n.hideNotification({notificationId:r});break;case"Cancel Friend Request":n.cancelFriendRequest({userId:t});break;case"Send Friend Request":n.sendFriendRequest({userId:t});break;case"Unblock":n.deletePlayerModeration({moderated:t,type:"block"});break;case"Block":n.sendPlayerModeration({moderated:t,type:"block"});break;case"Unmute":n.deletePlayerModeration({moderated:t,type:"mute"});break;case"Mute":n.sendPlayerModeration({moderated:t,type:"mute"});break;case"Show Avatar":n.deletePlayerModeration({moderated:t,type:"hideAvatar"});break;case"Hide Avatar":n.sendPlayerModeration({moderated:t,type:"hideAvatar"});break;case"Unfriend":n.deleteFriend({userId:t})}})(e,t.id)}})},f.methods.refreshUserDialogTreeData=function(){var e=this.userDialog;e.treeData=u(e.ref)},f.methods.changeUserDialogWorldSorting=function(){var e=this.userDialog;this.setUserDialogWorlds(e.worlds)},f.methods.changeUserDialogAvatarSorting=function(){var e=this.userDialog;this.setUserDialogAvatars(e.avatars)},f.data.worldDialog={visible:!1,loading:!1,id:"",$location:{},ref:{},isFavorite:!1,rooms:[],treeData:[],fileCreatedAt:"",fileSize:""},n.$on("LOGOUT",(function(){f.worldDialog.visible=!1})),n.$on("WORLD",(function(e){var{ref:t}=e,r=f.worldDialog;if(!1!==r.visible&&r.id===t.id){if(r.ref=t,"Loading"===r.fileSize){var i=c(t.assetUrl);i&&this.call(`file/${i}`).then((function(e){var t=e.versions[e.versions.length-1];r.fileCreatedAt=t.created_at,r.fileSize=`${(t.file.sizeInBytes/1048576).toFixed(2)} MiB`}))}f.applyWorldDialogInstances()}})),n.$on("FAVORITE",(function(e){var{ref:t}=e,r=f.worldDialog;!1===r.visible||t.$isDeleted||t.favoriteId!==r.id||(r.isFavorite=!0)})),n.$on("FAVORITE:@DELETE",(function(e){var t=f.worldDialog;!1!==t.visible&&t.id===e.ref.favoriteId&&(t.isFavorite=!1)})),f.methods.showWorldDialog=function(e){this.$nextTick(()=>S(this.$refs.worldDialog.$el));var t=this.worldDialog,r=n.parseLocation(e);""!==r.worldId&&(t.id=r.worldId,t.$location=r,t.treeData=[],t.fileCreatedAt="",t.fileSize="Loading",t.visible=!0,t.loading=!0,n.getCachedWorld({worldId:r.worldId}).catch(e=>{throw t.loading=!1,t.visible=!1,e}).then(e=>(t.id===e.ref.id&&(t.loading=!1,t.ref=e.ref,t.isFavorite=n.cachedFavoritesByObjectId.has(t.id),t.rooms=[],this.applyWorldDialogInstances(),e.cache&&n.getWorld(e.params)),e)))},f.methods.applyWorldDialogInstances=function(){var e=this.worldDialog,t={};for(var[r,i]of e.ref.instances)t[r]={id:r,occupants:i,users:[]};var{instanceId:a}=e.$location;for(var{ref:s}of(a&&void 0===t[a]&&(t[a]={id:a,occupants:0,users:[]}),this.friends.values()))if(void 0!==s&&void 0!==s.$location&&s.$location.worldId===e.id){var{instanceId:a}=s.$location;void 0===(l=t[a])&&(l={id:a,occupants:0,users:[]},t[a]=l),l.users.push(s)}if(this.isGameRunning){var o=n.parseLocation(this.lastLocation);if(o.worldId===e.id)void 0===(l=t[o.instanceId])&&(t[(l={id:o.instanceId,occupants:1,users:[]}).id]=l),l.users.push(n.currentUser)}var d=[];for(var l of Object.values(t)){const t=n.parseLocation(`${e.id}:${l.id}`);var s;if(l.location=t.tag,l.$location=t,t.userId)void 0===(s=n.cachedUsers.get(t.userId))?n.getUser({userId:t.userId}).then(e=>(Vue.set(t,"user",e.ref),e)):t.user=s;l.users.sort(g),d.push(l)}d.sort((function(e,t){return t.users.length-e.users.length||t.occupants-e.occupants})),e.rooms=d},f.methods.worldDialogCommand=function(e){var t=this.worldDialog;!1!==t.visible&&("New Instance"===e?this.showNewInstanceDialog(t.$location.tag):"Add Favorite"===e?this.showFavoriteDialog("world",t.id):this.$confirm(`Continue? ${e}`,"Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:r=>{if("confirm"===r)switch(e){case"Delete Favorite":n.deleteFavorite({objectId:t.id});break;case"Make Home":n.saveCurrentUser({homeLocation:t.id}).then(e=>(this.$message({message:"Home world updated",type:"success"}),e));break;case"Reset Home":n.saveCurrentUser({homeLocation:""}).then(e=>(this.$message({message:"Home world has been reset",type:"success"}),e))}}}))},f.methods.refreshWorldDialogTreeData=function(){var e=this.worldDialog;e.treeData=u(e.ref)},f.computed.worldDialogPlatform=function(){var{ref:e}=this.worldDialog,t=[];if(e.unityPackages)for(var r of e.unityPackages){var i="PC";"standalonewindows"===r.platform?i="PC":"android"===r.platform?i="Quest":r.platform&&({platform:i}=r),t.push(`${i}/${r.unityVersion}`)}return t.join(", ")},f.data.avatarDialog={visible:!1,loading:!1,id:"",ref:{},isFavorite:!1,treeData:[],fileCreatedAt:"",fileSize:""},n.$on("LOGOUT",(function(){f.avatarDialog.visible=!1})),n.$on("AVATAR",(function(e){var t=f.avatarDialog;if(!1!==t.visible&&t.id===e.ref.id&&(t.ref=e.ref,"Loading"===t.fileSize)){var r=c(e.ref.assetUrl);r&&this.call(`file/${r}`).then(e=>{var r=e.versions[e.versions.length-1];t.fileCreatedAt=r.created_at,t.fileSize=`${(r.file.sizeInBytes/1048576).toFixed(2)} MiB`})}})),n.$on("FAVORITE",(function(e){var{ref:t}=e,r=f.avatarDialog;!1===r.visible||t.$isDeleted||t.favoriteId!==r.id||(r.isFavorite=!0)})),n.$on("FAVORITE:@DELETE",(function(e){var t=f.avatarDialog;!1!==t.visible&&t.id===e.ref.favoriteId&&(t.isFavorite=!1)})),f.methods.showAvatarDialog=function(e){this.$nextTick(()=>S(this.$refs.avatarDialog.$el));var t=this.avatarDialog;t.id=e,t.treeData=[],t.fileCreatedAt="",t.fileSize="Loading",t.visible=!0,t.loading=!0,n.getCachedAvatar({avatarId:e}).catch(e=>{throw t.loading=!1,t.visible=!1,e}).then(e=>(t.id===e.ref.id&&(t.loading=!1,t.ref=e.ref,t.isFavorite=n.cachedFavoritesByObjectId.has(t.ref.id),e.cache&&n.getAvatar(e.params)),e))},f.methods.avatarDialogCommand=function(e){var t=this.avatarDialog;!1!==t.visible&&("Add Favorite"===e?this.showFavoriteDialog("avatar",t.id):this.$confirm(`Continue? ${e}`,"Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:r=>{if("confirm"===r)switch(e){case"Delete Favorite":n.deleteFavorite({objectId:t.id});break;case"Select Avatar":n.selectAvatar({avatarId:t.id})}}}))},f.methods.refreshAvatarDialogTreeData=function(){var e=this.avatarDialog;e.treeData=u(e.ref)},f.computed.avatarDialogPlatform=function(){var{ref:e}=this.avatarDialog,t=[];if(e.unityPackages)for(var r of e.unityPackages){var i="PC";"standalonewindows"===r.platform?i="PC":"android"===r.platform?i="Quest":r.platform&&({platform:i}=r),t.push(`${i}/${r.unityVersion}`)}return t.join(", ")},f.data.favoriteDialog={visible:!1,loading:!1,type:"",objectId:"",groups:[]},n.$on("LOGOUT",(function(){f.favoriteDialog.visible=!1})),f.methods.addFavorite=function(e){var t=this.favoriteDialog;t.loading=!0,n.addFavorite({type:t.type,favoriteId:t.objectId,tags:e.name}).finally(()=>{t.loading=!1}).then(e=>(t.visible=!1,e))},f.methods.showFavoriteDialog=function(e,t){this.$nextTick(()=>S(this.$refs.favoriteDialog.$el));var r=this.favoriteDialog;r.type=e,r.objectId=t,"friend"===e?(r.groups=n.favoriteFriendGroups,r.visible=!0):"world"===e?(r.groups=n.favoriteWorldGroups,r.visible=!0):"avatar"===e&&(r.groups=n.favoriteAvatarGroups,r.visible=!0)},f.data.inviteDialog={visible:!1,loading:!1,worldId:"",worldName:"",userIds:[]},n.$on("LOGOUT",(function(){f.inviteDialog.visible=!1})),f.methods.sendInvite=function(){this.$confirm("Continue? Invite","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:e=>{var t=this.inviteDialog;if("confirm"===e&&!0!==t.loading){t.loading=!0;var r={receiverUserId:"",type:"invite",message:"This is a generated invite",seen:!1,details:{worldId:t.worldId,worldName:t.worldName}},i=()=>{t.userIds.length>0?(r.receiverUserId=t.userIds.shift(),n.sendNotification(r).finally(i)):(t.loading=!1,t.visible=!1,this.$message({message:"Invite sent",type:"success"}))};i()}}})},f.methods.showInviteDialog=function(e){this.$nextTick(()=>S(this.$refs.inviteDialog.$el));var t=n.parseLocation(e);t.isOffline||t.isPrivate||""===t.worldId||n.getCachedWorld({worldId:t.worldId}).then(e=>{var r=this.inviteDialog;r.userIds=[],r.worldId=t.tag,r.worldName=e.ref.name,r.visible=!0})},f.data.socialStatusDialog={visible:!1,loading:!1,status:"",statusDescription:""},n.$on("LOGOUT",(function(){f.socialStatusDialog.visible=!1})),f.methods.saveSocialStatus=function(){var e=this.socialStatusDialog;e.loading||(e.loading=!0,n.saveCurrentUser({status:e.status,statusDescription:e.statusDescription}).finally(()=>{e.loading=!1}).then(t=>(e.visible=!1,this.$message({message:"Status updated",type:"success"}),t)))},f.methods.showSocialStatusDialog=function(){this.$nextTick(()=>S(this.$refs.socialStatusDialog.$el));var e=this.socialStatusDialog;e.status=n.currentUser.status,e.statusDescription=n.currentUser.statusDescription,e.visible=!0},f.data.bioDialog={visible:!1,loading:!1,bio:"",bioLinks:[]},n.$on("LOGOUT",(function(){f.bioDialog.visible=!1})),f.methods.saveBio=function(){var e=this.bioDialog;e.loading||(e.loading=!0,n.saveCurrentUser({bio:e.bio,bioLinks:e.bioLinks}).finally(()=>{e.loading=!1}).then(t=>(e.visible=!1,this.$message({message:"Bio updated",type:"success"}),t)))},f.methods.showBioDialog=function(){this.$nextTick(()=>S(this.$refs.bioDialog.$el));var e=this.bioDialog;e.bio=n.currentUser.bio,e.bioLinks=n.currentUser.bioLinks.slice(),e.visible=!0},f.data.newInstanceDialog={visible:!1,loading:!1,worldId:"",instanceId:"",accessType:"",location:"",url:""},n.$on("LOGOUT",(function(){f.newInstanceDialog.visible=!1})),f.methods.buildInstance=function(){var e=this.newInstanceDialog,t=[];t.push((99999*Math.random()+1).toFixed(0)),"public"!==e.accessType&&("friends+"===e.accessType?t.push(`~hidden(${n.currentUser.id})`):"friends"===e.accessType?t.push(`~friends(${n.currentUser.id})`):t.push(`~private(${n.currentUser.id})`),t.push(`~nonce(${"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{var t=16*Math.random()|0;return"x"!==e&&(t|=8),t.toString(16)})})`),"invite+"===e.accessType&&t.push("~canRequestInvite")),e.instanceId=t.join("")};var D=function(e,t){return t?`https://vrchat.net/launch?worldId=${encodeURIComponent(e)}&instanceId=${encodeURIComponent(t)}`:`https://vrchat.net/launch?worldId=${encodeURIComponent(e)}`},A=function(){var e=this.newInstanceDialog;e.instanceId?e.location=`${e.worldId}:${e.instanceId}`:e.location=e.worldId,e.url=D(e.worldId,e.instanceId)};f.watch["newInstanceDialog.worldId"]=A,f.watch["newInstanceDialog.instanceId"]=A,f.methods.showNewInstanceDialog=function(e){this.$nextTick(()=>S(this.$refs.newInstanceDialog.$el));var t=n.parseLocation(e);if(!t.isOffline&&!t.isPrivate&&""!==t.worldId){var r=this.newInstanceDialog;r.worldId=t.worldId,r.accessType="public",this.buildInstance(),r.visible=!0}},f.methods.makeHome=function(e){this.$confirm("Continue? Make Home","Confirm",{confirmButtonText:"Confirm",cancelButtonText:"Cancel",type:"info",callback:t=>{"confirm"===t&&n.saveCurrentUser({homeLocation:e}).then(e=>(this.$message({message:"Home world updated",type:"success"}),e))}})},f.data.launchDialog={visible:!1,loading:!1,desktop:VRCXStorage.GetBool("launchAsDesktop"),location:"",url:""},f.watch["launchDialog.desktop"]=function(){VRCXStorage.SetBool("launchAsDesktop",this.launchDialog.desktop)},n.$on("LOGOUT",(function(){f.launchDialog.visible=!1})),f.methods.showLaunchDialog=function(e){this.$nextTick(()=>S(this.$refs.launchDialog.$el));var t=n.parseLocation(e);if(!t.isOffline&&!t.isPrivate&&""!==t.worldId){var r=this.launchDialog;t.instanceId?r.location=`${t.worldId}:${t.instanceId}`:r.location=t.worldId,r.url=D(t.worldId,t.instanceId),r.visible=!0}},f.methods.launchGame=function(){var e=this.launchDialog;VRCX.StartGame(e.location,e.desktop),e.visible=!1},f=new Vue(f),window.$app=f}))},function(e,t,r){}]); \ No newline at end of file diff --git a/html/index.html b/html/index.html index 8119ecc8..1af1914f 100644 --- a/html/index.html +++ b/html/index.html @@ -1,1532 +1,6 @@ - - - - - - - -VRCX - - - - - - - - - - - - \ No newline at end of file diff --git a/html/src/app.js b/html/src/app.js index ec7c7796..b6b829c9 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -3147,7 +3147,7 @@ CefSharp.BindObjectAsync( VRCX, nextRefresh: 0, isGameRunning: false, - appVersion: 'VRCX 2020.03.21', + appVersion: 'VRCX 2020.04.07', latestAppVersion: '', ossDialog: false }, diff --git a/html/vr.css b/html/vr.css index dbc1dac9..48c88499 100644 --- a/html/vr.css +++ b/html/vr.css @@ -1,222 +1 @@ -@charset "utf-8"; -/* -Copyright(c) 2019 pypy. All rights reserved. - -This work is licensed under the terms of the MIT license. -For a copy, see . -*/ - -/* - 마지노선인듯 - 화면 24px -> 나나 32 - 손등 18px -> 나나 24 -*/ - -.noty_body { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.noty_layout { - max-width: none; - width: 80% !important; -} - -.noty_theme__relax.noty_bar { - margin: 4px 0; - overflow: hidden; - border-radius: 2px; - position: relative; -} - -.noty_theme__relax.noty_bar .noty_body { - padding: 5px 10px 10px; - font-size: 24px; - text-align: center; -} - -.noty_theme__relax.noty_bar .noty_buttons { - border-top: 1px solid #e7e7e7; - padding: 5px 10px; -} - -.noty_theme__relax.noty_type__alert, .noty_theme__relax.noty_type__notification { - background-color: #fff; - border: 1px solid #dedede; - color: #444; -} - -.noty_theme__relax.noty_type__warning { - background-color: #FFEAA8; - border: 1px solid #FFC237; - color: #826200; -} - -.noty_theme__relax.noty_type__warning .noty_buttons { - border-color: #dfaa30; -} - -.noty_theme__relax.noty_type__error { - background-color: #FF8181; - border: 1px solid #e25353; - color: #FFF; -} - -.noty_theme__relax.noty_type__error .noty_buttons { - border-color: darkred; -} - -.noty_theme__relax.noty_type__info, .noty_theme__relax.noty_type__information { - background-color: #78C5E7; - border: 1px solid #3badd6; - color: #FFF; -} - -.noty_theme__relax.noty_type__info .noty_buttons, .noty_theme__relax.noty_type__information .noty_buttons { - border-color: #0B90C4; -} - -.noty_theme__relax.noty_type__success { - background-color: #BCF5BC; - border: 1px solid #7cdd77; - color: darkgreen; -} - -.noty_theme__relax.noty_type__success .noty_buttons { - border-color: #50C24E; -} - -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: rgba(0, 0, 0, 0.1); - border-radius: 16px; -} - -::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.25); - border-radius: 16px; -} - -body, input, textarea, select, button { - font-family: 'Noto Sans JP', 'Noto Sans KR', 'Meiryo UI', 'Malgun Gothic', 'Segoe UI', sans-serif; - line-height: normal; -} - -.x-app { - display: flex; - flex-direction: column; - position: absolute; - width: 100%; - height: 100%; - overflow: hidden; -} - -.x-app-type { - background: #1f1f1f; - color: #fff; -} - -.x-container { - flex: none; - padding: 10px; - overflow: hidden auto; - position: relative; -} - -.x-friend-list { - overflow: hidden auto; - padding: 0 10px; -} - -.x-friend-item { - display: flex; - align-items: center; - font-size: 18px; - box-sizing: border-box; -} - -.x-friend-item .time { - margin-right: 5px; -} - -.x-friend-item .name { - font-weight: bold; -} - -.x-friend-item.friend .name { - color: #fff; -} - -.x-friend-item.favorite .name { - color: #ff0; -} - -.x-friend-item>.avatar { - flex: none; - width: 40px; - height: 40px; - margin-right: 8px; - display: inline-block; - position: relative; -} - -.x-friend-item>img.avatar { - width: 50px; - height: 37.5px; - margin-left: 5px; - margin-right: 0; - border-radius: 2px; -} - -.x-friend-item>.avatar>img { - width: 100%; - height: 100%; - border-radius: 40%; - object-fit: cover; -} - -.x-friend-item>.detail { - flex: 1; - overflow: hidden; -} - -.x-friend-item>.detail>.name, .x-friend-item>.detail>.extra { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.x-friend-item>.detail>.name { - font-weight: bold; -} - -.x-friend-item>.detail>.extra { - font-weight: normal; -} - -i.x-user-status { - display: inline-block; - width: 14px; - height: 14px; - border-radius: 50%; - background: gray; -} - -i.x-user-status.active { - background: #67C23A; -} - -i.x-user-status.joinme { - background: #409EFF; -} - -i.x-user-status.busy { - background: #F56C6C; -} \ No newline at end of file +.noty_body{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.noty_layout{max-width:none;width:80% !important}.noty_theme__relax.noty_bar{margin:4px 0;overflow:hidden;border-radius:2px;position:relative}.noty_theme__relax.noty_bar .noty_body{padding:5px 10px 10px;font-size:24px;text-align:center}.noty_theme__relax.noty_bar .noty_buttons{border-top:1px solid #e7e7e7;padding:5px 10px}.noty_theme__relax.noty_type__alert,.noty_theme__relax.noty_type__notification{background-color:#fff;border:1px solid #dedede;color:#444}.noty_theme__relax.noty_type__warning{background-color:#ffeaa8;border:1px solid #ffc237;color:#826200}.noty_theme__relax.noty_type__warning .noty_buttons{border-color:#dfaa30}.noty_theme__relax.noty_type__error{background-color:#ff8181;border:1px solid #e25353;color:#fff}.noty_theme__relax.noty_type__error .noty_buttons{border-color:darkred}.noty_theme__relax.noty_type__info,.noty_theme__relax.noty_type__information{background-color:#78c5e7;border:1px solid #3badd6;color:#fff}.noty_theme__relax.noty_type__info .noty_buttons,.noty_theme__relax.noty_type__information .noty_buttons{border-color:#0b90c4}.noty_theme__relax.noty_type__success{background-color:#bcf5bc;border:1px solid #7cdd77;color:#006400}.noty_theme__relax.noty_type__success .noty_buttons{border-color:#50c24e}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:rgba(0,0,0,.1);border-radius:16px}::-webkit-scrollbar-thumb{background:rgba(0,0,0,.25);border-radius:16px}body,input,textarea,select,button{font-family:"Noto Sans JP","Noto Sans KR","Meiryo UI","Malgun Gothic","Segoe UI",sans-serif;line-height:normal}.x-app{display:flex;flex-direction:column;position:absolute;width:100%;height:100%;overflow:hidden}.x-app-type{background:#1f1f1f;color:#fff}.x-container{flex:none;padding:10px;overflow:hidden auto;position:relative}.x-friend-list{overflow:hidden auto;padding:0 10px}.x-friend-item{display:flex;align-items:center;font-size:18px;box-sizing:border-box}.x-friend-item .time{margin-right:5px}.x-friend-item .name{font-weight:bold}.x-friend-item.friend .name{color:#fff}.x-friend-item.favorite .name{color:#ff0}.x-friend-item>.avatar{flex:none;width:40px;height:40px;margin-right:8px;display:inline-block;position:relative}.x-friend-item>img.avatar{width:50px;height:37.5px;margin-left:5px;margin-right:0;border-radius:2px}.x-friend-item>.avatar>img{width:100%;height:100%;border-radius:40%;object-fit:cover}.x-friend-item>.detail{flex:1;overflow:hidden}.x-friend-item>.detail>.name,.x-friend-item>.detail>.extra{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.x-friend-item>.detail>.name{font-weight:bold}.x-friend-item>.detail>.extra{font-weight:normal}i.x-user-status{display:inline-block;width:14px;height:14px;border-radius:50%;background:gray}i.x-user-status.active{background:#67c23a}i.x-user-status.joinme{background:#409eff}i.x-user-status.busy{background:#f56c6c} diff --git a/html/vr.js b/html/vr.js index df6aeea5..c802bd5e 100644 --- a/html/vr.js +++ b/html/vr.js @@ -1,763 +1 @@ -// Copyright(c) 2019-2020 pypy and individual contributors. -// All rights reserved. -// -// This work is licensed under the terms of the MIT license. -// For a copy, see . - -CefSharp.BindObjectAsync( - 'VRCX', - 'VRCXStorage', - 'SQLite' -).then(function () { - 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 array = JSON.parse(this.Get(key)); - 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 object = JSON.parse(this.Get(key)); - if (object === Object(object)) { - return object; - } - } catch (err) { - console.error(err); - } - return {}; - }; - - VRCXStorage.SetObject = function (key, value) { - this.Set(key, JSON.stringify(value)); - }; - - Noty.overrideDefaults({ - animation: { - open: 'animated fadeIn', - close: 'animated zoomOut' - }, - layout: 'topCenter', - theme: 'relax', - timeout: 6000 - }); - - 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 dt = new Date(s); - if (isNaN(dt)) { - return escapeTag(s); - } - var hours = dt.getHours(); - var map = { - 'YYYY': String(10000 + dt.getFullYear()).substr(-4), - 'MM': String(101 + dt.getMonth()).substr(-2), - 'DD': String(100 + dt.getDate()).substr(-2), - 'HH24': String(100 + hours).substr(-2), - 'HH': String(100 + (hours > 12 - ? hours - 12 - : hours)).substr(-2), - 'MI': String(100 + dt.getMinutes()).substr(-2), - 'SS': String(100 + dt.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); - - ELEMENT.locale(ELEMENT.lang.en); - - // - // API - // - - var API = {}; - - API.eventHandlers = new Map(); - - API.$emit = function (name, ...args) { - // console.log(name, ...args); - var handlers = this.eventHandlers.get(name); - if (handlers === undefined) { - return; - } - try { - for (var handler of handlers) { - handler.apply(this, args); - } - } catch (err) { - console.error(err); - } - }; - - API.$on = function (name, handler) { - var handlers = this.eventHandlers.get(name); - if (handlers === undefined) { - handlers = []; - this.eventHandlers.set(name, handlers); - } - handlers.push(handler); - }; - - API.$off = function (name, handler) { - var handlers = this.eventHandlers.get(name); - if (handlers === undefined) { - return; - } - var { length } = handlers; - for (var i = 0; i < length; ++i) { - if (handlers[i] === handler) { - if (length > 1) { - handlers.splice(i, 1); - } else { - this.eventHandlers.delete(name); - } - break; - } - } - }; - - API.pendingGetRequests = new Map(); - - API.call = function (endpoint, options) { - var resource = `https://api.vrchat.cloud/api/1/${endpoint}`; - var init = { - method: 'GET', - mode: 'cors', - credentials: 'include', - cache: 'no-cache', - redirect: 'follow', - referrerPolicy: 'no-referrer', - ...options - }; - var { params } = init; - var isGetRequest = init.method === 'GET'; - if (isGetRequest) { - // transform body to url - if (params === Object(params)) { - var url = new URL(resource); - var { searchParams } = url; - for (var key in params) { - searchParams.set(key, params[key]); - } - resource = url.toString(); - } - // merge requests - var req = this.pendingGetRequests.get(resource); - if (req !== undefined) { - return req; - } - } else { - init.headers = { - 'Content-Type': 'application/json;charset=utf-8', - ...init.headers - }; - init.body = params === Object(params) - ? JSON.stringify(params) - : '{}'; - } - var req = fetch(resource, init).catch((err) => { - this.$throw(0, err); - }).then((res) => res.json().catch(() => { - if (res.ok) { - this.$throw(0, 'Invalid JSON response'); - } - this.$throw(res.status); - }).then((json) => { - if (res.ok) { - if (json.success === Object(json.success)) { - new Noty({ - type: 'success', - text: escapeTag(json.success.message) - }).show(); - } - return json; - } - if (json === Object(json)) { - if (json.error === Object(json.error)) { - 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 - ); - } - } - this.$throw(res.status, json); - return json; - })); - if (isGetRequest) { - req.finally(() => { - this.pendingGetRequests.delete(resource); - }); - this.pendingGetRequests.set(resource, req); - } - return req; - }; - - API.statusCodes = { - 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) { - var text = []; - if (code > 0) { - var status = this.statusCodes[code]; - if (status === undefined) { - text.push(`${code}`); - } else { - text.push(`${code} ${status}`); - } - } - if (error !== undefined) { - text.push(JSON.stringify(error)); - } - text = text.map((s) => escapeTag(s)).join('
'); - if (text.length) { - new Noty({ - type: 'error', - text - }).show(); - } - throw new Error(text); - }; - - // API: Config - - API.cachedConfig = {}; - - API.$on('CONFIG', function (args) { - args.ref = this.applyConfig(args.json); - }); - - API.applyConfig = function (json) { - var ref = { - clientApiKey: '', - ...json - }; - this.cachedConfig = ref; - return ref; - }; - - API.getConfig = function () { - return this.call('config', { - method: 'GET' - }).then((json) => { - var args = { - ref: null, - json - }; - this.$emit('CONFIG', args); - return args; - }); - }; - - // API: Location - - API.parseLocation = function (tag) { - tag = String(tag || ''); - var ctx = { - tag, - isOffline: false, - isPrivate: false, - worldId: '', - instanceId: '', - instanceName: '', - accessType: '', - userId: null, - hiddenId: null, - privateId: null, - friendsId: null, - canRequestInvite: false - }; - if (tag === 'offline') { - ctx.isOffline = true; - } else if (tag === 'private') { - ctx.isPrivate = true; - } else if (tag.startsWith('local') === false) { - var sep = tag.indexOf(':'); - if (sep >= 0) { - ctx.worldId = tag.substr(0, sep); - ctx.instanceId = tag.substr(sep + 1); - ctx.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') { - ctx.hiddenId = value; - } else if (key === 'private') { - ctx.privateId = value; - } else if (key === 'friends') { - ctx.friendsId = value; - } else if (key === 'canRequestInvite') { - ctx.canRequestInvite = true; - } - } else { - ctx.instanceName = s; - } - }); - ctx.accessType = 'public'; - if (ctx.privateId !== null) { - if (ctx.canRequestInvite) { - // InvitePlus - ctx.accessType = 'invite+'; - } else { - // InviteOnly - ctx.accessType = 'invite'; - } - ctx.userId = ctx.privateId; - } else if (ctx.friendsId !== null) { - // FriendsOnly - ctx.accessType = 'friends'; - ctx.userId = ctx.friendsId; - } else if (ctx.hiddenId !== null) { - // FriendsOfGuests - ctx.accessType = 'friends+'; - ctx.userId = ctx.hiddenId; - } - } else { - ctx.worldId = tag; - } - } - return ctx; - }; - - Vue.component('location', { - template: '{{ text }}', - props: { - location: String - }, - 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.cachedWorlds.get(L.worldId); - if (ref === undefined) { - API.getWorld({ - worldId: L.worldId - }).then((args) => { - if (L.tag === this.location) { - if (L.instanceId) { - this.text = `${args.json.name} #${L.instanceName} ${L.accessType}`; - } else { - this.text = args.json.name; - } - } - return args; - }); - } else if (L.instanceId) { - this.text = `${ref.name} #${L.instanceName} ${L.accessType}`; - } else { - this.text = ref.name; - } - } - } - }, - watch: { - location() { - this.parse(); - } - }, - created() { - this.parse(); - } - }); - - // API: World - - API.cachedWorlds = new Map(); - - API.$on('WORLD', function (args) { - args.ref = this.applyWorld(args.json); - }); - - API.applyWorld = function (json) { - var ref = this.cachedWorlds.get(json.id); - if (ref === undefined) { - ref = { - id: '', - name: '', - description: '', - authorId: '', - authorName: '', - capacity: 0, - tags: [], - releaseStatus: '', - imageUrl: '', - thumbnailImageUrl: '', - assetUrl: '', - assetUrlObject: {}, - pluginUrl: '', - pluginUrlObject: {}, - unityPackageUrl: '', - unityPackageUrlObject: {}, - unityPackages: [], - version: 0, - favorites: 0, - created_at: '', - updated_at: '', - publicationDate: '', - labsPublicationDate: '', - visits: 0, - popularity: 0, - heat: 0, - publicOccupants: 0, - privateOccupants: 0, - occupants: 0, - instances: [], - // VRCX - $isLabs: false, - // - ...json - }; - this.cachedWorlds.set(ref.id, ref); - } else { - Object.assign(ref, json); - } - ref.$isLabs = ref.tags.includes('system_labs'); - return ref; - }; - - /* - params: { - worldId: string - } - */ - API.getWorld = function (params) { - return this.call(`worlds/${params.worldId}`, { - method: 'GET' - }).then((json) => { - var args = { - ref: null, - json, - params - }; - this.$emit('WORLD', args); - return args; - }); - }; - - var $app = { - data: { - API, - VRCX, - // 1 = 대시보드랑 손목에 보이는거 - // 2 = 항상 화면에 보이는 거 - appType: location.href.substr(-1), - currentTime: new Date().toJSON(), - cpuUsage: 0, - feeds: [], - devices: [] - }, - computed: {}, - methods: {}, - watch: {}, - el: '#x-app', - mounted() { - // https://media.discordapp.net/attachments/581757976625283083/611170278218924033/unknown.png - // 현재 날짜 시간 - // 컨트롤러 배터리 상황 - // -- - // OO is in Let's Just H!!!!! [GPS] - // OO has logged in [Online] -> TODO: location - // OO has logged out [Offline] -> TODO: location - // OO has joined [OnPlayerJoined] - // OO has left [OnPlayerLeft] - // [Moderation] - // OO has blocked you - // OO has muted you - // OO has hidden you - // -- - API.getConfig().catch((err) => { - // FIXME: 어케 복구하냐 이건 - throw err; - }).then((args) => { - setInterval(() => this.update(), 1000); - this.update(); - this.$nextTick(function () { - if (this.appType === '1') { - this.$el.style.display = ''; - } - }); - return args; - }); - } - }; - - $app.methods.update = function () { - this.currentTime = new Date().toJSON(); - this.currentUser = VRCXStorage.GetObject('currentUser') || {}; - VRCX.CpuUsage().then((cpuUsage) => { - this.cpuUsage = cpuUsage.toFixed(2); - }); - VRCX.GetVRDevices().then((devices) => { - devices.forEach((device) => { - device[2] = parseInt(device[2], 10); - }); - this.devices = devices; - }); - this.updateSharedFeed(); - }; - - $app.methods.updateSharedFeed = function () { - // TODO: block mute hideAvatar unfriend - var _feeds = this.feeds; - this.feeds = VRCXStorage.GetArray('sharedFeeds'); - if (this.appType === '2') { - var map = {}; - _feeds.forEach((feed) => { - if (feed.isFavorite) { - if (feed.type === 'OnPlayerJoined' || - feed.type === 'OnPlayerLeft') { - if (!map[feed.data] || - map[feed.data] < feed.created_at) { - map[feed.data] = feed.created_at; - } - } else if (feed.type === 'Online' || - feed.type === 'Offline') { - if (!map[feed.displayName] || - map[feed.displayName] < feed.created_at) { - map[feed.displayName] = feed.created_at; - } - } - } - }); - // disable notification on busy - if (this.currentUser.status === 'busy') { - return; - } - var notys = []; - this.feeds.forEach((feed) => { - if (feed.isFavorite) { - if (feed.type === 'Online' || - feed.type === 'Offline') { - if (!map[feed.displayName] || - map[feed.displayName] < feed.created_at) { - map[feed.displayName] = feed.created_at; - notys.push(feed); - } - } else if (feed.type === 'OnPlayerJoined' || - feed.type === 'OnPlayerLeft') { - if (!map[feed.data] || - map[feed.data] < feed.created_at) { - map[feed.data] = feed.created_at; - notys.push(feed); - } - } - } - }); - var bias = new Date(Date.now() - 60000).toJSON(); - notys.forEach((noty) => { - if (noty.created_at > bias) { - if (noty.type === 'OnPlayerJoined') { - new Noty({ - type: 'alert', - text: `${noty.data} has joined` - }).show(); - } else if (noty.type === 'OnPlayerLeft') { - new Noty({ - type: 'alert', - text: `${noty.data} has left` - }).show(); - } else if (noty.type === 'Online') { - new Noty({ - type: 'alert', - text: `${noty.displayName} has logged in` - }).show(); - } else if (noty.type === 'Offline') { - new Noty({ - type: 'alert', - text: `${noty.displayName} has logged out` - }).show(); - } - } - }); - } - }; - - $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 = new Vue($app); - window.$app = $app; -}); +!function(e){var t={};function r(n){if(t[n])return t[n].exports;var a=t[n]={i:n,l:!1,exports:{}};return e[n].call(a.exports,a,a.exports,r),a.l=!0,a.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)r.d(n,a,function(t){return e[t]}.bind(null,a));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}([,,,,function(e,t,r){r(5),e.exports=r(6)},function(e,t){CefSharp.BindObjectAsync("VRCX","VRCXStorage","SQLite").then((function(){VRCXStorage.GetBool=function(e){return"true"===this.Get(e)},VRCXStorage.SetBool=function(e,t){this.Set(e,t?"true":"false")},VRCXStorage.GetInt=function(e){return parseInt(this.Get(e),10)||0},VRCXStorage.SetInt=function(e,t){this.Set(e,String(t))},VRCXStorage.GetFloat=function(e){return parseFloat(this.Get(e),10)||0},VRCXStorage.SetFloat=function(e,t){this.Set(e,String(t))},VRCXStorage.GetArray=function(e){try{var t=JSON.parse(this.Get(e));if(Array.isArray(t))return t}catch(e){console.error(e)}return[]},VRCXStorage.SetArray=function(e,t){this.Set(e,JSON.stringify(t))},VRCXStorage.GetObject=function(e){try{var t=JSON.parse(this.Get(e));if(t===Object(t))return t}catch(e){console.error(e)}return{}},VRCXStorage.SetObject=function(e,t){this.Set(e,JSON.stringify(t))},Noty.overrideDefaults({animation:{open:"animated fadeIn",close:"animated zoomOut"},layout:"topCenter",theme:"relax",timeout:6e3});var e=e=>String(e).replace(/["&'<>]/gu,e=>`&#${e.charCodeAt(0)};`);Vue.filter("escapeTag",e);Vue.filter("commaNumber",e=>String(Number(e)||0).replace(/(\d)(?=(\d{3})+(?!\d))/gu,"$1,"));Vue.filter("formatDate",(t,r)=>{var n=new Date(t);if(isNaN(n))return e(t);var a=n.getHours(),i={YYYY:String(1e4+n.getFullYear()).substr(-4),MM:String(101+n.getMonth()).substr(-2),DD:String(100+n.getDate()).substr(-2),HH24:String(100+a).substr(-2),HH:String(100+(a>12?a-12:a)).substr(-2),MI:String(100+n.getMinutes()).substr(-2),SS:String(100+n.getSeconds()).substr(-2),AMPM:a>=12?"PM":"AM"};return r.replace(/YYYY|MM|DD|HH24|HH|MI|SS|AMPM/gu,e=>i[e]||e)});Vue.filter("textToHex",e=>String(e).split("").map(e=>e.charCodeAt(0).toString(16)).join(" "));Vue.filter("timeToText",t=>{var r=Number(t);if(isNaN(r))return e(t);var n=[];return(r=Math.floor(r/1e3))<0&&(r=-r),r>=86400&&(n.push(`${Math.floor(r/86400)}d`),r%=86400),r>=3600&&(n.push(`${Math.floor(r/3600)}h`),r%=3600),r>=60&&(n.push(`${Math.floor(r/60)}m`),r%=60),!r&&n.length||n.push(`${r}s`),n.join(" ")}),ELEMENT.locale(ELEMENT.lang.en);var t={};t.eventHandlers=new Map,t.$emit=function(e,...t){var r=this.eventHandlers.get(e);if(void 0!==r)try{for(var n of r)n.apply(this,t)}catch(e){console.error(e)}},t.$on=function(e,t){var r=this.eventHandlers.get(e);void 0===r&&(r=[],this.eventHandlers.set(e,r)),r.push(t)},t.$off=function(e,t){var r=this.eventHandlers.get(e);if(void 0!==r)for(var{length:n}=r,a=0;a1?r.splice(a,1):this.eventHandlers.delete(e);break}},t.pendingGetRequests=new Map,t.call=function(t,r){var n=`https://api.vrchat.cloud/api/1/${t}`,a={method:"GET",mode:"cors",credentials:"include",cache:"no-cache",redirect:"follow",referrerPolicy:"no-referrer",...r},{params:i}=a,s="GET"===a.method;if(s){if(i===Object(i)){var o=new URL(n),{searchParams:d}=o;for(var c in i)d.set(c,i[c]);n=o.toString()}if(void 0!==(u=this.pendingGetRequests.get(n)))return u}else a.headers={"Content-Type":"application/json;charset=utf-8",...a.headers},a.body=i===Object(i)?JSON.stringify(i):"{}";var u=fetch(n,a).catch(e=>{this.$throw(0,e)}).then(t=>t.json().catch(()=>{t.ok&&this.$throw(0,"Invalid JSON response"),this.$throw(t.status)}).then(r=>t.ok?(r.success===Object(r.success)&&new Noty({type:"success",text:e(r.success.message)}).show(),r):(r===Object(r)&&(r.error===Object(r.error)?this.$throw(r.error.status_code||t.status,r.error.message,r.error.data):"string"==typeof r.error&&this.$throw(r.status_code||t.status,r.error)),this.$throw(t.status,r),r)));return s&&(u.finally(()=>{this.pendingGetRequests.delete(n)}),this.pendingGetRequests.set(n,u)),u},t.statusCodes={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",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"},t.$throw=function(t,r){var n=[];if(t>0){var a=this.statusCodes[t];void 0===a?n.push(`${t}`):n.push(`${t} ${a}`)}throw void 0!==r&&n.push(JSON.stringify(r)),(n=n.map(t=>e(t)).join("
")).length&&new Noty({type:"error",text:n}).show(),new Error(n)},t.cachedConfig={},t.$on("CONFIG",(function(e){e.ref=this.applyConfig(e.json)})),t.applyConfig=function(e){var t={clientApiKey:"",...e};return this.cachedConfig=t,t},t.getConfig=function(){return this.call("config",{method:"GET"}).then(e=>{var t={ref:null,json:e};return this.$emit("CONFIG",t),t})},t.parseLocation=function(e){var t={tag:e=String(e||""),isOffline:!1,isPrivate:!1,worldId:"",instanceId:"",instanceName:"",accessType:"",userId:null,hiddenId:null,privateId:null,friendsId:null,canRequestInvite:!1};if("offline"===e)t.isOffline=!0;else if("private"===e)t.isPrivate=!0;else if(!1===e.startsWith("local")){var r=e.indexOf(":");r>=0?(t.worldId=e.substr(0,r),t.instanceId=e.substr(r+1),t.instanceId.split("~").forEach((e,r)=>{if(r){var n=e.indexOf("("),a=n>=0?e.lastIndexOf(")"):-1,i=a>=0?e.substr(0,n):e,s=n{{ text }}",props:{location:String},data(){return{text:this.location}},methods:{parse(){var e=t.parseLocation(this.location);if(e.isOffline)this.text="Offline";else if(e.isPrivate)this.text="Private";else if(e.worldId){var r=t.cachedWorlds.get(e.worldId);void 0===r?t.getWorld({worldId:e.worldId}).then(t=>(e.tag===this.location&&(e.instanceId?this.text=`${t.json.name} #${e.instanceName} ${e.accessType}`:this.text=t.json.name),t)):e.instanceId?this.text=`${r.name} #${e.instanceName} ${e.accessType}`:this.text=r.name}}},watch:{location(){this.parse()}},created(){this.parse()}}),t.cachedWorlds=new Map,t.$on("WORLD",(function(e){e.ref=this.applyWorld(e.json)})),t.applyWorld=function(e){var t=this.cachedWorlds.get(e.id);return void 0===t?(t={id:"",name:"",description:"",authorId:"",authorName:"",capacity:0,tags:[],releaseStatus:"",imageUrl:"",thumbnailImageUrl:"",assetUrl:"",assetUrlObject:{},pluginUrl:"",pluginUrlObject:{},unityPackageUrl:"",unityPackageUrlObject:{},unityPackages:[],version:0,favorites:0,created_at:"",updated_at:"",publicationDate:"",labsPublicationDate:"",visits:0,popularity:0,heat:0,publicOccupants:0,privateOccupants:0,occupants:0,instances:[],$isLabs:!1,...e},this.cachedWorlds.set(t.id,t)):Object.assign(t,e),t.$isLabs=t.tags.includes("system_labs"),t},t.getWorld=function(e){return this.call(`worlds/${e.worldId}`,{method:"GET"}).then(t=>{var r={ref:null,json:t,params:e};return this.$emit("WORLD",r),r})};var r={data:{API:t,VRCX:VRCX,appType:location.href.substr(-1),currentTime:(new Date).toJSON(),cpuUsage:0,feeds:[],devices:[]},computed:{},methods:{},watch:{},el:"#x-app",mounted(){t.getConfig().catch(e=>{throw e}).then(e=>(setInterval(()=>this.update(),1e3),this.update(),this.$nextTick((function(){"1"===this.appType&&(this.$el.style.display="")})),e))}};r.methods.update=function(){this.currentTime=(new Date).toJSON(),this.currentUser=VRCXStorage.GetObject("currentUser")||{},VRCX.CpuUsage().then(e=>{this.cpuUsage=e.toFixed(2)}),VRCX.GetVRDevices().then(e=>{e.forEach(e=>{e[2]=parseInt(e[2],10)}),this.devices=e}),this.updateSharedFeed()},r.methods.updateSharedFeed=function(){var e=this.feeds;if(this.feeds=VRCXStorage.GetArray("sharedFeeds"),"2"===this.appType){var t={};if(e.forEach(e=>{e.isFavorite&&("OnPlayerJoined"===e.type||"OnPlayerLeft"===e.type?(!t[e.data]||t[e.data]{e.isFavorite&&("Online"===e.type||"Offline"===e.type?(!t[e.displayName]||t[e.displayName]{e.created_at>n&&("OnPlayerJoined"===e.type?new Noty({type:"alert",text:`${e.data} has joined`}).show():"OnPlayerLeft"===e.type?new Noty({type:"alert",text:`${e.data} has left`}).show():"Online"===e.type?new Noty({type:"alert",text:`${e.displayName} has logged in`}).show():"Offline"===e.type&&new Noty({type:"alert",text:`${e.displayName} has logged out`}).show())})}},r.methods.userStatusClass=function(e){var t={};return e&&("offline"===e.location?t.offline=!0:"active"===e.status?t.active=!0:"join me"===e.status?t.joinme=!0:"busy"===e.status&&(t.busy=!0)),t},r=new Vue(r),window.$app=r}))},function(e,t,r){}]); \ No newline at end of file