mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-14 12:23:52 +02:00
split source files
This commit is contained in:
325
html/src/app.dark.scss
Normal file
325
html/src/app.dark.scss
Normal file
@@ -0,0 +1,325 @@
|
||||
@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 <https://opensource.org/licenses/MIT>.
|
||||
*/
|
||||
|
||||
::-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;
|
||||
}
|
||||
6839
html/src/app.js
Normal file
6839
html/src/app.js
Normal file
File diff suppressed because it is too large
Load Diff
498
html/src/app.scss
Normal file
498
html/src/app.scss
Normal file
@@ -0,0 +1,498 @@
|
||||
@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 <https://opensource.org/licenses/MIT>.
|
||||
*/
|
||||
|
||||
.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;
|
||||
}
|
||||
1850
html/src/index.html
Normal file
1850
html/src/index.html
Normal file
File diff suppressed because it is too large
Load Diff
1325
html/src/index.pug
Normal file
1325
html/src/index.pug
Normal file
File diff suppressed because it is too large
Load Diff
149
html/src/vr.html
Normal file
149
html/src/vr.html
Normal file
@@ -0,0 +1,149 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<title>VRCXVR</title>
|
||||
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
|
||||
<link rel="preconnect" href="https://api.vrchat.cloud">
|
||||
<link rel="preconnect" href="https://d348imysud55la.cloudfront.net">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/noty/3.2.0-beta/noty.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.11.1/theme-chalk/index.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Noto+Sans+JP|Noto+Sans+KR&display=swap">
|
||||
</head>
|
||||
<body>
|
||||
<div id="x-app" class="x-app" :class="{ 'x-app-type': appType === '1' }" style="display:none">
|
||||
|
||||
<div class="x-container" style="flex:1">
|
||||
<div ref="list" class="x-friend-list" style="color:#aaa">
|
||||
<template v-for="feed in feeds">
|
||||
<div v-if="feed.type === 'GPS'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.displayName"></span> is in <location :location="feed.location[0]"></location>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'Offline'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.displayName"></span> has logged out
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'Online'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.displayName"></span> has logged in
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'Status'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.displayName"></span> is <i class="x-user-status" :class="userStatusClass(feed.status[0])"></i> {{feed.status[0].statusDescription}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'OnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.data"></span> has joined
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'OnPlayerLeft'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<span class="name" v-text="feed.data"></span> has left
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="feed.type === 'Location'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }">
|
||||
<div class="detail">
|
||||
<span class="extra">
|
||||
<span class="time">{{ feed.created_at | formatDate('HH:MI') }}</span>
|
||||
<location :location="feed.data"></location>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x-container">
|
||||
<div style="display:flex;flex-direction:row">
|
||||
<template v-if="devices.length">
|
||||
<div v-for="device in devices" style="flex:none;text-align:center;width:64px">
|
||||
<template v-if="device[0] === 'tracker'">
|
||||
<img v-if="device[1] !== 'connected'" src="images/tracker_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/tracker_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/tracker_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<template v-else-if="device[0] === 'leftController'">
|
||||
<img v-if="device[1] !== 'connected'" src="images/controller_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/controller_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/controller_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<template v-else-if="device[0] === 'rightController'">
|
||||
<img v-if="device[1] !== 'connected'" src="images/controller_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/controller_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/controller_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<template v-else-if="device[0] === 'controller'">
|
||||
<img v-if="device[1] !== 'connected'" src="images/controller_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/controller_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/controller_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<template v-else-if="device[0] === 'base'">
|
||||
<img v-if="device[1] !== 'connected'" src="images/base_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/base_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/base_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<template v-else>
|
||||
<img v-if="device[1] !== 'connected'" src="images/other_status_off.png" style="width:32px;height:32px">
|
||||
<img v-else-if="device[2] < 20" src="images/other_status_ready_low.png" style="width:32px;height:32px">
|
||||
<img v-else src="images/other_status_ready.png" style="width:32px;height:32px">
|
||||
</template>
|
||||
<br><span>{{ device[2] }}%</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else>
|
||||
<span>No SteamVR Devices</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x-container">
|
||||
<span style="float:right">{{ currentTime | formatDate('YYYY-MM-DD HH:MI:SS AMPM') }}</span>
|
||||
<span>CPU {{ cpuUsage }}%</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/noty/3.2.0-beta/noty.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.11.1/index.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.11.1/locale/en.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = `vr.css?_=${Date.now()}`;
|
||||
document.head.appendChild(link);
|
||||
var script = document.createElement('script');
|
||||
script.src = `vr.js?_=${Date.now()}`;
|
||||
document.body.appendChild(script);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
763
html/src/vr.js
Normal file
763
html/src/vr.js
Normal file
@@ -0,0 +1,763 @@
|
||||
// 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 <https://opensource.org/licenses/MIT>.
|
||||
|
||||
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('<br>');
|
||||
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: '<span>{{ text }}<slot></slot></span>',
|
||||
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: `<strong>${noty.data}</strong> has joined`
|
||||
}).show();
|
||||
} else if (noty.type === 'OnPlayerLeft') {
|
||||
new Noty({
|
||||
type: 'alert',
|
||||
text: `<strong>${noty.data}</strong> has left`
|
||||
}).show();
|
||||
} else if (noty.type === 'Online') {
|
||||
new Noty({
|
||||
type: 'alert',
|
||||
text: `<strong>${noty.displayName}</strong> has logged in`
|
||||
}).show();
|
||||
} else if (noty.type === 'Offline') {
|
||||
new Noty({
|
||||
type: 'alert',
|
||||
text: `<strong>${noty.displayName}</strong> 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;
|
||||
});
|
||||
222
html/src/vr.scss
Normal file
222
html/src/vr.scss
Normal file
@@ -0,0 +1,222 @@
|
||||
@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 <https://opensource.org/licenses/MIT>.
|
||||
*/
|
||||
|
||||
/*
|
||||
마지노선인듯
|
||||
화면 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;
|
||||
}
|
||||
Reference in New Issue
Block a user