UI Refresh
@@ -15,10 +15,6 @@
|
|||||||
"@element-plus/icons-vue": "^2.3.2",
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@fontsource-variable/noto-sans-jp": "^5.2.9",
|
|
||||||
"@fontsource-variable/noto-sans-kr": "^5.2.9",
|
|
||||||
"@fontsource-variable/noto-sans-sc": "^5.2.9",
|
|
||||||
"@fontsource-variable/noto-sans-tc": "^5.2.9",
|
|
||||||
"@kamiya4047/eslint-plugin-pretty-import": "^0.1.6",
|
"@kamiya4047/eslint-plugin-pretty-import": "^0.1.6",
|
||||||
"@sentry/vite-plugin": "^4.6.1",
|
"@sentry/vite-plugin": "^4.6.1",
|
||||||
"@sentry/vue": "^10.32.1",
|
"@sentry/vue": "^10.32.1",
|
||||||
@@ -47,12 +43,12 @@
|
|||||||
"noty": "^3.2.0-beta-deprecated",
|
"noty": "^3.2.0-beta-deprecated",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.7.4",
|
||||||
"remixicon": "^4.7.0",
|
"remixicon": "^4.8.0",
|
||||||
"sass-embedded": "^1.97.1",
|
"sass-embedded": "^1.97.1",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"vite": "^7.3.0",
|
"vite": "^7.3.0",
|
||||||
"vue": "^3.5.26",
|
"vue": "^3.5.26",
|
||||||
"vue-i18n": "^11.2.2",
|
"vue-i18n": "^11.2.8",
|
||||||
"vue-marquee-text-component": "^2.0.1",
|
"vue-marquee-text-component": "^2.0.1",
|
||||||
"vue-router": "^4.6.4",
|
"vue-router": "^4.6.4",
|
||||||
"vue-showdown": "^4.2.0",
|
"vue-showdown": "^4.2.0",
|
||||||
@@ -2351,46 +2347,6 @@
|
|||||||
"url": "https://github.com/sponsors/ayuhito"
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fontsource-variable/noto-sans-jp": {
|
|
||||||
"version": "5.2.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans-jp/-/noto-sans-jp-5.2.9.tgz",
|
|
||||||
"integrity": "sha512-osPL5f7dvGDjuMuFwDTGPLG37030D8X5zk+3BWea6txAVDFeE/ZIrKW0DY0uSDfRn9+NiKbiFn/2QvZveKXTog==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "OFL-1.1",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ayuhito"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fontsource-variable/noto-sans-kr": {
|
|
||||||
"version": "5.2.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans-kr/-/noto-sans-kr-5.2.9.tgz",
|
|
||||||
"integrity": "sha512-g1BnJdJbnAgRUP8YxyPIm8npZVUbtt6VgtLnkGR7poa/RVbVGd27i+9138DmwRvtbKhJG1fPLQ/V3BonvFykRQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "OFL-1.1",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ayuhito"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fontsource-variable/noto-sans-sc": {
|
|
||||||
"version": "5.2.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans-sc/-/noto-sans-sc-5.2.9.tgz",
|
|
||||||
"integrity": "sha512-ZEEpZlxjYEIVdg85K38mqaoeBcorrN3Z6MaIkwK5w5Dqn/e9v5uVIYr0ukoLsFxaVyEXSi/c3caOeMHjbOMtfA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "OFL-1.1",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ayuhito"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@fontsource-variable/noto-sans-tc": {
|
|
||||||
"version": "5.2.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans-tc/-/noto-sans-tc-5.2.9.tgz",
|
|
||||||
"integrity": "sha512-GhtbSE8IZTP3vZj7Fu1G/PERxguMe3jryAbHovSd22Rs7aYdbXQD8vBqkTT/tkHIUn6t2IFReTfgKUoQBPCe+w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "OFL-1.1",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ayuhito"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@gar/promisify": {
|
"node_modules/@gar/promisify": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||||
@@ -2451,14 +2407,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@intlify/core-base": {
|
"node_modules/@intlify/core-base": {
|
||||||
"version": "11.2.2",
|
"version": "11.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.2.8.tgz",
|
||||||
"integrity": "sha512-0mCTBOLKIqFUP3BzwuFW23hYEl9g/wby6uY//AC5hTgQfTsM2srCYF2/hYGp+a5DZ/HIFIgKkLJMzXTt30r0JQ==",
|
"integrity": "sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intlify/message-compiler": "11.2.2",
|
"@intlify/message-compiler": "11.2.8",
|
||||||
"@intlify/shared": "11.2.2"
|
"@intlify/shared": "11.2.8"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
@@ -2468,13 +2424,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@intlify/message-compiler": {
|
"node_modules/@intlify/message-compiler": {
|
||||||
"version": "11.2.2",
|
"version": "11.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.2.8.tgz",
|
||||||
"integrity": "sha512-XS2p8Ff5JxWsKhgfld4/MRQzZRQ85drMMPhb7Co6Be4ZOgqJX1DzcZt0IFgGTycgqL8rkYNwgnD443Q+TapOoA==",
|
"integrity": "sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intlify/shared": "11.2.2",
|
"@intlify/shared": "11.2.8",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2485,9 +2441,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@intlify/shared": {
|
"node_modules/@intlify/shared": {
|
||||||
"version": "11.2.2",
|
"version": "11.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.2.8.tgz",
|
||||||
"integrity": "sha512-OtCmyFpSXxNu/oET/aN6HtPCbZ01btXVd0f3w00YsHOb13Kverk1jzA2k47pAekM55qbUw421fvPF1yxZ+gicw==",
|
"integrity": "sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -15566,9 +15522,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/remixicon": {
|
"node_modules/remixicon": {
|
||||||
"version": "4.7.0",
|
"version": "4.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/remixicon/-/remixicon-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/remixicon/-/remixicon-4.8.0.tgz",
|
||||||
"integrity": "sha512-g2pHOofQWARWpxdbrQu5+K3C8ZbqguQFzE54HIMWFCpFa63pumaAltIgZmFMRQpKKBScRWQASQfWxS9asNCcHQ==",
|
"integrity": "sha512-8qTM/bWkmsAWitvcL9XrVPVdqHRrdmnNp4zCFBdmIHBygxfHWwoK6NzitbiMyRzjcXKBMlS/ab5M65/ZlxJTVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
@@ -18493,14 +18449,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue-i18n": {
|
"node_modules/vue-i18n": {
|
||||||
"version": "11.2.2",
|
"version": "11.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.2.8.tgz",
|
||||||
"integrity": "sha512-ULIKZyRluUPRCZmihVgUvpq8hJTtOqnbGZuv4Lz+byEKZq4mU0g92og414l6f/4ju+L5mORsiUuEPYrAuX2NJg==",
|
"integrity": "sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intlify/core-base": "11.2.2",
|
"@intlify/core-base": "11.2.8",
|
||||||
"@intlify/shared": "11.2.2",
|
"@intlify/shared": "11.2.8",
|
||||||
"@vue/devtools-api": "^6.5.0"
|
"@vue/devtools-api": "^6.5.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -68,12 +68,12 @@
|
|||||||
"noty": "^3.2.0-beta-deprecated",
|
"noty": "^3.2.0-beta-deprecated",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.7.4",
|
||||||
"remixicon": "^4.7.0",
|
"remixicon": "^4.8.0",
|
||||||
"sass-embedded": "^1.97.1",
|
"sass-embedded": "^1.97.1",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"vite": "^7.3.0",
|
"vite": "^7.3.0",
|
||||||
"vue": "^3.5.26",
|
"vue": "^3.5.26",
|
||||||
"vue-i18n": "^11.2.2",
|
"vue-i18n": "^11.2.8",
|
||||||
"vue-marquee-text-component": "^2.0.1",
|
"vue-marquee-text-component": "^2.0.1",
|
||||||
"vue-router": "^4.6.4",
|
"vue-router": "^4.6.4",
|
||||||
"vue-showdown": "^4.2.0",
|
"vue-showdown": "^4.2.0",
|
||||||
|
|||||||
@@ -56,11 +56,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style scoped>
|
||||||
:deep(.el-splitter-bar__dragger) {
|
|
||||||
width: 4px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add title bar spacing for macOS */
|
/* Add title bar spacing for macOS */
|
||||||
.x-app.with-macos-titlebar {
|
.x-app.with-macos-titlebar {
|
||||||
padding-top: 28px;
|
padding-top: 28px;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
--offy: 14.5px;
|
--offy: 14.5px;
|
||||||
}
|
}
|
||||||
.flags {
|
.flags {
|
||||||
background: url('../images/flags.png') no-repeat;
|
background: url('/images/flags.png') no-repeat;
|
||||||
background-size: calc(var(--offx) * 6);
|
background-size: calc(var(--offx) * 6);
|
||||||
width: var(--offx);
|
width: var(--offx);
|
||||||
height: calc(var(--offx) / 72 * 52);
|
height: calc(var(--offx) / 72 * 52);
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/* Noty.js */
|
||||||
|
|
||||||
|
.noty_layout {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noty_theme__mint.noty_bar {
|
||||||
|
position: relative;
|
||||||
|
margin: 4px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
color: #2f2f2f;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #d1d1d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noty_theme__mint.noty_type__warning {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #ffae42;
|
||||||
|
border-bottom: 1px solid #e89f3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noty_theme__mint.noty_type__error {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #de636f;
|
||||||
|
border-bottom: 1px solid #ca5a65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noty_theme__mint.noty_type__info,
|
||||||
|
.noty_theme__mint.noty_type__information {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #7f7eff;
|
||||||
|
border-bottom: 1px solid #7473e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noty_theme__mint.noty_type__success {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #afc765;
|
||||||
|
border-bottom: 1px solid #a0b55c;
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div @click="confirm" class="avatar-info">
|
<div @click="confirm" class="avatar-info">
|
||||||
<span style="margin-right: 5px">{{ avatarName }}</span>
|
<span v-if="avatarType" :class="color" class="mr-2"><i :class="avatarTypeIcons" /></span>
|
||||||
<span v-if="avatarType" :class="color" style="margin-right: 5px"><i :class="avatarTypeIcons" /></span>
|
<span class="mr-2">{{ avatarName }}</span>
|
||||||
<span v-if="avatarTags" style="color: #909399; font-family: monospace; font-size: 12px">{{ avatarTags }}</span>
|
<span v-if="avatarTags" style="color: var(--el-text-color-secondary); font-size: 12px">{{ avatarTags }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
imageurl: String,
|
imageurl: String,
|
||||||
userid: String,
|
userid: String,
|
||||||
hintownerid: String,
|
hintownerid: String,
|
||||||
hintavatarname: String,
|
hintavatarname: [String, Object],
|
||||||
avatartags: Array
|
avatartags: Array
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,7 +45,9 @@
|
|||||||
if (!props.imageurl) {
|
if (!props.imageurl) {
|
||||||
avatarName.value = '';
|
avatarName.value = '';
|
||||||
} else if (props.hintownerid) {
|
} else if (props.hintownerid) {
|
||||||
avatarName.value = props.hintavatarname;
|
if (typeof props.hintavatarname === 'string') {
|
||||||
|
avatarName.value = props.hintavatarname;
|
||||||
|
}
|
||||||
ownerId = props.hintownerid;
|
ownerId = props.hintownerid;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="paginatedData"
|
:data="paginatedData"
|
||||||
v-bind="mergedTableProps"
|
v-bind="mergedTableProps"
|
||||||
|
:stripe="false"
|
||||||
:default-sort="resolvedDefaultSort"
|
:default-sort="resolvedDefaultSort"
|
||||||
@row-click="handleRowClick">
|
@row-click="handleRowClick">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@@ -102,7 +103,6 @@
|
|||||||
delete rest.defaultSort;
|
delete rest.defaultSort;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
stripe: true,
|
|
||||||
...rest
|
...rest
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -168,7 +168,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped>
|
||||||
.toolbar-icon:hover {
|
.toolbar-icon:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,13 @@
|
|||||||
{{ t('dialog.user.info.close_instance') }} </el-button
|
{{ t('dialog.user.info.close_instance') }} </el-button
|
||||||
><br /><br />
|
><br /><br />
|
||||||
</template>
|
</template>
|
||||||
<span><span style="color: #409eff">PC: </span>{{ props.instance.platforms.standalonewindows }}</span
|
<span
|
||||||
|
><span style="color: var(--el-color-primary)">PC: </span
|
||||||
|
>{{ props.instance.platforms.standalonewindows }}</span
|
||||||
><br />
|
><br />
|
||||||
<span><span style="color: #67c23a">Android: </span>{{ props.instance.platforms.android }}</span
|
<span
|
||||||
|
><span style="color: var(--el-color-success)">Android: </span
|
||||||
|
>{{ props.instance.platforms.android }}</span
|
||||||
><br />
|
><br />
|
||||||
<span>{{ t('dialog.user.info.instance_game_version') }} {{ props.instance.gameServerVersion }}</span
|
<span>{{ t('dialog.user.info.instance_game_version') }} {{ props.instance.gameServerVersion }}</span
|
||||||
><br />
|
><br />
|
||||||
@@ -46,13 +50,13 @@
|
|||||||
<span v-if="props.friendcount" style="margin-left: 5px">({{ props.friendcount }})</span>
|
<span v-if="props.friendcount" style="margin-left: 5px">({{ props.friendcount }})</span>
|
||||||
<span
|
<span
|
||||||
v-if="state.isValidInstance && !props.instance.hasCapacityForYou"
|
v-if="state.isValidInstance && !props.instance.hasCapacityForYou"
|
||||||
style="margin-left: 5px; color: lightcoral"
|
style="margin-left: 5px; color: var(--el-color-danger)"
|
||||||
>{{ t('dialog.user.info.instance_full') }}</span
|
>{{ t('dialog.user.info.instance_full') }}</span
|
||||||
>
|
>
|
||||||
<span v-if="props.instance.queueSize" style="margin-left: 5px"
|
<span v-if="props.instance.queueSize" style="margin-left: 5px"
|
||||||
>{{ t('dialog.user.info.instance_queue') }} {{ props.instance.queueSize }}</span
|
>{{ t('dialog.user.info.instance_queue') }} {{ props.instance.queueSize }}</span
|
||||||
>
|
>
|
||||||
<span v-if="state.isAgeGated" style="margin-left: 5px; color: lightcoral">{{
|
<span v-if="state.isAgeGated" style="margin-left: 5px; color: var(--el-color-danger)">{{
|
||||||
t('dialog.user.info.instance_age_gated')
|
t('dialog.user.info.instance_age_gated')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<span v-if="!text" class="transparent">-</span>
|
<div v-if="!text" class="transparent">-</div>
|
||||||
<span v-show="text">
|
<div v-show="text" class="flex items-center">
|
||||||
<span
|
<div v-if="region" :class="['flags', 'mr-1.5', region]"></div>
|
||||||
:class="{ 'x-link': link && location !== 'private' && location !== 'offline' }"
|
<el-tooltip
|
||||||
@click="handleShowWorldDialog">
|
:content="`${t('dialog.new_instance.instance_id')}: #${instanceName}`"
|
||||||
<el-icon :class="['is-loading', 'inline-block']" style="margin-right: 3px" v-if="isTraveling"
|
:disabled="!instanceName"
|
||||||
><Loading
|
:show-after="300"
|
||||||
/></el-icon>
|
placement="top">
|
||||||
<span>{{ text }}</span>
|
<div
|
||||||
</span>
|
:class="['x-location', { 'x-link': link && location !== 'private' && location !== 'offline' }]"
|
||||||
<span v-if="groupName" :class="{ 'x-link': link }" @click="handleShowGroupDialog">({{ groupName }})</span>
|
class="inline-flex min-w-0 flex-nowrap items-center overflow-hidden"
|
||||||
<span
|
@click="handleShowWorldDialog">
|
||||||
v-if="region"
|
<el-icon :class="['is-loading']" class="mr-1" v-if="isTraveling"><Loading /></el-icon>
|
||||||
:class="['flags', 'inline-block', 'ml-5', region, 'transform-[translateY(3px)]']"></span>
|
<span class="min-w-0 truncate">{{ text }}</span>
|
||||||
<NativeTooltip v-if="isClosed" :content="t('dialog.user.info.instance_closed')">
|
<span
|
||||||
|
v-if="groupName"
|
||||||
|
class="ml-0.5 whitespace-nowrap"
|
||||||
|
:class="{ 'x-link': link }"
|
||||||
|
@click.stop="handleShowGroupDialog">
|
||||||
|
({{ groupName }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip v-if="isClosed" :content="t('dialog.user.info.instance_closed')">
|
||||||
<el-icon :class="['inline-block', 'ml-5']" style="color: lightcoral"><WarnTriangleFilled /></el-icon>
|
<el-icon :class="['inline-block', 'ml-5']" style="color: lightcoral"><WarnTriangleFilled /></el-icon>
|
||||||
</NativeTooltip>
|
</el-tooltip>
|
||||||
<el-icon v-if="strict" :class="['inline-block', 'ml-5']"><Lock /></el-icon>
|
<el-icon v-if="strict" :class="['inline-block', 'ml-5']"><Lock /></el-icon>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -30,6 +39,7 @@
|
|||||||
|
|
||||||
import { useGroupStore, useInstanceStore, useSearchStore, useWorldStore } from '../stores';
|
import { useGroupStore, useInstanceStore, useSearchStore, useWorldStore } from '../stores';
|
||||||
import { getGroupName, getWorldName, parseLocation } from '../shared/utils';
|
import { getGroupName, getWorldName, parseLocation } from '../shared/utils';
|
||||||
|
import { accessTypeLocaleKeyMap } from '../shared/constants';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -67,6 +77,7 @@
|
|||||||
const isTraveling = ref(false);
|
const isTraveling = ref(false);
|
||||||
const groupName = ref('');
|
const groupName = ref('');
|
||||||
const isClosed = ref(false);
|
const isClosed = ref(false);
|
||||||
|
const instanceName = ref('');
|
||||||
|
|
||||||
let isDisposed = false;
|
let isDisposed = false;
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -108,7 +119,8 @@
|
|||||||
isTraveling.value = true;
|
isTraveling.value = true;
|
||||||
}
|
}
|
||||||
const L = parseLocation(instanceId);
|
const L = parseLocation(instanceId);
|
||||||
setText(L, L.instanceName);
|
setText(L);
|
||||||
|
instanceName.value = L.instanceName;
|
||||||
if (!L.isRealInstance) {
|
if (!L.isRealInstance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -116,7 +128,8 @@
|
|||||||
const instanceRef = cachedInstances.get(L.tag);
|
const instanceRef = cachedInstances.get(L.tag);
|
||||||
if (typeof instanceRef !== 'undefined') {
|
if (typeof instanceRef !== 'undefined') {
|
||||||
if (instanceRef.displayName) {
|
if (instanceRef.displayName) {
|
||||||
setText(L, instanceRef.displayName);
|
setText(L);
|
||||||
|
instanceName.value = instanceRef.displayName;
|
||||||
}
|
}
|
||||||
if (instanceRef.closedAt) {
|
if (instanceRef.closedAt) {
|
||||||
isClosed.value = true;
|
isClosed.value = true;
|
||||||
@@ -147,7 +160,9 @@
|
|||||||
strict.value = L.strict;
|
strict.value = L.strict;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setText(L, instanceName) {
|
function setText(L) {
|
||||||
|
const accessTypeLabel = translateAccessType(L.accessTypeName);
|
||||||
|
|
||||||
if (L.isOffline) {
|
if (L.isOffline) {
|
||||||
text.value = 'Offline';
|
text.value = 'Offline';
|
||||||
} else if (L.isPrivate) {
|
} else if (L.isPrivate) {
|
||||||
@@ -156,13 +171,13 @@
|
|||||||
text.value = 'Traveling';
|
text.value = 'Traveling';
|
||||||
} else if (typeof props.hint === 'string' && props.hint !== '') {
|
} else if (typeof props.hint === 'string' && props.hint !== '') {
|
||||||
if (L.instanceId) {
|
if (L.instanceId) {
|
||||||
text.value = `${props.hint} #${instanceName} ${L.accessTypeName}`;
|
text.value = `${props.hint} · ${accessTypeLabel}`;
|
||||||
} else {
|
} else {
|
||||||
text.value = props.hint;
|
text.value = props.hint;
|
||||||
}
|
}
|
||||||
} else if (L.worldId) {
|
} else if (L.worldId) {
|
||||||
if (L.instanceId) {
|
if (L.instanceId) {
|
||||||
text.value = `${L.worldId} #${instanceName} ${L.accessTypeName}`;
|
text.value = `${L.worldId} · ${accessTypeLabel}`;
|
||||||
} else {
|
} else {
|
||||||
text.value = L.worldId;
|
text.value = L.worldId;
|
||||||
}
|
}
|
||||||
@@ -172,7 +187,7 @@
|
|||||||
.then((name) => {
|
.then((name) => {
|
||||||
if (!isDisposed && name && currentInstanceId() === L.tag) {
|
if (!isDisposed && name && currentInstanceId() === L.tag) {
|
||||||
if (L.instanceId) {
|
if (L.instanceId) {
|
||||||
text.value = `${name} #${instanceName} ${L.accessTypeName}`;
|
text.value = `${name} · ${translateAccessType(L.accessTypeName)}`;
|
||||||
} else {
|
} else {
|
||||||
text.value = name;
|
text.value = name;
|
||||||
}
|
}
|
||||||
@@ -182,13 +197,21 @@
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
});
|
});
|
||||||
} else if (L.instanceId) {
|
} else if (L.instanceId) {
|
||||||
text.value = `${ref.name} #${instanceName} ${L.accessTypeName}`;
|
text.value = `${ref.name} · ${accessTypeLabel}`;
|
||||||
} else {
|
} else {
|
||||||
text.value = ref.name;
|
text.value = ref.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function translateAccessType(accessTypeName) {
|
||||||
|
const key = accessTypeLocaleKeyMap[accessTypeName];
|
||||||
|
if (!key) {
|
||||||
|
return accessTypeName;
|
||||||
|
}
|
||||||
|
return t(key);
|
||||||
|
}
|
||||||
|
|
||||||
function handleShowWorldDialog() {
|
function handleShowWorldDialog() {
|
||||||
if (props.link) {
|
if (props.link) {
|
||||||
let instanceId = currentInstanceId();
|
let instanceId = currentInstanceId();
|
||||||
@@ -218,15 +241,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.inline-block {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-5 {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transparent {
|
.transparent {
|
||||||
color: transparent;
|
color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(html.dark .x-location),
|
||||||
|
:global(:root.dark .x-location),
|
||||||
|
:global(:root[data-theme='dark'] .x-location) {
|
||||||
|
color: var(--color-zinc-300);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span class="x-location-world">
|
||||||
|
<span v-if="region" :class="['flags', 'inline-block', 'mr-1.25', region]"></span>
|
||||||
<span @click="showLaunchDialog" class="x-link">
|
<span @click="showLaunchDialog" class="x-link">
|
||||||
<el-icon v-if="isUnlocked" :class="['inline-block', 'mr-5']"><Unlock /></el-icon>
|
<el-icon v-if="isUnlocked" :class="['inline-block', 'mr-1.25']"><Unlock /></el-icon>
|
||||||
<span>#{{ instanceName }} {{ accessTypeName }}</span>
|
<span> {{ accessTypeName }} #{{ instanceName }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="groupName" @click="showGroupDialog" class="x-link">({{ groupName }})</span>
|
<span v-if="groupName" @click="showGroupDialog" class="x-link">({{ groupName }})</span>
|
||||||
<span v-if="region" :class="['flags', 'inline-block', 'ml-5', region, 'transform-[translateY(3px)]']"></span>
|
<el-tooltip v-if="isClosed" :content="t('dialog.user.info.instance_closed')">
|
||||||
<NativeTooltip v-if="isClosed" :content="t('dialog.user.info.instance_closed')">
|
|
||||||
<el-icon :class="['inline-block', 'ml-5']" style="color: lightcoral"><WarnTriangleFilled /></el-icon>
|
<el-icon :class="['inline-block', 'ml-5']" style="color: lightcoral"><WarnTriangleFilled /></el-icon>
|
||||||
</NativeTooltip>
|
</el-tooltip>
|
||||||
<el-icon v-if="strict" style="display: inline-block; margin-left: 5px"><Lock /></el-icon>
|
<el-icon v-if="strict" style="display: inline-block; margin-left: 5px"><Lock /></el-icon>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
import { useGroupStore, useInstanceStore, useLaunchStore } from '../stores';
|
import { useGroupStore, useInstanceStore, useLaunchStore } from '../stores';
|
||||||
import { getGroupName, parseLocation } from '../shared/utils';
|
import { getGroupName, parseLocation } from '../shared/utils';
|
||||||
|
import { accessTypeLocaleKeyMap } from '../shared/constants';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { cachedInstances } = useInstanceStore();
|
const { cachedInstances } = useInstanceStore();
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
function parse() {
|
function parse() {
|
||||||
const locObj = props.locationobject;
|
const locObj = props.locationobject;
|
||||||
location.value = locObj.tag;
|
location.value = locObj.tag;
|
||||||
accessTypeName.value = locObj.accessTypeName;
|
accessTypeName.value = translateAccessType(locObj.accessTypeName);
|
||||||
strict.value = locObj.strict;
|
strict.value = locObj.strict;
|
||||||
shortName.value = locObj.shortName;
|
shortName.value = locObj.shortName;
|
||||||
|
|
||||||
@@ -97,6 +98,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function translateAccessType(accessTypeNameRaw) {
|
||||||
|
const key = accessTypeLocaleKeyMap[accessTypeNameRaw];
|
||||||
|
if (!key) {
|
||||||
|
return accessTypeNameRaw;
|
||||||
|
}
|
||||||
|
return t(key);
|
||||||
|
}
|
||||||
|
|
||||||
watch(() => props.locationobject, parse, { immediate: true });
|
watch(() => props.locationobject, parse, { immediate: true });
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -126,11 +135,9 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ml-5 {
|
:global(html.dark .x-location-world),
|
||||||
margin-left: 5px;
|
:global(:root.dark .x-location-world),
|
||||||
}
|
:global(:root[data-theme='dark'] .x-location-world) {
|
||||||
|
color: var(--color-zinc-100);
|
||||||
.mr-5 {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,537 +0,0 @@
|
|||||||
<template>
|
|
||||||
<span
|
|
||||||
ref="triggerEl"
|
|
||||||
class="vrcx-native-tooltip__trigger"
|
|
||||||
:style="triggerStyle"
|
|
||||||
@mouseenter="onEnter"
|
|
||||||
@mouseleave="onLeave"
|
|
||||||
@focusin="onEnter"
|
|
||||||
@focusout="onLeave"
|
|
||||||
@keydown.esc="close">
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div
|
|
||||||
ref="tooltipEl"
|
|
||||||
class="vrcx-native-tooltip__content"
|
|
||||||
:class="[
|
|
||||||
placementClass,
|
|
||||||
{
|
|
||||||
'has-arrow': props.showArrow,
|
|
||||||
'is-open': isOpen,
|
|
||||||
'is-closing': isClosing
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
:style="contentStyle"
|
|
||||||
popover="manual"
|
|
||||||
role="tooltip"
|
|
||||||
@mouseenter="cancelClose"
|
|
||||||
@mouseleave="onLeave">
|
|
||||||
<slot name="content">
|
|
||||||
<span class="vrcx-native-tooltip__text" v-text="content" />
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed, onBeforeUnmount, ref } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
content: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
showAfter: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
placement: {
|
|
||||||
type: String,
|
|
||||||
default: 'top'
|
|
||||||
},
|
|
||||||
enterMs: {
|
|
||||||
type: Number,
|
|
||||||
default: 120
|
|
||||||
},
|
|
||||||
exitMs: {
|
|
||||||
type: Number,
|
|
||||||
default: 100
|
|
||||||
},
|
|
||||||
offset: {
|
|
||||||
type: Number,
|
|
||||||
default: 8
|
|
||||||
},
|
|
||||||
maxWidth: {
|
|
||||||
type: String,
|
|
||||||
default: '260px'
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
showArrow: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ARROW_SIZE_PX = 10;
|
|
||||||
|
|
||||||
const triggerEl = ref(null);
|
|
||||||
const tooltipEl = ref(null);
|
|
||||||
|
|
||||||
const isOpen = ref(false);
|
|
||||||
const isClosing = ref(false);
|
|
||||||
|
|
||||||
const anchorName = `--vrcx-tt-${Math.random().toString(36).slice(2, 10)}`;
|
|
||||||
|
|
||||||
const triggerStyle = computed(() => {
|
|
||||||
return {
|
|
||||||
'anchor-name': anchorName
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const contentStyle = computed(() => {
|
|
||||||
const effectiveOffsetPx = props.offset + (props.showArrow ? ARROW_SIZE_PX / 2 : 0);
|
|
||||||
return {
|
|
||||||
'position-anchor': anchorName,
|
|
||||||
'--vrcx-tt-enter': `${props.enterMs}ms`,
|
|
||||||
'--vrcx-tt-exit': `${props.exitMs}ms`,
|
|
||||||
'--vrcx-tt-offset': `${effectiveOffsetPx}px`,
|
|
||||||
'--vrcx-tt-max-width': props.maxWidth,
|
|
||||||
'--vrcx-tt-shift-x': `${shiftX.value}px`,
|
|
||||||
'--vrcx-tt-shift-y': `${shiftY.value}px`,
|
|
||||||
'--vrcx-tt-arrow-x': `${arrowX.value}px`,
|
|
||||||
'--vrcx-tt-arrow-y': `${arrowY.value}px`
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const placementClass = computed(() => {
|
|
||||||
const normalized = String(props.placement || 'top').toLowerCase();
|
|
||||||
return `is-${normalized}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const shiftX = ref(0);
|
|
||||||
const shiftY = ref(0);
|
|
||||||
const arrowX = ref(0);
|
|
||||||
const arrowY = ref(0);
|
|
||||||
|
|
||||||
const timers = {
|
|
||||||
open: 0,
|
|
||||||
close: 0,
|
|
||||||
hide: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
function clearTimer(key) {
|
|
||||||
const id = timers[key];
|
|
||||||
if (id) {
|
|
||||||
window.clearTimeout(id);
|
|
||||||
timers[key] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearAllTimers() {
|
|
||||||
clearTimer('open');
|
|
||||||
clearTimer('close');
|
|
||||||
clearTimer('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetOffsets() {
|
|
||||||
shiftX.value = 0;
|
|
||||||
shiftY.value = 0;
|
|
||||||
arrowX.value = 0;
|
|
||||||
arrowY.value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPopoverOpen(el) {
|
|
||||||
return Boolean(el?.matches?.(':popover-open'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateViewportShift() {
|
|
||||||
const el = tooltipEl.value;
|
|
||||||
if (!el) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
shiftX.value = 0;
|
|
||||||
shiftY.value = 0;
|
|
||||||
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
const margin = 8;
|
|
||||||
const vw = window.innerWidth;
|
|
||||||
const vh = window.innerHeight;
|
|
||||||
|
|
||||||
let dx = 0;
|
|
||||||
let dy = 0;
|
|
||||||
|
|
||||||
if (rect.left < margin) {
|
|
||||||
dx += margin - rect.left;
|
|
||||||
}
|
|
||||||
if (rect.right > vw - margin) {
|
|
||||||
dx -= rect.right - (vw - margin);
|
|
||||||
}
|
|
||||||
if (rect.top < margin) {
|
|
||||||
dy += margin - rect.top;
|
|
||||||
}
|
|
||||||
if (rect.bottom > vh - margin) {
|
|
||||||
dy -= rect.bottom - (vh - margin);
|
|
||||||
}
|
|
||||||
|
|
||||||
shiftX.value = Math.round(dx);
|
|
||||||
shiftY.value = Math.round(dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateArrowPosition() {
|
|
||||||
if (!props.showArrow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const trigger = triggerEl.value;
|
|
||||||
const tooltip = tooltipEl.value;
|
|
||||||
if (!trigger || !tooltip) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const placement = String(props.placement || 'top').toLowerCase();
|
|
||||||
|
|
||||||
const tr = trigger.getBoundingClientRect();
|
|
||||||
const tt = tooltip.getBoundingClientRect();
|
|
||||||
|
|
||||||
const cs = window.getComputedStyle(tooltip);
|
|
||||||
const padLeft = Number.parseFloat(cs.paddingLeft) || 0;
|
|
||||||
const padRight = Number.parseFloat(cs.paddingRight) || 0;
|
|
||||||
const padTop = Number.parseFloat(cs.paddingTop) || 0;
|
|
||||||
const padBottom = Number.parseFloat(cs.paddingBottom) || 0;
|
|
||||||
|
|
||||||
const padding = 12;
|
|
||||||
const half = ARROW_SIZE_PX / 2;
|
|
||||||
|
|
||||||
if (placement.startsWith('top') || placement.startsWith('bottom')) {
|
|
||||||
const desired = tr.left + tr.width / 2 - tt.left;
|
|
||||||
|
|
||||||
const edgeLeft = Math.max(padding, padLeft) + half;
|
|
||||||
const edgeRight = Math.max(padding, padRight) + half;
|
|
||||||
const min = edgeLeft;
|
|
||||||
const max = tt.width - edgeRight;
|
|
||||||
|
|
||||||
const clamped = min > max ? tt.width / 2 : Math.min(Math.max(desired, min), max);
|
|
||||||
arrowX.value = Math.round(clamped);
|
|
||||||
arrowY.value = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (placement.startsWith('left') || placement.startsWith('right')) {
|
|
||||||
const desired = tr.top + tr.height / 2 - tt.top;
|
|
||||||
|
|
||||||
const edgeTop = Math.max(padding, padTop) + half;
|
|
||||||
const edgeBottom = Math.max(padding, padBottom) + half;
|
|
||||||
const min = edgeTop;
|
|
||||||
const max = tt.height - edgeBottom;
|
|
||||||
|
|
||||||
const clamped = min > max ? tt.height / 2 : Math.min(Math.max(desired, min), max);
|
|
||||||
arrowY.value = Math.round(clamped);
|
|
||||||
arrowX.value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function open() {
|
|
||||||
if (props.disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const el = tooltipEl.value;
|
|
||||||
if (!el) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAllTimers();
|
|
||||||
|
|
||||||
const doOpen = () => {
|
|
||||||
timers.open = 0;
|
|
||||||
|
|
||||||
const tooltip = tooltipEl.value;
|
|
||||||
if (!tooltip) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const alreadyOpen = isPopoverOpen(tooltip);
|
|
||||||
|
|
||||||
isClosing.value = false;
|
|
||||||
if (!alreadyOpen) {
|
|
||||||
isOpen.value = false;
|
|
||||||
tooltip.showPopover();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
updateViewportShift();
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
updateArrowPosition();
|
|
||||||
isOpen.value = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (props.showAfter > 0) {
|
|
||||||
timers.open = window.setTimeout(doOpen, props.showAfter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
doOpen();
|
|
||||||
}
|
|
||||||
|
|
||||||
function close(immediate = false) {
|
|
||||||
const el = tooltipEl.value;
|
|
||||||
if (!el) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAllTimers();
|
|
||||||
|
|
||||||
if (immediate) {
|
|
||||||
isOpen.value = false;
|
|
||||||
isClosing.value = false;
|
|
||||||
resetOffsets();
|
|
||||||
if (isPopoverOpen(el)) {
|
|
||||||
el.hidePopover();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isOpen.value = false;
|
|
||||||
isClosing.value = true;
|
|
||||||
timers.hide = window.setTimeout(() => {
|
|
||||||
timers.hide = 0;
|
|
||||||
isClosing.value = false;
|
|
||||||
resetOffsets();
|
|
||||||
if (isPopoverOpen(el)) {
|
|
||||||
el.hidePopover();
|
|
||||||
}
|
|
||||||
}, props.exitMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onEnter() {
|
|
||||||
open();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onLeave() {
|
|
||||||
clearTimer('open');
|
|
||||||
clearTimer('close');
|
|
||||||
timers.close = window.setTimeout(() => {
|
|
||||||
timers.close = 0;
|
|
||||||
close();
|
|
||||||
}, 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelClose() {
|
|
||||||
clearTimer('close');
|
|
||||||
clearTimer('hide');
|
|
||||||
if (isPopoverOpen(tooltipEl.value)) {
|
|
||||||
isClosing.value = false;
|
|
||||||
isOpen.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
close(true);
|
|
||||||
clearAllTimers();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.vrcx-native-tooltip__trigger {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content {
|
|
||||||
position: fixed;
|
|
||||||
inset: auto;
|
|
||||||
|
|
||||||
overflow: visible;
|
|
||||||
clip-path: none;
|
|
||||||
|
|
||||||
inline-size: max-content;
|
|
||||||
max-inline-size: min(var(--vrcx-tt-max-width), calc(100vw - 16px));
|
|
||||||
min-inline-size: 0;
|
|
||||||
padding: 6px 10px;
|
|
||||||
|
|
||||||
border-radius: var(--el-border-radius-base);
|
|
||||||
border: 0;
|
|
||||||
|
|
||||||
background: var(--el-tooltip-bg-color, color-mix(in srgb, var(--el-color-black) 88%, transparent));
|
|
||||||
color: var(--el-tooltip-text-color, var(--el-color-white));
|
|
||||||
|
|
||||||
box-shadow: none;
|
|
||||||
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.35;
|
|
||||||
|
|
||||||
white-space: pre-line;
|
|
||||||
word-break: break-word;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
transition-property: opacity;
|
|
||||||
transition-duration: var(--vrcx-tt-exit);
|
|
||||||
transition-timing-function: linear;
|
|
||||||
transition-behavior: allow-discrete;
|
|
||||||
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(html.dark) .vrcx-native-tooltip__content {
|
|
||||||
background: var(--el-tooltip-bg-color, color-mix(in srgb, var(--el-color-black) 96%, transparent));
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.has-arrow::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
background: var(--el-tooltip-bg-color, color-mix(in srgb, var(--el-color-black) 88%, transparent));
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(html.dark) .vrcx-native-tooltip__content.has-arrow::before {
|
|
||||||
background: var(--el-tooltip-bg-color, color-mix(in srgb, var(--el-color-black) 96%, transparent));
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-top::before,
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-top-start::before,
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-top-end::before {
|
|
||||||
left: var(--vrcx-tt-arrow-x, 50%);
|
|
||||||
bottom: -5px;
|
|
||||||
translate: -50% 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-bottom::before,
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-bottom-start::before,
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-bottom-end::before {
|
|
||||||
left: var(--vrcx-tt-arrow-x, 50%);
|
|
||||||
top: -5px;
|
|
||||||
translate: -50% 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-left::before,
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-left-start::before,
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-left-end::before {
|
|
||||||
top: var(--vrcx-tt-arrow-y, 50%);
|
|
||||||
right: -5px;
|
|
||||||
translate: 0 -50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-right::before,
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-right-start::before,
|
|
||||||
.vrcx-native-tooltip__content.has-arrow.is-right-end::before {
|
|
||||||
top: var(--vrcx-tt-arrow-y, 50%);
|
|
||||||
left: -5px;
|
|
||||||
translate: 0 -50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content:popover-open.is-open {
|
|
||||||
opacity: 1;
|
|
||||||
transition-duration: var(--vrcx-tt-enter);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content:popover-open.is-closing {
|
|
||||||
opacity: 0;
|
|
||||||
transition-duration: var(--vrcx-tt-exit);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-top {
|
|
||||||
left: anchor(center);
|
|
||||||
bottom: anchor(top);
|
|
||||||
translate: calc(-50% + var(--vrcx-tt-shift-x)) calc((-1 * var(--vrcx-tt-offset)) + var(--vrcx-tt-shift-y));
|
|
||||||
transform-origin: bottom center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-top-start {
|
|
||||||
left: anchor(left);
|
|
||||||
bottom: anchor(top);
|
|
||||||
translate: var(--vrcx-tt-shift-x) calc((-1 * var(--vrcx-tt-offset)) + var(--vrcx-tt-shift-y));
|
|
||||||
transform-origin: bottom left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-top-end {
|
|
||||||
right: anchor(right);
|
|
||||||
bottom: anchor(top);
|
|
||||||
translate: var(--vrcx-tt-shift-x) calc((-1 * var(--vrcx-tt-offset)) + var(--vrcx-tt-shift-y));
|
|
||||||
transform-origin: bottom right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-bottom {
|
|
||||||
left: anchor(center);
|
|
||||||
top: anchor(bottom);
|
|
||||||
translate: calc(-50% + var(--vrcx-tt-shift-x)) calc(var(--vrcx-tt-offset) + var(--vrcx-tt-shift-y));
|
|
||||||
transform-origin: top center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-bottom-start {
|
|
||||||
left: anchor(left);
|
|
||||||
top: anchor(bottom);
|
|
||||||
translate: var(--vrcx-tt-shift-x) calc(var(--vrcx-tt-offset) + var(--vrcx-tt-shift-y));
|
|
||||||
transform-origin: top left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-bottom-end {
|
|
||||||
right: anchor(right);
|
|
||||||
top: anchor(bottom);
|
|
||||||
translate: var(--vrcx-tt-shift-x) calc(var(--vrcx-tt-offset) + var(--vrcx-tt-shift-y));
|
|
||||||
transform-origin: top right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-left {
|
|
||||||
right: anchor(left);
|
|
||||||
top: anchor(center);
|
|
||||||
translate: calc((-1 * var(--vrcx-tt-offset)) + var(--vrcx-tt-shift-x)) calc(-50% + var(--vrcx-tt-shift-y));
|
|
||||||
transform-origin: center right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-left-start {
|
|
||||||
right: anchor(left);
|
|
||||||
top: anchor(top);
|
|
||||||
translate: calc((-1 * var(--vrcx-tt-offset)) + var(--vrcx-tt-shift-x)) var(--vrcx-tt-shift-y);
|
|
||||||
transform-origin: top right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-left-end {
|
|
||||||
right: anchor(left);
|
|
||||||
bottom: anchor(bottom);
|
|
||||||
translate: calc((-1 * var(--vrcx-tt-offset)) + var(--vrcx-tt-shift-x)) var(--vrcx-tt-shift-y);
|
|
||||||
transform-origin: bottom right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-right {
|
|
||||||
left: anchor(right);
|
|
||||||
top: anchor(center);
|
|
||||||
translate: calc(var(--vrcx-tt-offset) + var(--vrcx-tt-shift-x)) calc(-50% + var(--vrcx-tt-shift-y));
|
|
||||||
transform-origin: center left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-right-start {
|
|
||||||
left: anchor(right);
|
|
||||||
top: anchor(top);
|
|
||||||
translate: calc(var(--vrcx-tt-offset) + var(--vrcx-tt-shift-x)) var(--vrcx-tt-shift-y);
|
|
||||||
transform-origin: top left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content.is-right-end {
|
|
||||||
left: anchor(right);
|
|
||||||
bottom: anchor(bottom);
|
|
||||||
translate: calc(var(--vrcx-tt-offset) + var(--vrcx-tt-shift-x)) var(--vrcx-tt-shift-y);
|
|
||||||
transform-origin: bottom left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__content:not([class*='is-']) {
|
|
||||||
left: anchor(center);
|
|
||||||
bottom: anchor(top);
|
|
||||||
translate: calc(-50% + var(--vrcx-tt-shift-x)) calc((-1 * var(--vrcx-tt-offset)) + var(--vrcx-tt-shift-y));
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrcx-native-tooltip__text {
|
|
||||||
display: block;
|
|
||||||
white-space: pre-line;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="x-menu-container nav-menu-container">
|
<div class="x-menu-container nav-menu-container" :class="{ 'is-collapsed': isCollapsed }">
|
||||||
<template v-if="navLayoutReady">
|
<template v-if="navLayoutReady">
|
||||||
<div>
|
<div class="nav-menu-body mt-5">
|
||||||
<div v-if="updateInProgress" class="pending-update" @click="showVRCXUpdateDialog">
|
<div v-if="updateInProgress" class="pending-update" @click="showVRCXUpdateDialog">
|
||||||
<el-progress
|
<el-progress
|
||||||
type="circle"
|
type="circle"
|
||||||
@@ -16,80 +16,71 @@
|
|||||||
type="success"
|
type="success"
|
||||||
plain
|
plain
|
||||||
style="font-size: 19px; height: 36px; width: 44px; margin: 10px"
|
style="font-size: 19px; height: 36px; width: 44px; margin: 10px"
|
||||||
@click="showVRCXUpdateDialog"
|
@click="showVRCXUpdateDialog">
|
||||||
><i class="ri-download-line"></i
|
<i class="ri-download-line"></i>
|
||||||
></el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-menu collapse :default-active="activeMenuIndex" :collapse-transition="false" ref="navMenuRef">
|
<el-menu ref="navMenuRef" class="nav-menu" :collapse="isCollapsed" :collapse-transition="false">
|
||||||
<el-popover
|
<template v-for="item in menuItems" :key="item.index">
|
||||||
v-for="item in navMenuItems"
|
<el-menu-item
|
||||||
:disabled="!item.entries?.length"
|
v-if="!item.children?.length"
|
||||||
:key="item.index"
|
:index="item.index"
|
||||||
:ref="(el) => setNavPopoverRef(el, item.index)"
|
:class="{ notify: isNavItemNotified(item) }"
|
||||||
placement="right-start"
|
@click="handleMenuItemClick(item)">
|
||||||
trigger="hover"
|
<i :class="item.icon"></i>
|
||||||
:hide-after="isSteamVRRunning ? 400 : 150"
|
<template #title>
|
||||||
:show-arrow="false"
|
|
||||||
:offset="0"
|
|
||||||
:width="navPopoverWidth"
|
|
||||||
transition="nav-menu-slide"
|
|
||||||
@before-enter="handleSubMenuBeforeEnter()"
|
|
||||||
:popper-style="navPopoverStyle"
|
|
||||||
popper-class="nav-menu-popover-popper">
|
|
||||||
<div class="nav-menu-popover">
|
|
||||||
<div class="nav-menu-popover__header">
|
|
||||||
<i :class="item.icon"></i>
|
|
||||||
<span>{{ item.titleIsCustom ? item.title : t(item.title || '') }}</span>
|
<span>{{ item.titleIsCustom ? item.title : t(item.title || '') }}</span>
|
||||||
</div>
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
<div class="nav-menu-popover__menu">
|
<el-sub-menu v-else :index="item.index">
|
||||||
<button
|
<template #title>
|
||||||
v-for="entry in item.entries"
|
<div :class="{ notify: isNavItemNotified(item) }">
|
||||||
:key="entry.label"
|
<i :class="item.icon"></i>
|
||||||
type="button"
|
<span v-show="!isCollapsed">{{
|
||||||
:class="['nav-menu-popover__menu-item', { notify: isEntryNotified(entry) }]"
|
item.titleIsCustom ? item.title : t(item.title || '')
|
||||||
@click="handleSubmenuClick(entry, item.index)">
|
}}</span>
|
||||||
<i v-if="entry.icon" :class="entry.icon" class="nav-menu-popover__menu-icon"></i>
|
</div>
|
||||||
<span class="nav-menu-popover__menu-label">{{ t(entry.label) }}</span>
|
</template>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template #reference>
|
|
||||||
<el-menu-item
|
<el-menu-item
|
||||||
:index="item.index"
|
v-for="entry in item.children"
|
||||||
:class="{ notify: isNavItemNotified(item) }"
|
:key="entry.index"
|
||||||
@click="handleMenuItemClick(item)">
|
:index="entry.index"
|
||||||
<i :class="item.icon"></i>
|
class="pl-8!"
|
||||||
<template #title v-if="item.tooltip">
|
:class="{ notify: isEntryNotified(entry) }"
|
||||||
<span>{{ item.tooltipIsCustom ? item.tooltip : t(item.tooltip) }}</span>
|
@click="handleSubmenuClick(entry, item.index)">
|
||||||
|
<i v-show="entry.icon" :class="entry.icon"></i>
|
||||||
|
<template #title>
|
||||||
|
<span>{{ t(entry.label) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</template>
|
</el-sub-menu>
|
||||||
</el-popover>
|
</template>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
<el-divider style="width: calc(100% - 18px); margin-left: 9px"></el-divider>
|
|
||||||
<NativeTooltip :content="t('prompt.direct_access_omni.header')" placement="right">
|
|
||||||
<div class="bottom-button" @click="directAccessPaste"><i class="ri-compass-3-line"></i></div>
|
|
||||||
</NativeTooltip>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-menu-container-bottom">
|
<div class="nav-menu-container-bottom mb-4">
|
||||||
<NativeTooltip v-if="branch === 'Nightly'" :show-after="150" :content="'Feedback'" placement="right">
|
<el-tooltip
|
||||||
|
v-if="branch === 'Nightly'"
|
||||||
|
:show-after="150"
|
||||||
|
:content="'Feedback'"
|
||||||
|
:disabled="!isCollapsed"
|
||||||
|
placement="right">
|
||||||
<div
|
<div
|
||||||
class="bottom-button"
|
class="bottom-button"
|
||||||
id="feedback"
|
id="feedback"
|
||||||
@click="!sentryErrorReporting && setSentryErrorReporting()">
|
@click="!sentryErrorReporting && setSentryErrorReporting()">
|
||||||
<i class="ri-feedback-line"></i>
|
<i class="ri-feedback-line"></i>
|
||||||
|
<span v-show="!isCollapsed" class="bottom-button__label">Feedback</span>
|
||||||
</div>
|
</div>
|
||||||
</NativeTooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<el-popover
|
<el-popover
|
||||||
v-model:visible="supportMenuVisible"
|
v-model:visible="supportMenuVisible"
|
||||||
placement="right"
|
placement="right"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
popper-style="padding:4px;border-radius:8px;"
|
popper-style="padding:4px;border-radius:8px;"
|
||||||
:offset="4"
|
:offset="-10"
|
||||||
:show-arrow="false"
|
:show-arrow="false"
|
||||||
:width="200"
|
:width="200"
|
||||||
:hide-after="0">
|
:hide-after="0">
|
||||||
@@ -119,11 +110,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<div>
|
<div>
|
||||||
<NativeTooltip :show-after="150" :content="t('nav_tooltip.help_support')" placement="right">
|
<el-tooltip
|
||||||
|
:show-after="150"
|
||||||
|
:content="t('nav_tooltip.help_support')"
|
||||||
|
placement="right"
|
||||||
|
:disabled="!isCollapsed">
|
||||||
<div class="bottom-button">
|
<div class="bottom-button">
|
||||||
<i class="ri-question-line"></i>
|
<i class="ri-question-line"></i>
|
||||||
|
<span v-show="!isCollapsed" class="bottom-button__label">{{
|
||||||
|
t('nav_tooltip.help_support')
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</NativeTooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
@@ -133,7 +131,7 @@
|
|||||||
placement="right"
|
placement="right"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
popper-style="padding:4px;border-radius:8px;"
|
popper-style="padding:4px;border-radius:8px;"
|
||||||
:offset="4"
|
:offset="-10"
|
||||||
:show-arrow="false"
|
:show-arrow="false"
|
||||||
:width="200"
|
:width="200"
|
||||||
:hide-after="0">
|
:hide-after="0">
|
||||||
@@ -143,7 +141,7 @@
|
|||||||
<div class="nav-menu-settings__meta">
|
<div class="nav-menu-settings__meta">
|
||||||
<span class="nav-menu-settings__title" @click="openGithub"
|
<span class="nav-menu-settings__title" @click="openGithub"
|
||||||
>VRCX
|
>VRCX
|
||||||
<i class="ri-heart-3-fill" style="color: #64cd8a; font-size: 14px"></i>
|
<i class="ri-heart-3-fill nav-menu-settings__heart"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="nav-menu-settings__version">{{ version }}</span>
|
<span class="nav-menu-settings__version">{{ version }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,7 +168,7 @@
|
|||||||
:class="{ 'is-active': themeMode === theme }"
|
:class="{ 'is-active': themeMode === theme }"
|
||||||
@click="handleThemeSelect(theme)">
|
@click="handleThemeSelect(theme)">
|
||||||
<span class="nav-menu-theme__label">{{ themeDisplayName(theme) }}</span>
|
<span class="nav-menu-theme__label">{{ themeDisplayName(theme) }}</span>
|
||||||
<span v-if="themeMode === theme" class="nav-menu-theme__check">✔</span>
|
<span v-if="themeMode === theme" class="nav-menu-theme__check">✓</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
@@ -190,9 +188,24 @@
|
|||||||
<template #reference>
|
<template #reference>
|
||||||
<div class="bottom-button">
|
<div class="bottom-button">
|
||||||
<i class="ri-settings-3-line"></i>
|
<i class="ri-settings-3-line"></i>
|
||||||
|
<span v-show="!isCollapsed" class="bottom-button__label">{{
|
||||||
|
t('nav_tooltip.manage')
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
<el-tooltip
|
||||||
|
:show-after="150"
|
||||||
|
:content="t('nav_tooltip.expand_menu')"
|
||||||
|
:disabled="!isCollapsed"
|
||||||
|
placement="right">
|
||||||
|
<div class="bottom-button" @click="toggleNavCollapse">
|
||||||
|
<i class="ri-side-bar-line"></i>
|
||||||
|
<span v-show="!isCollapsed" class="bottom-button__label">{{
|
||||||
|
t('nav_tooltip.collapse_menu')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -215,9 +228,9 @@
|
|||||||
useAdvancedSettingsStore,
|
useAdvancedSettingsStore,
|
||||||
useAppearanceSettingsStore,
|
useAppearanceSettingsStore,
|
||||||
useAuthStore,
|
useAuthStore,
|
||||||
useGameStore,
|
|
||||||
useSearchStore,
|
useSearchStore,
|
||||||
useUiStore,
|
useUiStore,
|
||||||
|
useUserStore,
|
||||||
useVRCXUpdaterStore
|
useVRCXUpdaterStore
|
||||||
} from '../stores';
|
} from '../stores';
|
||||||
import { THEME_CONFIG, links, navDefinitions } from '../shared/constants';
|
import { THEME_CONFIG, links, navDefinitions } from '../shared/constants';
|
||||||
@@ -226,8 +239,6 @@
|
|||||||
|
|
||||||
import configRepository from '../service/config';
|
import configRepository from '../service/config';
|
||||||
|
|
||||||
import 'remixicon/fonts/remixicon.css';
|
|
||||||
|
|
||||||
const CustomNavDialog = defineAsyncComponent(() => import('./dialogs/CustomNavDialog.vue'));
|
const CustomNavDialog = defineAsyncComponent(() => import('./dialogs/CustomNavDialog.vue'));
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
@@ -257,26 +268,13 @@
|
|||||||
},
|
},
|
||||||
{ type: 'item', key: 'notification' },
|
{ type: 'item', key: 'notification' },
|
||||||
{ type: 'item', key: 'charts' },
|
{ type: 'item', key: 'charts' },
|
||||||
{ type: 'item', key: 'tools' }
|
{ type: 'item', key: 'tools' },
|
||||||
|
{ type: 'item', key: 'direct-access' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const navDefinitionMap = new Map(navDefinitions.map((item) => [item.key, item]));
|
const navDefinitionMap = new Map(navDefinitions.map((item) => [item.key, item]));
|
||||||
const DEFAULT_FOLDER_ICON = 'ri-menu-fold-line';
|
const DEFAULT_FOLDER_ICON = 'ri-menu-fold-line';
|
||||||
|
|
||||||
const navPopoverWidth = 250;
|
|
||||||
const navPopoverStyle = {
|
|
||||||
zIndex: 500,
|
|
||||||
borderRadius: '0',
|
|
||||||
border: '1px solid var(--el-border-color)',
|
|
||||||
borderLeft: 'none',
|
|
||||||
borderBottom: 'none',
|
|
||||||
borderTop: 'none',
|
|
||||||
boxShadow: '0 8px 20px rgba(0,0,0,0.05)',
|
|
||||||
padding: '0',
|
|
||||||
background: 'var(--el-bg-color)',
|
|
||||||
height: '100vh'
|
|
||||||
};
|
|
||||||
|
|
||||||
const VRCXUpdaterStore = useVRCXUpdaterStore();
|
const VRCXUpdaterStore = useVRCXUpdaterStore();
|
||||||
const { pendingVRCXUpdate, pendingVRCXInstall, updateInProgress, updateProgress, branch, appVersion } =
|
const { pendingVRCXUpdate, pendingVRCXInstall, updateInProgress, updateProgress, branch, appVersion } =
|
||||||
storeToRefs(VRCXUpdaterStore);
|
storeToRefs(VRCXUpdaterStore);
|
||||||
@@ -288,18 +286,19 @@
|
|||||||
const { setSentryErrorReporting } = useAdvancedSettingsStore();
|
const { setSentryErrorReporting } = useAdvancedSettingsStore();
|
||||||
const { logout } = useAuthStore();
|
const { logout } = useAuthStore();
|
||||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||||
const { themeMode } = storeToRefs(appearanceSettingsStore);
|
const { themeMode, isNavCollapsed: isCollapsed } = storeToRefs(appearanceSettingsStore);
|
||||||
const { isSteamVRRunning } = storeToRefs(useGameStore());
|
const userStore = useUserStore();
|
||||||
|
const { currentUser } = storeToRefs(userStore);
|
||||||
|
const { showUserDialog } = userStore;
|
||||||
|
|
||||||
const settingsMenuVisible = ref(false);
|
const settingsMenuVisible = ref(false);
|
||||||
const themeMenuVisible = ref(false);
|
const themeMenuVisible = ref(false);
|
||||||
const supportMenuVisible = ref(false);
|
const supportMenuVisible = ref(false);
|
||||||
const navMenuRef = ref(null);
|
const navMenuRef = ref(null);
|
||||||
const navPopoverRefs = new Map();
|
|
||||||
const navLayout = ref([]);
|
const navLayout = ref([]);
|
||||||
const navLayoutReady = ref(false);
|
const navLayoutReady = ref(false);
|
||||||
|
|
||||||
const navMenuItems = computed(() => {
|
const menuItems = computed(() => {
|
||||||
const items = [];
|
const items = [];
|
||||||
navLayout.value.forEach((entry) => {
|
navLayout.value.forEach((entry) => {
|
||||||
if (entry.type === 'item') {
|
if (entry.type === 'item') {
|
||||||
@@ -310,7 +309,7 @@
|
|||||||
items.push({
|
items.push({
|
||||||
...definition,
|
...definition,
|
||||||
index: definition.key,
|
index: definition.key,
|
||||||
tooltipIsCustom: false,
|
title: definition.tooltip || definition.labelKey,
|
||||||
titleIsCustom: false
|
titleIsCustom: false
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -324,7 +323,6 @@
|
|||||||
items.push({
|
items.push({
|
||||||
...definition,
|
...definition,
|
||||||
index: definition.key,
|
index: definition.key,
|
||||||
tooltipIsCustom: false,
|
|
||||||
titleIsCustom: false
|
titleIsCustom: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -334,83 +332,41 @@
|
|||||||
const folderEntries = folderDefinitions.map((definition) => ({
|
const folderEntries = folderDefinitions.map((definition) => ({
|
||||||
label: definition.labelKey,
|
label: definition.labelKey,
|
||||||
routeName: definition.routeName,
|
routeName: definition.routeName,
|
||||||
key: definition.key,
|
index: definition.key,
|
||||||
icon: definition.icon
|
icon: definition.icon,
|
||||||
|
action: definition.action
|
||||||
}));
|
}));
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
index: entry.id,
|
index: entry.id,
|
||||||
icon: entry.icon || DEFAULT_FOLDER_ICON,
|
icon: entry.icon || DEFAULT_FOLDER_ICON,
|
||||||
tooltip: entry.name?.trim() || t('nav_menu.custom_nav.folder_name_placeholder'),
|
|
||||||
tooltipIsCustom: true,
|
|
||||||
title: entry.name?.trim() || t('nav_menu.custom_nav.folder_name_placeholder'),
|
title: entry.name?.trim() || t('nav_menu.custom_nav.folder_name_placeholder'),
|
||||||
titleIsCustom: true,
|
titleIsCustom: true,
|
||||||
entries: folderEntries
|
children: folderEntries
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return items;
|
return items;
|
||||||
});
|
});
|
||||||
|
|
||||||
const folderCyclePointers = new Map();
|
|
||||||
|
|
||||||
const navigateToFolderEntry = (folderIndex, entry) => {
|
|
||||||
if (!entry) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (entry.routeName) {
|
|
||||||
handleRouteChange(entry.routeName, folderIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (entry.path) {
|
|
||||||
router.push(entry.path);
|
|
||||||
if (folderIndex) {
|
|
||||||
navMenuRef.value?.updateActiveIndex(folderIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFolderCycleNavigation = (item) => {
|
|
||||||
if (!item?.entries?.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entries = item.entries.filter((entry) => Boolean(entry?.routeName || entry?.path));
|
|
||||||
if (!entries.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let pointer = folderCyclePointers.get(item.index) ?? 0;
|
|
||||||
if (pointer >= entries.length || pointer < 0) {
|
|
||||||
pointer = 0;
|
|
||||||
}
|
|
||||||
const entry = entries[pointer];
|
|
||||||
folderCyclePointers.set(item.index, (pointer + 1) % entries.length);
|
|
||||||
navigateToFolderEntry(item.index, entry);
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeMenuIndex = computed(() => {
|
const activeMenuIndex = computed(() => {
|
||||||
const currentRouteName = router.currentRoute.value?.name;
|
const currentRoute = router.currentRoute.value;
|
||||||
if (!currentRouteName) {
|
const currentRouteName = currentRoute?.name;
|
||||||
const firstEntry = navLayout.value[0];
|
const navKey = currentRoute?.meta?.navKey || currentRouteName;
|
||||||
if (!firstEntry) {
|
if (!navKey) {
|
||||||
return 'feed';
|
return getFirstNavRoute(navLayout.value) || 'feed';
|
||||||
}
|
|
||||||
return firstEntry.type === 'folder' ? firstEntry.id : firstEntry.key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entry of navLayout.value) {
|
for (const entry of navLayout.value) {
|
||||||
if (entry.type === 'item' && entry.key === currentRouteName) {
|
if (entry.type === 'item' && entry.key === navKey) {
|
||||||
return entry.key;
|
return entry.key;
|
||||||
}
|
}
|
||||||
if (entry.type === 'folder' && entry.items?.includes(currentRouteName)) {
|
if (entry.type === 'folder' && entry.items?.includes(navKey)) {
|
||||||
return entry.id;
|
return navKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallback = navLayout.value[0];
|
return getFirstNavRoute(navLayout.value) || 'feed';
|
||||||
if (!fallback) {
|
|
||||||
return 'feed';
|
|
||||||
}
|
|
||||||
return fallback.type === 'folder' ? fallback.id : fallback.key;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const version = computed(() => appVersion.value?.split('VRCX ')?.[1] || '-');
|
const version = computed(() => appVersion.value?.split('VRCX ')?.[1] || '-');
|
||||||
@@ -448,6 +404,10 @@
|
|||||||
|
|
||||||
const generateFolderId = () => `nav-folder-${dayjs().toISOString()}-${Math.random().toString().slice(2, 4)}`;
|
const generateFolderId = () => `nav-folder-${dayjs().toISOString()}-${Math.random().toString().slice(2, 4)}`;
|
||||||
|
|
||||||
|
const showCurrentUserDialog = () => {
|
||||||
|
showUserDialog(currentUser.value?.id);
|
||||||
|
};
|
||||||
|
|
||||||
const sanitizeLayout = (layout) => {
|
const sanitizeLayout = (layout) => {
|
||||||
const usedKeys = new Set();
|
const usedKeys = new Set();
|
||||||
const normalized = [];
|
const normalized = [];
|
||||||
@@ -627,52 +587,47 @@
|
|||||||
if (notifiedMenus.value.includes(item.index)) {
|
if (notifiedMenus.value.includes(item.index)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (item.entries?.length) {
|
if (item.children?.length) {
|
||||||
return item.entries.some((entry) => isEntryNotified(entry));
|
return item.children.some((entry) => isEntryNotified(entry));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setNavPopoverRef = (el, index) => {
|
const closeNavFlyouts = () => {
|
||||||
if (!index) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (el) {
|
|
||||||
navPopoverRefs.set(index, el);
|
|
||||||
} else {
|
|
||||||
navPopoverRefs.delete(index);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeNavPopover = (index) => {
|
|
||||||
navPopoverRefs.get(index)?.hide?.();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmenuClick = (entry, index) => {
|
|
||||||
if (!entry) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entries = navMenuItems.value.find((item) => item.index === index)?.entries || [];
|
|
||||||
const indexOfEntry = entries.findIndex((e) => e.label === entry.label);
|
|
||||||
folderCyclePointers.set(index, (indexOfEntry + 1) % entries.length);
|
|
||||||
|
|
||||||
if (entry.routeName) {
|
|
||||||
handleRouteChange(entry.routeName, index || entry.routeName);
|
|
||||||
} else if (entry.path) {
|
|
||||||
router.push(entry.path);
|
|
||||||
if (index) {
|
|
||||||
navMenuRef.value?.updateActiveIndex(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closeNavPopover(index);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubMenuBeforeEnter = () => {
|
|
||||||
settingsMenuVisible.value = false;
|
settingsMenuVisible.value = false;
|
||||||
supportMenuVisible.value = false;
|
supportMenuVisible.value = false;
|
||||||
themeMenuVisible.value = false;
|
themeMenuVisible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const triggerNavAction = (entry, navIndex = entry?.index) => {
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.action === 'direct-access') {
|
||||||
|
closeNavFlyouts();
|
||||||
|
directAccessPaste();
|
||||||
|
if (navIndex) {
|
||||||
|
navMenuRef.value?.updateActiveIndex(navIndex);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.routeName) {
|
||||||
|
handleRouteChange(entry.routeName, navIndex);
|
||||||
|
closeNavFlyouts();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.path) {
|
||||||
|
router.push(entry.path);
|
||||||
|
if (navIndex) {
|
||||||
|
navMenuRef.value?.updateActiveIndex(navIndex);
|
||||||
|
}
|
||||||
|
closeNavFlyouts();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleRouteChange = (routeName, navIndex = routeName) => {
|
const handleRouteChange = (routeName, navIndex = routeName) => {
|
||||||
if (!routeName) {
|
if (!routeName) {
|
||||||
return;
|
return;
|
||||||
@@ -697,17 +652,23 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const getFirstNavRoute = (layout) => {
|
function getFirstNavRoute(layout) {
|
||||||
for (const entry of layout) {
|
for (const entry of layout) {
|
||||||
if (entry.type === 'item') {
|
if (entry.type === 'item') {
|
||||||
return entry.key;
|
const definition = navDefinitionMap.get(entry.key);
|
||||||
|
if (definition?.routeName) {
|
||||||
|
return definition.routeName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (entry.type === 'folder' && entry.items?.length) {
|
if (entry.type === 'folder' && entry.items?.length) {
|
||||||
return entry.items[0];
|
const definition = entry.items.map((key) => navDefinitionMap.get(key)).find((def) => def?.routeName);
|
||||||
|
if (definition?.routeName) {
|
||||||
|
return definition.routeName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
}
|
||||||
|
|
||||||
let hasNavigatedToInitialRoute = false;
|
let hasNavigatedToInitialRoute = false;
|
||||||
const navigateToFirstNavEntry = () => {
|
const navigateToFirstNavEntry = () => {
|
||||||
@@ -724,15 +685,17 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmenuClick = (entry, index) => {
|
||||||
|
const navIndex = index || entry?.index;
|
||||||
|
triggerNavAction(entry, navIndex);
|
||||||
|
};
|
||||||
|
|
||||||
const handleMenuItemClick = (item) => {
|
const handleMenuItemClick = (item) => {
|
||||||
if (!item) {
|
triggerNavAction(item, item?.index);
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
if (item.entries?.length) {
|
const toggleNavCollapse = () => {
|
||||||
handleFolderCycleNavigation(item);
|
appearanceSettingsStore.toggleNavCollapsed();
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleRouteChange(item.routeName, item.index);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -755,146 +718,147 @@
|
|||||||
:deep(.el-divider) {
|
:deep(.el-divider) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-menu-container {
|
.nav-menu-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 240px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 0 0 240px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
z-index: 600;
|
z-index: 600;
|
||||||
background-color: var(--el-bg-color);
|
background-color: var(--el-bg-color-page);
|
||||||
border-right: 1px solid var(--el-border-color);
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
.el-menu {
|
backdrop-filter: blur(14px) saturate(130%);
|
||||||
background: 0;
|
}
|
||||||
border: 0;
|
|
||||||
}
|
.nav-menu-body {
|
||||||
.el-menu-item i[class*='ri-'] {
|
display: flex;
|
||||||
font-size: 19px;
|
flex-direction: column;
|
||||||
width: 24px;
|
flex: 1;
|
||||||
height: 24px;
|
overflow: hidden auto;
|
||||||
display: inline-flex;
|
align-items: center;
|
||||||
align-items: center;
|
}
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
.nav-menu {
|
||||||
vertical-align: middle;
|
background: transparent;
|
||||||
}
|
border: 0;
|
||||||
.bottom-button {
|
width: 100%;
|
||||||
font-size: 19px;
|
}
|
||||||
width: 64px;
|
|
||||||
height: 56px;
|
.nav-menu :deep(.el-menu-item),
|
||||||
display: inline-flex;
|
.nav-menu :deep(.el-sub-menu__title) {
|
||||||
align-items: center;
|
height: 46px;
|
||||||
justify-content: center;
|
line-height: 46px;
|
||||||
text-align: center;
|
display: flex;
|
||||||
vertical-align: middle;
|
align-items: center;
|
||||||
cursor: pointer;
|
column-gap: 10px;
|
||||||
}
|
font-size: 13px;
|
||||||
.bottom-button:hover {
|
padding: 0 20px !important;
|
||||||
background-color: var(--el-menu-hover-bg-color);
|
}
|
||||||
transition:
|
|
||||||
border-color var(--el-transition-duration),
|
.nav-menu :deep(.el-menu-item i[class*='ri-']),
|
||||||
background-color var(--el-transition-duration),
|
.nav-menu :deep(.el-sub-menu__title i[class*='ri-']) {
|
||||||
color var(--el-transition-duration);
|
font-size: 19px;
|
||||||
}
|
width: 24px;
|
||||||
.nav-menu-container-bottom {
|
height: 24px;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu :deep(.el-sub-menu__title > div) {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu :deep(.el-sub-menu__icon-arrow) {
|
||||||
|
right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-button {
|
||||||
|
font-size: 19px;
|
||||||
|
width: 100%;
|
||||||
|
height: 46px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 20px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
& > span {
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-menu-popover {
|
.bottom-button i {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-button__label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-button:hover {
|
||||||
|
background-color: var(--el-menu-hover-bg-color);
|
||||||
|
transition:
|
||||||
|
border-color var(--el-transition-duration),
|
||||||
|
background-color var(--el-transition-duration),
|
||||||
|
color var(--el-transition-duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-container-bottom {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
}
|
||||||
min-width: 240px;
|
|
||||||
background-color: var(--el-bg-color);
|
|
||||||
border-left: 1px solid var(--el-border-color);
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.nav-menu-popover__header {
|
.nav-menu-container.is-collapsed .nav-menu :deep(.el-menu-item),
|
||||||
display: inline-flex;
|
.nav-menu-container.is-collapsed .nav-menu :deep(.el-sub-menu__title) {
|
||||||
align-items: center;
|
column-gap: 0;
|
||||||
gap: 10px;
|
justify-content: center;
|
||||||
min-height: 52px;
|
padding: 0;
|
||||||
padding: 0 20px;
|
}
|
||||||
border-bottom: 1px solid var(--el-border-color-light, var(--el-border-color));
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-popover__header i {
|
.nav-menu-container.is-collapsed {
|
||||||
font-size: 18px;
|
width: 64px;
|
||||||
color: var(--el-color-primary);
|
flex-basis: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-menu-popover__menu {
|
.nav-menu-container.is-collapsed .nav-menu :deep(.el-sub-menu__title > div) {
|
||||||
display: flex;
|
gap: 0;
|
||||||
flex-direction: column;
|
}
|
||||||
flex: 1;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 12px 12px 16px;
|
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-popover__menu::-webkit-scrollbar {
|
.nav-menu-container.is-collapsed .bottom-button {
|
||||||
width: 6px;
|
width: 100%;
|
||||||
}
|
justify-content: center;
|
||||||
|
gap: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-menu-popover__menu::-webkit-scrollbar-thumb {
|
:deep(.el-menu-item .el-menu-tooltip__trigger) {
|
||||||
background-color: rgba(0, 0, 0, 0.18);
|
justify-content: center;
|
||||||
border-radius: 3px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-popover__menu::-webkit-scrollbar-track {
|
:deep(.el-button.is-text:not(.is-disabled):hover) {
|
||||||
background: transparent;
|
background-color: var(--el-menu-hover-bg-color);
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-popover__menu-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
text-align: left;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
font-size: 13px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color var(--el-transition-duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-popover__menu-item.notify::after {
|
|
||||||
content: '';
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--el-text-color-primary);
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-popover__menu-item:hover {
|
|
||||||
background-color: var(--el-menu-hover-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-popover__menu-item:focus-visible {
|
|
||||||
outline: 2px solid var(--el-color-primary);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-popover__menu-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-popover__menu-label {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-menu-settings {
|
.nav-menu-settings {
|
||||||
@@ -930,6 +894,11 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-menu-settings__heart {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-menu-settings__version {
|
.nav-menu-settings__version {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
@@ -942,8 +911,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-regular);
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: background-color var(--el-transition-duration);
|
transition: background-color var(--el-transition-duration);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -962,7 +931,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-menu-settings__item--danger:hover {
|
.nav-menu-settings__item--danger:hover {
|
||||||
background-color: rgba(245, 108, 108, 0.18);
|
background-color: color-mix(in oklch, var(--el-color-danger) 18%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -971,23 +940,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
.nav-menu-support__search {
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--el-fill-color-light);
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-support__heading {
|
|
||||||
padding: 4px 12px 0;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-support__section {
|
.nav-menu-support__section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1015,7 +967,6 @@
|
|||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
transition: background-color var(--el-transition-duration);
|
transition: background-color var(--el-transition-duration);
|
||||||
@@ -1031,18 +982,4 @@
|
|||||||
background-color: var(--el-menu-hover-bg-color);
|
background-color: var(--el-menu-hover-bg-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.nav-menu-slide-enter-active),
|
|
||||||
:global(.nav-menu-slide-leave-active) {
|
|
||||||
transition:
|
|
||||||
opacity 0.1s ease,
|
|
||||||
transform 0.1s ease;
|
|
||||||
transform-origin: left center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.nav-menu-slide-enter-from),
|
|
||||||
:global(.nav-menu-slide-leave-to) {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-12px);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
class="x-dialog x-avatar-dialog"
|
class="x-dialog x-avatar-dialog"
|
||||||
v-model="avatarDialog.visible"
|
v-model="avatarDialog.visible"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
width="700px">
|
top="10vh"
|
||||||
|
width="930px">
|
||||||
<div v-loading="avatarDialog.loading">
|
<div v-loading="avatarDialog.loading">
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
<img
|
<img
|
||||||
@@ -246,11 +247,7 @@
|
|||||||
style="margin-left: 5px"
|
style="margin-left: 5px"
|
||||||
@click="selectAvatarWithoutConfirmation(avatarDialog.id)"></el-button>
|
@click="selectAvatarWithoutConfirmation(avatarDialog.id)"></el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-dropdown
|
<el-dropdown trigger="click" style="margin-left: 5px" @command="avatarDialogCommand">
|
||||||
trigger="click"
|
|
||||||
size="small"
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@command="avatarDialogCommand">
|
|
||||||
<el-button
|
<el-button
|
||||||
:type="avatarDialog.isBlocked ? 'danger' : 'default'"
|
:type="avatarDialog.isBlocked ? 'danger' : 'default'"
|
||||||
:icon="MoreFilled"
|
:icon="MoreFilled"
|
||||||
|
|||||||
@@ -321,7 +321,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style scoped>
|
||||||
.img-size {
|
.img-size {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 375px;
|
height: 375px;
|
||||||
|
|||||||
@@ -68,12 +68,12 @@
|
|||||||
<span
|
<span
|
||||||
v-if="avatar.releaseStatus === 'public'"
|
v-if="avatar.releaseStatus === 'public'"
|
||||||
class="extra"
|
class="extra"
|
||||||
style="color: #67c23a"
|
style="color: var(--el-color-success)"
|
||||||
v-text="avatar.releaseStatus"></span>
|
v-text="avatar.releaseStatus"></span>
|
||||||
<span
|
<span
|
||||||
v-else-if="avatar.releaseStatus === 'private'"
|
v-else-if="avatar.releaseStatus === 'private'"
|
||||||
class="extra"
|
class="extra"
|
||||||
style="color: #f56c6c"
|
style="color: var(--el-color-danger)"
|
||||||
v-text="avatar.releaseStatus"></span>
|
v-text="avatar.releaseStatus"></span>
|
||||||
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
|
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
|
||||||
<span class="extra" v-text="avatarTagStrings.get(avatar.id)"></span>
|
<span class="extra" v-text="avatarTagStrings.get(avatar.id)"></span>
|
||||||
|
|||||||
@@ -329,8 +329,9 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
const openFolderEditor = (index) => {
|
const openFolderEditor = (index) => {
|
||||||
folderEditor.isEditing = !!index;
|
const isEditing = index !== undefined && index !== null;
|
||||||
folderEditor.index = folderEditor.isEditing ? index : -1;
|
folderEditor.isEditing = isEditing;
|
||||||
|
folderEditor.index = isEditing ? index : -1;
|
||||||
if (folderEditor.isEditing) {
|
if (folderEditor.isEditing) {
|
||||||
const entry = localLayout.value[index];
|
const entry = localLayout.value[index];
|
||||||
folderEditor.data = {
|
folderEditor.data = {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
:z-index="groupDialogIndex"
|
:z-index="groupDialogIndex"
|
||||||
v-model="groupDialog.visible"
|
v-model="groupDialog.visible"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
width="770px"
|
top="10vh"
|
||||||
|
width="930px"
|
||||||
class="x-dialog x-group-dialog">
|
class="x-dialog x-group-dialog">
|
||||||
<div v-loading="groupDialog.loading" class="group-body">
|
<div v-loading="groupDialog.loading" class="group-body">
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
@@ -258,11 +259,7 @@
|
|||||||
@click="joinGroup(groupDialog.id)"></el-button>
|
@click="joinGroup(groupDialog.id)"></el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<el-dropdown
|
<el-dropdown trigger="click" style="margin-left: 5px" @command="groupDialogCommand">
|
||||||
trigger="click"
|
|
||||||
size="small"
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@command="groupDialogCommand">
|
|
||||||
<el-button
|
<el-button
|
||||||
:type="groupDialog.ref.membershipStatus === 'userblocked' ? 'danger' : 'default'"
|
:type="groupDialog.ref.membershipStatus === 'userblocked' ? 'danger' : 'default'"
|
||||||
:icon="MoreFilled"
|
:icon="MoreFilled"
|
||||||
@@ -616,7 +613,8 @@
|
|||||||
<span class="name">{{ t('dialog.group.info.links') }}</span>
|
<span class="name">{{ t('dialog.group.info.links') }}</span>
|
||||||
<div
|
<div
|
||||||
v-if="groupDialog.ref.links && groupDialog.ref.links.length > 0"
|
v-if="groupDialog.ref.links && groupDialog.ref.links.length > 0"
|
||||||
style="margin-top: 5px">
|
style="margin-top: 5px"
|
||||||
|
class="flex">
|
||||||
<template v-for="(link, index) in groupDialog.ref.links" :key="index">
|
<template v-for="(link, index) in groupDialog.ref.links" :key="index">
|
||||||
<el-tooltip v-if="link">
|
<el-tooltip v-if="link">
|
||||||
<template #content>
|
<template #content>
|
||||||
@@ -1086,7 +1084,6 @@
|
|||||||
<el-tabs
|
<el-tabs
|
||||||
v-model="groupDialogGalleryCurrentName"
|
v-model="groupDialogGalleryCurrentName"
|
||||||
v-loading="isGroupGalleryLoading"
|
v-loading="isGroupGalleryLoading"
|
||||||
type="card"
|
|
||||||
style="margin-top: 10px">
|
style="margin-top: 10px">
|
||||||
<template v-for="(gallery, index) in groupDialog.ref.galleries" :key="index">
|
<template v-for="(gallery, index) in groupDialog.ref.galleries" :key="index">
|
||||||
<el-tab-pane>
|
<el-tab-pane>
|
||||||
@@ -1839,7 +1836,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style scoped>
|
||||||
.time-group-container {
|
.time-group-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
width="90vw">
|
width="90vw">
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ groupMemberModeration.groupRef.name }}</h3>
|
<h3>{{ groupMemberModeration.groupRef.name }}</h3>
|
||||||
<el-tabs type="card" style="height: 100%">
|
<el-tabs style="height: 100%">
|
||||||
<el-tab-pane :label="t('dialog.group_member_moderation.members')">
|
<el-tab-pane :label="t('dialog.group_member_moderation.members')">
|
||||||
<div style="margin-top: 10px">
|
<div style="margin-top: 10px">
|
||||||
<el-button
|
<el-button
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:title="t('dialog.new_instance.header')"
|
:title="t('dialog.new_instance.header')"
|
||||||
width="650px"
|
width="650px"
|
||||||
append-to-body>
|
append-to-body>
|
||||||
<el-tabs v-model="newInstanceDialog.selectedTab" type="card" @tab-click="newInstanceTabClick">
|
<el-tabs v-model="newInstanceDialog.selectedTab" @tab-click="newInstanceTabClick">
|
||||||
<el-tab-pane name="Normal" :label="t('dialog.new_instance.normal')">
|
<el-tab-pane name="Normal" :label="t('dialog.new_instance.normal')">
|
||||||
<el-form :model="newInstanceDialog" label-width="150px">
|
<el-form :model="newInstanceDialog" label-width="150px">
|
||||||
<el-form-item :label="t('dialog.new_instance.access_type')">
|
<el-form-item :label="t('dialog.new_instance.access_type')">
|
||||||
|
|||||||
@@ -63,14 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button
|
<el-button size="small" @click="showGalleryPage">{{ t('dialog.boop_dialog.emoji_manager') }}</el-button>
|
||||||
size="small"
|
|
||||||
@click="
|
|
||||||
redirectToToolsTab();
|
|
||||||
showGalleryDialog();
|
|
||||||
"
|
|
||||||
>{{ t('dialog.boop_dialog.emoji_manager') }}</el-button
|
|
||||||
>
|
|
||||||
<el-button size="small" @click="closeDialog">{{ t('dialog.boop_dialog.cancel') }}</el-button>
|
<el-button size="small" @click="closeDialog">{{ t('dialog.boop_dialog.cancel') }}</el-button>
|
||||||
<el-button size="small" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{
|
<el-button size="small" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{
|
||||||
t('dialog.boop_dialog.send')
|
t('dialog.boop_dialog.send')
|
||||||
@@ -87,7 +80,6 @@
|
|||||||
import { notificationRequest, userRequest } from '../../api';
|
import { notificationRequest, userRequest } from '../../api';
|
||||||
import { miscRequest } from '../../api';
|
import { miscRequest } from '../../api';
|
||||||
import { photonEmojis } from '../../shared/constants/photon.js';
|
import { photonEmojis } from '../../shared/constants/photon.js';
|
||||||
import { redirectToToolsTab } from '../../shared/utils/base/ui';
|
|
||||||
import { useGalleryStore } from '../../stores';
|
import { useGalleryStore } from '../../stores';
|
||||||
import { useNotificationStore } from '../../stores';
|
import { useNotificationStore } from '../../stores';
|
||||||
import { useUserStore } from '../../stores/user.js';
|
import { useUserStore } from '../../stores/user.js';
|
||||||
@@ -98,7 +90,7 @@
|
|||||||
|
|
||||||
const { sendBoopDialog } = storeToRefs(useUserStore());
|
const { sendBoopDialog } = storeToRefs(useUserStore());
|
||||||
const { notificationTable } = storeToRefs(useNotificationStore());
|
const { notificationTable } = storeToRefs(useNotificationStore());
|
||||||
const { showGalleryDialog, refreshEmojiTable } = useGalleryStore();
|
const { showGalleryPage, refreshEmojiTable } = useGalleryStore();
|
||||||
const { emojiTable } = storeToRefs(useGalleryStore());
|
const { emojiTable } = storeToRefs(useGalleryStore());
|
||||||
const { isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
const { isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
@click="userDialogCommand('Add Favorite')"></el-button>
|
@click="userDialogCommand('Add Favorite')"></el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<el-dropdown trigger="click" size="small" @command="onCommand">
|
<el-dropdown trigger="click" @command="onCommand">
|
||||||
<el-button
|
<el-button
|
||||||
:type="
|
:type="
|
||||||
userDialog.incomingRequest || userDialog.outgoingRequest
|
userDialog.incomingRequest || userDialog.outgoingRequest
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
:icon="CircleCheck"
|
:icon="CircleCheck"
|
||||||
command="Moderation Unblock"
|
command="Moderation Unblock"
|
||||||
divided
|
divided
|
||||||
style="color: #f56c6c">
|
style="color: var(--el-color-danger)">
|
||||||
{{ t('dialog.user.actions.moderation_unblock') }}
|
{{ t('dialog.user.actions.moderation_unblock') }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
v-if="userDialog.isMute"
|
v-if="userDialog.isMute"
|
||||||
:icon="Microphone"
|
:icon="Microphone"
|
||||||
command="Moderation Unmute"
|
command="Moderation Unmute"
|
||||||
style="color: #f56c6c">
|
style="color: var(--el-color-danger)">
|
||||||
{{ t('dialog.user.actions.moderation_unmute') }}
|
{{ t('dialog.user.actions.moderation_unmute') }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
v-if="userDialog.isMuteChat"
|
v-if="userDialog.isMuteChat"
|
||||||
:icon="ChatLineRound"
|
:icon="ChatLineRound"
|
||||||
command="Moderation Enable Chatbox"
|
command="Moderation Enable Chatbox"
|
||||||
style="color: #f56c6c">
|
style="color: var(--el-color-danger)">
|
||||||
{{ t('dialog.user.actions.moderation_enable_chatbox') }}
|
{{ t('dialog.user.actions.moderation_enable_chatbox') }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item v-else :icon="ChatDotRound" command="Moderation Disable Chatbox">
|
<el-dropdown-item v-else :icon="ChatDotRound" command="Moderation Disable Chatbox">
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
v-if="userDialog.isInteractOff"
|
v-if="userDialog.isInteractOff"
|
||||||
:icon="Pointer"
|
:icon="Pointer"
|
||||||
command="Moderation Enable Avatar Interaction"
|
command="Moderation Enable Avatar Interaction"
|
||||||
style="color: #f56c6c">
|
style="color: var(--el-color-danger)">
|
||||||
{{ t('dialog.user.actions.moderation_enable_avatar_interaction') }}
|
{{ t('dialog.user.actions.moderation_enable_avatar_interaction') }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item v-else :icon="CircleClose" command="Moderation Disable Avatar Interaction">
|
<el-dropdown-item v-else :icon="CircleClose" command="Moderation Disable Avatar Interaction">
|
||||||
@@ -189,7 +189,11 @@
|
|||||||
{{ t('dialog.user.actions.report_hacking') }}
|
{{ t('dialog.user.actions.report_hacking') }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<template v-if="userDialog.isFriend">
|
<template v-if="userDialog.isFriend">
|
||||||
<el-dropdown-item :icon="Delete" command="Unfriend" divided style="color: #f56c6c">
|
<el-dropdown-item
|
||||||
|
:icon="Delete"
|
||||||
|
command="Unfriend"
|
||||||
|
divided
|
||||||
|
style="color: var(--el-color-danger)">
|
||||||
{{ t('dialog.user.actions.unfriend') }}
|
{{ t('dialog.user.actions.unfriend') }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
v-model="userDialog.visible"
|
v-model="userDialog.visible"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
top="10vh"
|
top="10vh"
|
||||||
width="940px">
|
width="930px">
|
||||||
<div v-loading="userDialog.loading">
|
<div v-loading="userDialog.loading">
|
||||||
<UserSummaryHeader
|
<UserSummaryHeader
|
||||||
:get-user-state-text="getUserStateText"
|
:get-user-state-text="getUserStateText"
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
style="margin-left: 5px; padding: 0"
|
style="margin-left: 5px; padding: 0"
|
||||||
@click="showBioDialog"></el-button>
|
@click="showBioDialog"></el-button>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 5px" class="flex">
|
<div style="margin-top: 5px" class="flex items-center">
|
||||||
<el-tooltip v-for="(link, index) in userDialog.ref.bioLinks" :key="index">
|
<el-tooltip v-for="(link, index) in userDialog.ref.bioLinks" :key="index">
|
||||||
<template #content>
|
<template #content>
|
||||||
<span v-text="link"></span>
|
<span v-text="link"></span>
|
||||||
@@ -426,10 +426,13 @@
|
|||||||
<div class="x-friend-item" @click="toggleAvatarCopying">
|
<div class="x-friend-item" @click="toggleAvatarCopying">
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<span class="name">{{ t('dialog.user.info.avatar_cloning') }}</span>
|
<span class="name">{{ t('dialog.user.info.avatar_cloning') }}</span>
|
||||||
<span v-if="currentUser.allowAvatarCopying" class="extra" style="color: #67c23a">{{
|
<span
|
||||||
t('dialog.user.info.avatar_cloning_allow')
|
v-if="currentUser.allowAvatarCopying"
|
||||||
}}</span>
|
class="extra"
|
||||||
<span v-else class="extra" style="color: #f56c6c">{{
|
style="color: var(--el-color-success)"
|
||||||
|
>{{ t('dialog.user.info.avatar_cloning_allow') }}</span
|
||||||
|
>
|
||||||
|
<span v-else class="extra" style="color: var(--el-color-danger)">{{
|
||||||
t('dialog.user.info.avatar_cloning_deny')
|
t('dialog.user.info.avatar_cloning_deny')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -437,10 +440,13 @@
|
|||||||
<div class="x-friend-item" @click="toggleAllowBooping">
|
<div class="x-friend-item" @click="toggleAllowBooping">
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<span class="name">{{ t('dialog.user.info.booping') }}</span>
|
<span class="name">{{ t('dialog.user.info.booping') }}</span>
|
||||||
<span v-if="currentUser.isBoopingEnabled" class="extra" style="color: #67c23a">{{
|
<span
|
||||||
t('dialog.user.info.avatar_cloning_allow')
|
v-if="currentUser.isBoopingEnabled"
|
||||||
}}</span>
|
class="extra"
|
||||||
<span v-else class="extra" style="color: #f56c6c">{{
|
style="color: var(--el-color-success)"
|
||||||
|
>{{ t('dialog.user.info.avatar_cloning_allow') }}</span
|
||||||
|
>
|
||||||
|
<span v-else class="extra" style="color: var(--el-color-danger)">{{
|
||||||
t('dialog.user.info.avatar_cloning_deny')
|
t('dialog.user.info.avatar_cloning_deny')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -451,10 +457,10 @@
|
|||||||
<span
|
<span
|
||||||
v-if="!currentUser.hasSharedConnectionsOptOut"
|
v-if="!currentUser.hasSharedConnectionsOptOut"
|
||||||
class="extra"
|
class="extra"
|
||||||
style="color: #67c23a"
|
style="color: var(--el-color-success)"
|
||||||
>{{ t('dialog.user.info.avatar_cloning_allow') }}</span
|
>{{ t('dialog.user.info.avatar_cloning_allow') }}</span
|
||||||
>
|
>
|
||||||
<span v-else class="extra" style="color: #f56c6c">{{
|
<span v-else class="extra" style="color: var(--el-color-danger)">{{
|
||||||
t('dialog.user.info.avatar_cloning_deny')
|
t('dialog.user.info.avatar_cloning_deny')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -467,10 +473,10 @@
|
|||||||
<span
|
<span
|
||||||
v-if="userDialog.ref.allowAvatarCopying"
|
v-if="userDialog.ref.allowAvatarCopying"
|
||||||
class="extra"
|
class="extra"
|
||||||
style="color: #67c23a"
|
style="color: var(--el-color-success)"
|
||||||
>{{ t('dialog.user.info.avatar_cloning_allow') }}</span
|
>{{ t('dialog.user.info.avatar_cloning_allow') }}</span
|
||||||
>
|
>
|
||||||
<span v-else class="extra" style="color: #f56c6c">{{
|
<span v-else class="extra" style="color: var(--el-color-danger)">{{
|
||||||
t('dialog.user.info.avatar_cloning_deny')
|
t('dialog.user.info.avatar_cloning_deny')
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -634,9 +640,10 @@
|
|||||||
t('dialog.user.groups.total_count', { count: userGroups.groups.length })
|
t('dialog.user.groups.total_count', { count: userGroups.groups.length })
|
||||||
}}</span>
|
}}</span>
|
||||||
<template v-if="userDialogGroupEditMode">
|
<template v-if="userDialogGroupEditMode">
|
||||||
<span style="margin-left: 10px; color: #909399; font-size: 10px">{{
|
<span
|
||||||
t('dialog.user.groups.hold_shift')
|
style="margin-left: 10px; color: var(--el-text-color-secondary); font-size: 10px"
|
||||||
}}</span>
|
>{{ t('dialog.user.groups.hold_shift') }}</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center">
|
<div style="display: flex; align-items: center">
|
||||||
@@ -872,7 +879,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:icon="Close"
|
:icon="Close"
|
||||||
circle
|
circle
|
||||||
style="color: #f56c6c; margin-left: 5px"
|
style="color: var(--el-color-danger); margin-left: 5px"
|
||||||
@click.stop="leaveGroup(group.id)">
|
@click.stop="leaveGroup(group.id)">
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -892,7 +899,7 @@
|
|||||||
<span style="font-weight: bold; font-size: 16px">{{
|
<span style="font-weight: bold; font-size: 16px">{{
|
||||||
t('dialog.user.groups.own_groups')
|
t('dialog.user.groups.own_groups')
|
||||||
}}</span>
|
}}</span>
|
||||||
<span style="color: #909399; font-size: 12px; margin-left: 5px"
|
<span style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 5px"
|
||||||
>{{ userGroups.ownGroups.length }}/{{
|
>{{ userGroups.ownGroups.length }}/{{
|
||||||
cachedConfig?.constants?.GROUPS?.MAX_OWNED
|
cachedConfig?.constants?.GROUPS?.MAX_OWNED
|
||||||
}}</span
|
}}</span
|
||||||
@@ -936,9 +943,10 @@
|
|||||||
<span style="font-weight: bold; font-size: 16px">{{
|
<span style="font-weight: bold; font-size: 16px">{{
|
||||||
t('dialog.user.groups.mutual_groups')
|
t('dialog.user.groups.mutual_groups')
|
||||||
}}</span>
|
}}</span>
|
||||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
<span
|
||||||
userGroups.mutualGroups.length
|
style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 5px"
|
||||||
}}</span>
|
>{{ userGroups.mutualGroups.length }}</span
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="x-friend-list"
|
class="x-friend-list"
|
||||||
style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
|
style="margin-top: 10px; margin-bottom: 15px; min-height: 60px">
|
||||||
@@ -978,7 +986,7 @@
|
|||||||
<span style="font-weight: bold; font-size: 16px">{{
|
<span style="font-weight: bold; font-size: 16px">{{
|
||||||
t('dialog.user.groups.groups')
|
t('dialog.user.groups.groups')
|
||||||
}}</span>
|
}}</span>
|
||||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">
|
<span style="color: var(--el-text-color-secondary); font-size: 12px; margin-left: 5px">
|
||||||
{{ userGroups.remainingGroups.length }}
|
{{ userGroups.remainingGroups.length }}
|
||||||
<template v-if="currentUser.id === userDialog.id">
|
<template v-if="currentUser.id === userDialog.id">
|
||||||
/
|
/
|
||||||
@@ -1143,7 +1151,12 @@
|
|||||||
:class="userFavoriteWorldsStatus(list[1])">
|
:class="userFavoriteWorldsStatus(list[1])">
|
||||||
</i>
|
</i>
|
||||||
<span style="font-weight: bold; font-size: 14px" v-text="list[0]"></span>
|
<span style="font-weight: bold; font-size: 14px" v-text="list[0]"></span>
|
||||||
<span style="color: #909399; font-size: 10px; margin-left: 5px"
|
<span
|
||||||
|
style="
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 10px;
|
||||||
|
margin-left: 5px;
|
||||||
|
"
|
||||||
>{{ list[2].length }}/{{ favoriteLimits.maxFavoritesPerGroup.world }}</span
|
>{{ list[2].length }}/{{ favoriteLimits.maxFavoritesPerGroup.world }}</span
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
@@ -1272,13 +1285,13 @@
|
|||||||
<span
|
<span
|
||||||
v-if="avatar.releaseStatus === 'public'"
|
v-if="avatar.releaseStatus === 'public'"
|
||||||
class="extra"
|
class="extra"
|
||||||
style="color: #67c23a"
|
style="color: var(--el-color-success)"
|
||||||
v-text="avatar.releaseStatus">
|
v-text="avatar.releaseStatus">
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="avatar.releaseStatus === 'private'"
|
v-else-if="avatar.releaseStatus === 'private'"
|
||||||
class="extra"
|
class="extra"
|
||||||
style="color: #f56c6c"
|
style="color: var(--el-color-danger)"
|
||||||
v-text="avatar.releaseStatus">
|
v-text="avatar.releaseStatus">
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
|
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
|
||||||
@@ -1400,11 +1413,11 @@
|
|||||||
userRequest,
|
userRequest,
|
||||||
worldRequest
|
worldRequest
|
||||||
} from '../../../api';
|
} from '../../../api';
|
||||||
import { getNextDialogIndex, redirectToToolsTab } from '../../../shared/utils/base/ui';
|
|
||||||
import { processBulk, request } from '../../../service/request';
|
import { processBulk, request } from '../../../service/request';
|
||||||
import { userDialogGroupSortingOptions, userDialogMutualFriendSortingOptions } from '../../../shared/constants';
|
import { userDialogGroupSortingOptions, userDialogMutualFriendSortingOptions } from '../../../shared/constants';
|
||||||
import { userDialogWorldOrderOptions, userDialogWorldSortingOptions } from '../../../shared/constants/';
|
import { userDialogWorldOrderOptions, userDialogWorldSortingOptions } from '../../../shared/constants/';
|
||||||
import { database } from '../../../service/database';
|
import { database } from '../../../service/database';
|
||||||
|
import { getNextDialogIndex } from '../../../shared/utils/base/ui';
|
||||||
|
|
||||||
import SendInviteDialog from '../InviteDialog/SendInviteDialog.vue';
|
import SendInviteDialog from '../InviteDialog/SendInviteDialog.vue';
|
||||||
import UserSummaryHeader from './UserSummaryHeader.vue';
|
import UserSummaryHeader from './UserSummaryHeader.vue';
|
||||||
@@ -1455,7 +1468,7 @@
|
|||||||
const { refreshInviteMessageTableData } = useInviteStore();
|
const { refreshInviteMessageTableData } = useInviteStore();
|
||||||
const { friendLogTable } = storeToRefs(useFriendStore());
|
const { friendLogTable } = storeToRefs(useFriendStore());
|
||||||
const { getFriendRequest, handleFriendDelete } = useFriendStore();
|
const { getFriendRequest, handleFriendDelete } = useFriendStore();
|
||||||
const { clearInviteImageUpload, showFullscreenImageDialog } = useGalleryStore();
|
const { clearInviteImageUpload, showFullscreenImageDialog, showGalleryPage } = useGalleryStore();
|
||||||
|
|
||||||
const { logout } = useAuthStore();
|
const { logout } = useAuthStore();
|
||||||
const { cachedConfig } = storeToRefs(useAuthStore());
|
const { cachedConfig } = storeToRefs(useAuthStore());
|
||||||
@@ -1896,6 +1909,9 @@
|
|||||||
}
|
}
|
||||||
} else if (command === 'Previous Instances') {
|
} else if (command === 'Previous Instances') {
|
||||||
showPreviousInstancesUserDialog(D.ref);
|
showPreviousInstancesUserDialog(D.ref);
|
||||||
|
} else if (command === 'Manage Gallery') {
|
||||||
|
userDialog.value.visible = false;
|
||||||
|
showGalleryPage();
|
||||||
} else if (command === 'Invite To Group') {
|
} else if (command === 'Invite To Group') {
|
||||||
showInviteGroupDialog('', D.id);
|
showInviteGroupDialog('', D.id);
|
||||||
} else if (command === 'Send Boop') {
|
} else if (command === 'Send Boop') {
|
||||||
|
|||||||
@@ -316,7 +316,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style scoped>
|
||||||
.img-size {
|
.img-size {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 375px;
|
height: 375px;
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
:z-index="worldDialogIndex"
|
:z-index="worldDialogIndex"
|
||||||
class="x-dialog x-world-dialog"
|
class="x-dialog x-world-dialog"
|
||||||
v-model="isDialogVisible"
|
v-model="isDialogVisible"
|
||||||
|
top="10vh"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
width="770px">
|
width="930px">
|
||||||
<div v-loading="worldDialog.loading">
|
<div v-loading="worldDialog.loading">
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
<img
|
<img
|
||||||
@@ -204,11 +205,7 @@
|
|||||||
style="margin-left: 5px"
|
style="margin-left: 5px"
|
||||||
@click="worldDialogCommand('Add Favorite')" />
|
@click="worldDialogCommand('Add Favorite')" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-dropdown
|
<el-dropdown trigger="click" style="margin-left: 5px" @command="worldDialogCommand">
|
||||||
trigger="click"
|
|
||||||
size="small"
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@command="worldDialogCommand">
|
|
||||||
<el-button type="default" :icon="MoreFilled" size="large" circle />
|
<el-button type="default" :icon="MoreFilled" size="large" circle />
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
@@ -302,7 +299,10 @@
|
|||||||
command="Delete Persistent Data">
|
command="Delete Persistent Data">
|
||||||
{{ t('dialog.world.actions.delete_persistent_data') }}
|
{{ t('dialog.world.actions.delete_persistent_data') }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item :icon="Delete" command="Delete" style="color: #f56c6c">
|
<el-dropdown-item
|
||||||
|
:icon="Delete"
|
||||||
|
command="Delete"
|
||||||
|
style="color: var(--el-color-danger)">
|
||||||
{{ t('dialog.world.actions.delete') }}
|
{{ t('dialog.world.actions.delete') }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import colors from 'tailwindcss/colors';
|
||||||
|
|
||||||
|
import configRepository from '../service/config';
|
||||||
|
|
||||||
|
// Tailwind indigo-500 in OKLCH
|
||||||
|
const DEFAULT_PRIMARY = 'oklch(58.5% 0.233 277.117)';
|
||||||
|
const DARK_WEIGHT = 0.2;
|
||||||
|
const CONFIG_KEY = 'VRCX_elPrimaryColor';
|
||||||
|
const STYLE_ID = 'el-dynamic-theme';
|
||||||
|
|
||||||
|
let elementThemeInstance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep okLCH as-is; otherwise normalize hex; fallback to default.
|
||||||
|
* @param {string} color
|
||||||
|
* @param {string} fallback
|
||||||
|
*/
|
||||||
|
function toPrimaryColor(color, fallback = DEFAULT_PRIMARY) {
|
||||||
|
if (typeof color === 'string' && color.trim()) {
|
||||||
|
if (color.trim().startsWith('oklch(')) {
|
||||||
|
return color.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Element Plus CSS variables based on a primary color.
|
||||||
|
* Light colors use Tailwind palette directly; only dark-2 is calculated.
|
||||||
|
* Dark mode overrides light-9 with a softer tint for better contrast.
|
||||||
|
* @param {string} primary
|
||||||
|
* @param {object|null} palette
|
||||||
|
*/
|
||||||
|
function setElementPlusColors(primary, palette = null) {
|
||||||
|
let styleEl = document.getElementById(STYLE_ID);
|
||||||
|
if (!styleEl) {
|
||||||
|
styleEl = document.createElement('style');
|
||||||
|
styleEl.id = STYLE_ID;
|
||||||
|
document.head.appendChild(styleEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive Element Plus light steps either from a palette or by mixing with white.
|
||||||
|
const safePalette = palette || null;
|
||||||
|
const lightValues = safePalette
|
||||||
|
? ['400', '300', '200', '100', '50', '50', '50', '50', '50'].map(
|
||||||
|
(key) => safePalette[key] || primary
|
||||||
|
)
|
||||||
|
: Array.from({ length: 9 }, (_, idx) => {
|
||||||
|
const whitePercent = (idx + 1) * 10;
|
||||||
|
const primaryPercent = 100 - whitePercent;
|
||||||
|
return `color-mix(in oklch, ${primary} ${primaryPercent}%, white ${whitePercent}%)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const lights = lightValues
|
||||||
|
.map(
|
||||||
|
(value, index) =>
|
||||||
|
` --el-color-primary-light-${index + 1}: ${value};`
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const darkPercent = DARK_WEIGHT * 100;
|
||||||
|
const primaryPercent = 100 - darkPercent;
|
||||||
|
const darkValue = `color-mix(in oklch, ${primary} ${primaryPercent}%, black ${darkPercent}%)`;
|
||||||
|
const darkLight9 = `color-mix(in oklch, ${primary} 18%, transparent)`;
|
||||||
|
|
||||||
|
const baseSelector =
|
||||||
|
":root, html.dark, :root.dark, :root[data-theme='dark']";
|
||||||
|
const darkSelector = "html.dark, :root.dark, :root[data-theme='dark']";
|
||||||
|
styleEl.textContent =
|
||||||
|
`${baseSelector} {\n --el-color-primary: ${primary};\n${lights}\n --el-color-primary-dark-2: ${darkValue};\n}\n` +
|
||||||
|
`${darkSelector} {\n --el-color-primary-light-9: ${darkLight9};\n}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findTailwindPalette(primary) {
|
||||||
|
const entries = Object.values(colors);
|
||||||
|
for (const palette of entries) {
|
||||||
|
if (
|
||||||
|
palette &&
|
||||||
|
typeof palette === 'object' &&
|
||||||
|
palette['500'] === primary
|
||||||
|
) {
|
||||||
|
return palette;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared Element Plus theme controller.
|
||||||
|
* @param {string} defaultColor
|
||||||
|
*/
|
||||||
|
export function useElementTheme(defaultColor = DEFAULT_PRIMARY) {
|
||||||
|
if (elementThemeInstance) {
|
||||||
|
return elementThemeInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPrimary = ref(defaultColor);
|
||||||
|
const isApplying = ref(false);
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
|
const applyPrimaryColor = async (color, palette = null) => {
|
||||||
|
const nextColor = toPrimaryColor(color, currentPrimary.value);
|
||||||
|
const effectivePalette = palette || findTailwindPalette(nextColor);
|
||||||
|
isApplying.value = true;
|
||||||
|
setElementPlusColors(nextColor, effectivePalette);
|
||||||
|
currentPrimary.value = nextColor;
|
||||||
|
try {
|
||||||
|
await configRepository.setString(CONFIG_KEY, nextColor);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to persist theme color', error);
|
||||||
|
} finally {
|
||||||
|
isApplying.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initPrimaryColor = async (fallbackColor = currentPrimary.value) => {
|
||||||
|
if (initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
const storedColor =
|
||||||
|
(await configRepository.getString(CONFIG_KEY)) ||
|
||||||
|
fallbackColor ||
|
||||||
|
DEFAULT_PRIMARY;
|
||||||
|
await applyPrimaryColor(storedColor);
|
||||||
|
};
|
||||||
|
|
||||||
|
elementThemeInstance = {
|
||||||
|
currentPrimary,
|
||||||
|
isApplying,
|
||||||
|
applyPrimaryColor,
|
||||||
|
initPrimaryColor
|
||||||
|
};
|
||||||
|
|
||||||
|
return elementThemeInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { toPrimaryColor };
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
|
||||||
|
export function useTableHeight(tableRef, options = {}) {
|
||||||
|
const containerRef = ref(null);
|
||||||
|
const offset = options.offset ?? 127;
|
||||||
|
const immediate = options.immediate ?? true;
|
||||||
|
|
||||||
|
let resizeObserver;
|
||||||
|
|
||||||
|
const setTableHeight = () => {
|
||||||
|
if (!tableRef?.value || !containerRef.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tableRef.value.tableProps = {
|
||||||
|
...(tableRef.value.tableProps || {}),
|
||||||
|
// @ts-ignore default is null
|
||||||
|
height: containerRef.value.clientHeight - offset
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (immediate) {
|
||||||
|
setTableHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
setTableHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (containerRef.value) {
|
||||||
|
resizeObserver.observe(containerRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
resizeObserver?.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
containerRef,
|
||||||
|
setTableHeight
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
<title>VRCX</title>
|
<title>VRCX</title>
|
||||||
|
|
||||||
<!-- <link rel="stylesheet" href="app.css" /> -->
|
|
||||||
|
|
||||||
<link rel="preconnect" href="https://api.vrchat.cloud" />
|
<link rel="preconnect" href="https://api.vrchat.cloud" />
|
||||||
<link rel="preconnect" href="https://files.vrchat.cloud" />
|
<link rel="preconnect" href="https://files.vrchat.cloud" />
|
||||||
<link rel="preconnect" href="https://d348imysud55la.cloudfront.net" />
|
<link rel="preconnect" href="https://d348imysud55la.cloudfront.net" />
|
||||||
|
|||||||
@@ -1782,6 +1782,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
|
"user": "User",
|
||||||
|
"group": "Group",
|
||||||
"user_group": "User/Group",
|
"user_group": "User/Group",
|
||||||
"photo": "Photo",
|
"photo": "Photo",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
|
|||||||
@@ -21,7 +21,10 @@
|
|||||||
"about": "About",
|
"about": "About",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"help_support": "Help & Support"
|
"manage": "Manage",
|
||||||
|
"help_support": "Help & Support",
|
||||||
|
"expand_menu": "Expand Menu",
|
||||||
|
"collapse_menu": "Collapse Menu"
|
||||||
},
|
},
|
||||||
"nav_menu": {
|
"nav_menu": {
|
||||||
"resources": "RESOURCES",
|
"resources": "RESOURCES",
|
||||||
@@ -292,6 +295,10 @@
|
|||||||
"bulk_unfriend_selection": "Bulk Unfriend Selection",
|
"bulk_unfriend_selection": "Bulk Unfriend Selection",
|
||||||
"load": "Load missing entries",
|
"load": "Load missing entries",
|
||||||
"load_tooltip": "Load",
|
"load_tooltip": "Load",
|
||||||
|
"load_dialog_title": "Load missing entries",
|
||||||
|
"load_dialog_message": "Retrieving missing profile fields for your friends.",
|
||||||
|
"load_cancel": "Cancel",
|
||||||
|
"load_complete": "Missing entries loaded",
|
||||||
"favorites_only_tooltip": "Filter favorites only",
|
"favorites_only_tooltip": "Filter favorites only",
|
||||||
"search_placeholder": "Search",
|
"search_placeholder": "Search",
|
||||||
"filter_placeholder": "Filter",
|
"filter_placeholder": "Filter",
|
||||||
@@ -560,7 +567,8 @@
|
|||||||
"table_max_size": "Table Max Size",
|
"table_max_size": "Table Max Size",
|
||||||
"table_page_sizes": "Table Page Sizes",
|
"table_page_sizes": "Table Page Sizes",
|
||||||
"table_page_sizes_error": "Page size must be a number between 1 and 1000",
|
"table_page_sizes_error": "Page size must be a number between 1 and 1000",
|
||||||
"show_notification_icon_dot": "Show Tray Notification Dot"
|
"show_notification_icon_dot": "Show Tray Notification Dot",
|
||||||
|
"compact_table_mode": "Compact Table Mode"
|
||||||
},
|
},
|
||||||
"timedate": {
|
"timedate": {
|
||||||
"header": "Time/Date",
|
"header": "Time/Date",
|
||||||
@@ -569,6 +577,9 @@
|
|||||||
"time_format_12": "12 Hour",
|
"time_format_12": "12 Hour",
|
||||||
"force_iso_date_format": "Force ISO Date Format"
|
"force_iso_date_format": "Force ISO Date Format"
|
||||||
},
|
},
|
||||||
|
"theme_color": {
|
||||||
|
"header": "Theme Color"
|
||||||
|
},
|
||||||
"side_panel": {
|
"side_panel": {
|
||||||
"header": "Side Panel",
|
"header": "Side Panel",
|
||||||
"sorting": {
|
"sorting": {
|
||||||
@@ -873,7 +884,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"side_panel": {
|
"side_panel": {
|
||||||
"search_placeholder": "Search",
|
"search_placeholder": "Search Friend",
|
||||||
"search_result_active": "Offline",
|
"search_result_active": "Offline",
|
||||||
"search_result_offline": "Active",
|
"search_result_offline": "Active",
|
||||||
"search_result_more": "Search More:",
|
"search_result_more": "Search More:",
|
||||||
@@ -2254,6 +2265,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
|
"user": "User",
|
||||||
|
"group": "Group",
|
||||||
"user_group": "User/Group",
|
"user_group": "User/Group",
|
||||||
"photo": "Photo",
|
"photo": "Photo",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
|
|||||||
@@ -1899,6 +1899,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "Fecha",
|
"date": "Fecha",
|
||||||
"type": "Tipo",
|
"type": "Tipo",
|
||||||
|
"user": "Usuario",
|
||||||
|
"group": "Grupo",
|
||||||
"user_group": "Usuario/Grupo",
|
"user_group": "Usuario/Grupo",
|
||||||
"photo": "Foto",
|
"photo": "Foto",
|
||||||
"message": "Mensaje",
|
"message": "Mensaje",
|
||||||
|
|||||||
@@ -1788,6 +1788,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
|
"user": "Utilisateur",
|
||||||
|
"group": "Groupe",
|
||||||
"user_group": "Utilisateur/Groupe",
|
"user_group": "Utilisateur/Groupe",
|
||||||
"photo": "Photo",
|
"photo": "Photo",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
|
|||||||
@@ -1660,6 +1660,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
|
"user": "Felhasználó",
|
||||||
|
"group": "Csoport",
|
||||||
"user_group": "User/Group",
|
"user_group": "User/Group",
|
||||||
"photo": "Photo",
|
"photo": "Photo",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
|
|||||||
@@ -2082,6 +2082,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "日付",
|
"date": "日付",
|
||||||
"type": "種類",
|
"type": "種類",
|
||||||
|
"user": "ユーザー",
|
||||||
|
"group": "グループ",
|
||||||
"user_group": "ユーザーまたはグループ",
|
"user_group": "ユーザーまたはグループ",
|
||||||
"photo": "画像",
|
"photo": "画像",
|
||||||
"message": "メッセージ",
|
"message": "メッセージ",
|
||||||
|
|||||||
@@ -1674,6 +1674,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "날짜",
|
"date": "날짜",
|
||||||
"type": "유형",
|
"type": "유형",
|
||||||
|
"user": "유저",
|
||||||
|
"group": "그룹",
|
||||||
"user_group": "User/Group",
|
"user_group": "User/Group",
|
||||||
"photo": "사진",
|
"photo": "사진",
|
||||||
"message": "메시지",
|
"message": "메시지",
|
||||||
|
|||||||
@@ -2225,6 +2225,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "Data",
|
"date": "Data",
|
||||||
"type": "Typ",
|
"type": "Typ",
|
||||||
|
"user": "Użytkownik",
|
||||||
|
"group": "Grupa",
|
||||||
"user_group": "Użytkownik/Grupa",
|
"user_group": "Użytkownik/Grupa",
|
||||||
"photo": "Obrazek",
|
"photo": "Obrazek",
|
||||||
"message": "Wiadomość",
|
"message": "Wiadomość",
|
||||||
|
|||||||
@@ -1660,6 +1660,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "Data",
|
"date": "Data",
|
||||||
"type": "Tipo",
|
"type": "Tipo",
|
||||||
|
"user": "Usuário",
|
||||||
|
"group": "Grupo",
|
||||||
"user_group": "Usuário/Grupo",
|
"user_group": "Usuário/Grupo",
|
||||||
"photo": "Foto",
|
"photo": "Foto",
|
||||||
"message": "Mensagem",
|
"message": "Mensagem",
|
||||||
|
|||||||
@@ -2069,6 +2069,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "Дата",
|
"date": "Дата",
|
||||||
"type": "Тип",
|
"type": "Тип",
|
||||||
|
"user": "Пользователь",
|
||||||
|
"group": "Группа",
|
||||||
"user_group": "Пользователь/Группа",
|
"user_group": "Пользователь/Группа",
|
||||||
"photo": "Фото",
|
"photo": "Фото",
|
||||||
"message": "Сообщение",
|
"message": "Сообщение",
|
||||||
|
|||||||
@@ -1964,6 +1964,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "วันที่",
|
"date": "วันที่",
|
||||||
"type": "ประเภท",
|
"type": "ประเภท",
|
||||||
|
"user": "ผู้ใช้",
|
||||||
|
"group": "กลุ่ม",
|
||||||
"user_group": "ผู้ใช้/กลุ่ม",
|
"user_group": "ผู้ใช้/กลุ่ม",
|
||||||
"photo": "รูปภาพ",
|
"photo": "รูปภาพ",
|
||||||
"message": "ข้อความ",
|
"message": "ข้อความ",
|
||||||
|
|||||||
@@ -1660,6 +1660,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
|
"user": "Người chơi",
|
||||||
|
"group": "Nhóm",
|
||||||
"user_group": "User/Group",
|
"user_group": "User/Group",
|
||||||
"photo": "Photo",
|
"photo": "Photo",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
|
|||||||
@@ -2207,6 +2207,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "时间",
|
"date": "时间",
|
||||||
"type": "类型",
|
"type": "类型",
|
||||||
|
"user": "玩家",
|
||||||
|
"group": "群组",
|
||||||
"user_group": "玩家/群组",
|
"user_group": "玩家/群组",
|
||||||
"photo": "封面",
|
"photo": "封面",
|
||||||
"message": "消息",
|
"message": "消息",
|
||||||
|
|||||||
@@ -2192,6 +2192,8 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"date": "時間",
|
"date": "時間",
|
||||||
"type": "類型",
|
"type": "類型",
|
||||||
|
"user": "用戶",
|
||||||
|
"group": "群組",
|
||||||
"user_group": "用戶/群組",
|
"user_group": "用戶/群組",
|
||||||
"photo": "照片",
|
"photo": "照片",
|
||||||
"message": "訊息",
|
"message": "訊息",
|
||||||
|
|||||||
@@ -8,12 +8,10 @@ import LastJoin from '../components/LastJoin.vue';
|
|||||||
import Launch from '../components/Launch.vue';
|
import Launch from '../components/Launch.vue';
|
||||||
import Location from '../components/Location.vue';
|
import Location from '../components/Location.vue';
|
||||||
import LocationWorld from '../components/LocationWorld.vue';
|
import LocationWorld from '../components/LocationWorld.vue';
|
||||||
import NativeTooltip from '../components/NativeTooltip.vue';
|
|
||||||
import Timer from '../components/Timer.vue';
|
import Timer from '../components/Timer.vue';
|
||||||
|
|
||||||
export function initComponents(app) {
|
export function initComponents(app) {
|
||||||
app.component('Location', Location);
|
app.component('Location', Location);
|
||||||
app.component('NativeTooltip', NativeTooltip);
|
|
||||||
app.component('Timer', Timer);
|
app.component('Timer', Timer);
|
||||||
app.component('InstanceInfo', InstanceInfo);
|
app.component('InstanceInfo', InstanceInfo);
|
||||||
app.component('LastJoin', LastJoin);
|
app.component('LastJoin', LastJoin);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function initNoty(isVrOverlay = false) {
|
|||||||
},
|
},
|
||||||
layout: 'bottomLeft',
|
layout: 'bottomLeft',
|
||||||
theme: 'mint',
|
theme: 'mint',
|
||||||
timeout: 6000
|
timeout: 2000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import PlayerList from './../views/PlayerList/PlayerList.vue';
|
|||||||
import Search from './../views/Search/Search.vue';
|
import Search from './../views/Search/Search.vue';
|
||||||
import Settings from './../views/Settings/Settings.vue';
|
import Settings from './../views/Settings/Settings.vue';
|
||||||
import Tools from './../views/Tools/Tools.vue';
|
import Tools from './../views/Tools/Tools.vue';
|
||||||
|
import Gallery from './../views/Tools/Gallery.vue';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@@ -83,6 +84,12 @@ const routes = [
|
|||||||
component: Charts
|
component: Charts
|
||||||
},
|
},
|
||||||
{ path: 'tools', name: 'tools', component: Tools },
|
{ path: 'tools', name: 'tools', component: Tools },
|
||||||
|
{
|
||||||
|
path: 'tools/gallery',
|
||||||
|
name: 'gallery',
|
||||||
|
component: Gallery,
|
||||||
|
meta: { navKey: 'tools' }
|
||||||
|
},
|
||||||
{ path: 'settings', name: 'settings', component: Settings }
|
{ path: 'settings', name: 'settings', component: Settings }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 645 B After Width: | Height: | Size: 645 B |
|
Before Width: | Height: | Size: 541 B After Width: | Height: | Size: 541 B |
|
Before Width: | Height: | Size: 843 B After Width: | Height: | Size: 843 B |
|
Before Width: | Height: | Size: 737 B After Width: | Height: | Size: 737 B |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 659 B |
|
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 557 B After Width: | Height: | Size: 557 B |
|
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 903 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 823 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
const accessTypeLocaleKeyMap = {
|
||||||
|
public: 'dialog.new_instance.access_type_public',
|
||||||
|
group: 'dialog.new_instance.access_type_group',
|
||||||
|
'friends+': 'dialog.new_instance.access_type_friend_plus',
|
||||||
|
friends: 'dialog.new_instance.access_type_friend',
|
||||||
|
'invite+': 'dialog.new_instance.access_type_invite_plus',
|
||||||
|
invite: 'dialog.new_instance.access_type_invite',
|
||||||
|
groupPublic: 'dialog.new_instance.group_access_type_public',
|
||||||
|
groupPlus: 'dialog.new_instance.group_access_type_plus',
|
||||||
|
groupMembers: 'dialog.new_instance.group_access_type_members'
|
||||||
|
};
|
||||||
|
|
||||||
|
export { accessTypeLocaleKeyMap };
|
||||||
@@ -10,3 +10,4 @@ export * from './moderation';
|
|||||||
export * from './themes';
|
export * from './themes';
|
||||||
export * from './link';
|
export * from './link';
|
||||||
export * from './ui';
|
export * from './ui';
|
||||||
|
export * from './accessType';
|
||||||
|
|||||||
@@ -1,52 +1,69 @@
|
|||||||
import amoled from '../../assets/scss/themes/theme.amoled.scss?url';
|
import appCss from '../../app.css?url';
|
||||||
import dark from '../../assets/scss/themes/theme.dark.scss?url';
|
// import appLegacy from '../../assets/scss/themes/app_legacy.scss?url';
|
||||||
import darkblue from '../../assets/scss/themes/theme.darkblue.scss?url';
|
// import material3 from '../../assets/scss/themes/theme.material3.scss?url';
|
||||||
import material3 from '../../assets/scss/themes/theme.material3.scss?url';
|
|
||||||
|
|
||||||
export const THEME_CONFIG = {
|
export const THEME_CONFIG = {
|
||||||
system: {
|
system: {
|
||||||
cssFile: '',
|
cssFiles: [appCss],
|
||||||
isDark: 'system',
|
isDark: 'system',
|
||||||
name: 'System'
|
name: 'System'
|
||||||
},
|
},
|
||||||
light: {
|
light: {
|
||||||
cssFile: '',
|
cssFiles: [appCss],
|
||||||
isDark: false,
|
isDark: false,
|
||||||
|
useDarkClass: false,
|
||||||
name: 'Light'
|
name: 'Light'
|
||||||
},
|
},
|
||||||
dark: { cssFile: dark, isDark: true, name: 'Dark' },
|
dark: {
|
||||||
darkblue: {
|
cssFiles: [appCss],
|
||||||
cssFile: darkblue,
|
|
||||||
isDark: true,
|
isDark: true,
|
||||||
name: 'Dark Blue'
|
useDarkClass: true,
|
||||||
},
|
name: 'Dark'
|
||||||
amoled: {
|
}
|
||||||
cssFile: amoled,
|
// darkold: {
|
||||||
isDark: true,
|
// cssFiles: [appLegacy, dark],
|
||||||
name: 'Amoled'
|
|
||||||
},
|
|
||||||
// darkvanillaold: {
|
|
||||||
// cssFile: darkvanillaold,
|
|
||||||
// isDark: true,
|
// isDark: true,
|
||||||
|
// useDarkClass: false,
|
||||||
|
// name: 'Dark (Old)'
|
||||||
|
// },
|
||||||
|
// darkblue: {
|
||||||
|
// cssFiles: [appLegacy, darkblue],
|
||||||
|
// isDark: true,
|
||||||
|
// useDarkClass: false,
|
||||||
|
// name: 'Dark Blue'
|
||||||
|
// },
|
||||||
|
// amoled: {
|
||||||
|
// cssFiles: [appLegacy, amoled],
|
||||||
|
// isDark: true,
|
||||||
|
// useDarkClass: false,
|
||||||
|
// name: 'Amoled'
|
||||||
|
// },
|
||||||
|
// darkvanillaold: {
|
||||||
|
// cssFiles: [appLegacy, darkvanillaold],
|
||||||
|
// isDark: true,
|
||||||
|
// useDarkClass: false,
|
||||||
// name: 'Dark Vanilla Old'
|
// name: 'Dark Vanilla Old'
|
||||||
// },
|
// },
|
||||||
// darkvanilla: {
|
// darkvanilla: {
|
||||||
// cssFile: darkvanilla,
|
// cssFiles: [appLegacy, darkvanilla],
|
||||||
// isDark: true,
|
// isDark: true,
|
||||||
|
// useDarkClass: false,
|
||||||
// name: 'Dark Vanilla'
|
// name: 'Dark Vanilla'
|
||||||
// },
|
// },
|
||||||
// pink: {
|
// pink: {
|
||||||
// cssFile: pink,
|
// cssFiles: [appLegacy, pink],
|
||||||
// isDark: true,
|
// isDark: true,
|
||||||
|
// useDarkClass: false,
|
||||||
// name: 'Pink'
|
// name: 'Pink'
|
||||||
// },
|
// },
|
||||||
material3: {
|
// material3: {
|
||||||
cssFile: material3,
|
// cssFiles: [appLegacy, material3],
|
||||||
isDark: true,
|
// isDark: true,
|
||||||
name: 'Material 3',
|
// useDarkClass: false,
|
||||||
fontLinks: [
|
// name: 'Material 3',
|
||||||
'https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;600&family=Noto+Sans+TC:wght@300;400;500&family=Noto+Sans+SC:wght@300;400;500&family=Noto+Sans+JP:wght@300;400;500&family=Roboto&display=swap',
|
// fontLinks: [
|
||||||
'https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200'
|
// 'https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;600&family=Noto+Sans+TC:wght@300;400;500&family=Noto+Sans+SC:wght@300;400;500&family=Noto+Sans+JP:wght@300;400;500&family=Roboto&display=swap',
|
||||||
]
|
// 'https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200'
|
||||||
}
|
// ]
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,6 +96,13 @@ const navDefinitions = [
|
|||||||
tooltip: 'nav_tooltip.tools',
|
tooltip: 'nav_tooltip.tools',
|
||||||
labelKey: 'nav_tooltip.tools',
|
labelKey: 'nav_tooltip.tools',
|
||||||
routeName: 'tools'
|
routeName: 'tools'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'direct-access',
|
||||||
|
icon: 'ri-compass-3-line',
|
||||||
|
tooltip: 'prompt.direct_access_omni.header',
|
||||||
|
labelKey: 'prompt.direct_access_omni.header',
|
||||||
|
action: 'direct-access'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,25 @@ function applyThemeFonts(themeKey, fontLinks = []) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureStylesheetLink(id) {
|
||||||
|
const linkEl = /** @type {HTMLLinkElement | null} */ (
|
||||||
|
document.getElementById(id)
|
||||||
|
);
|
||||||
|
if (!linkEl) {
|
||||||
|
const created = document.createElement('link');
|
||||||
|
created.setAttribute('id', id);
|
||||||
|
created.rel = 'stylesheet';
|
||||||
|
document.head.appendChild(created);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
return linkEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeStylesheetLink(id) {
|
||||||
|
const linkEl = document.getElementById(id);
|
||||||
|
linkEl?.remove();
|
||||||
|
}
|
||||||
|
|
||||||
function changeAppThemeStyle(themeMode) {
|
function changeAppThemeStyle(themeMode) {
|
||||||
if (themeMode === 'system') {
|
if (themeMode === 'system') {
|
||||||
themeMode = systemIsDarkMode() ? 'dark' : 'light';
|
themeMode = systemIsDarkMode() ? 'dark' : 'light';
|
||||||
@@ -61,27 +80,35 @@ function changeAppThemeStyle(themeMode) {
|
|||||||
themeConfig = THEME_CONFIG[themeMode];
|
themeConfig = THEME_CONFIG[themeMode];
|
||||||
}
|
}
|
||||||
|
|
||||||
let filePathPrefix = 'file://vrcx/';
|
const cssFiles = Array.isArray(themeConfig.cssFiles)
|
||||||
if (LINUX) {
|
? themeConfig.cssFiles.filter(Boolean)
|
||||||
filePathPrefix = './';
|
: themeConfig.cssFile
|
||||||
}
|
? [themeConfig.cssFile]
|
||||||
if (process.env.NODE_ENV === 'development') {
|
: [];
|
||||||
filePathPrefix = 'http://localhost:9000/';
|
|
||||||
console.log('Using development file path prefix:', filePathPrefix);
|
if (cssFiles.length > 0) {
|
||||||
|
const $appThemeStyle = ensureStylesheetLink('app-theme-style');
|
||||||
|
$appThemeStyle.href = cssFiles[0];
|
||||||
|
} else {
|
||||||
|
removeStylesheetLink('app-theme-style');
|
||||||
}
|
}
|
||||||
|
|
||||||
let $appThemeStyle = document.getElementById('app-theme-style');
|
if (cssFiles.length > 1) {
|
||||||
if (!$appThemeStyle) {
|
const $appThemeOverlayStyle = ensureStylesheetLink(
|
||||||
$appThemeStyle = document.createElement('link');
|
'app-theme-overlay-style'
|
||||||
$appThemeStyle.setAttribute('id', 'app-theme-style');
|
);
|
||||||
$appThemeStyle.rel = 'stylesheet';
|
$appThemeOverlayStyle.href = cssFiles[1];
|
||||||
document.head.appendChild($appThemeStyle);
|
} else {
|
||||||
|
removeStylesheetLink('app-theme-overlay-style');
|
||||||
}
|
}
|
||||||
$appThemeStyle.href = themeConfig.cssFile ? themeConfig.cssFile : '';
|
|
||||||
|
|
||||||
applyThemeFonts(themeMode, themeConfig.fontLinks);
|
applyThemeFonts(themeMode, themeConfig.fontLinks);
|
||||||
|
|
||||||
if (themeConfig.isDark) {
|
const shouldUseDarkClass =
|
||||||
|
typeof themeConfig.useDarkClass === 'boolean'
|
||||||
|
? themeConfig.useDarkClass
|
||||||
|
: Boolean(themeConfig.isDark);
|
||||||
|
if (shouldUseDarkClass) {
|
||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add('dark');
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove('dark');
|
document.documentElement.classList.remove('dark');
|
||||||
@@ -258,19 +285,19 @@ function setLoginContainerStyle(isDarkMode) {
|
|||||||
loginContainerStyle.id = 'login-container-style';
|
loginContainerStyle.id = 'login-container-style';
|
||||||
loginContainerStyle.type = 'text/css';
|
loginContainerStyle.type = 'text/css';
|
||||||
|
|
||||||
const backgroundColor = isDarkMode ? '#101010' : '#ffffff';
|
const backgroundFallback = isDarkMode ? '#101010' : '#ffffff';
|
||||||
const inputBackgroundColor = isDarkMode ? '#333333' : '#ffffff';
|
const inputBackgroundFallback = isDarkMode ? '#1f1f1f' : '#ffffff';
|
||||||
const inputBorder = isDarkMode ? '1px solid #3b3b3b' : '1px solid #DCDFE6';
|
const borderFallback = isDarkMode ? '#3b3b3b' : '#DCDFE6';
|
||||||
|
|
||||||
loginContainerStyle.innerHTML = `
|
loginContainerStyle.innerHTML = `
|
||||||
.x-login-container {
|
.x-login-container {
|
||||||
background-color: ${backgroundColor} !important;
|
background-color: var(--el-bg-color-page, ${backgroundFallback}) !important;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.x-login-container .el-input__wrapper {
|
.x-login-container .el-input__wrapper {
|
||||||
background-color: ${inputBackgroundColor} !important;
|
background-color: var(--el-bg-color, ${inputBackgroundFallback}) !important;
|
||||||
border: ${inputBorder} !important;
|
border: 1px solid var(--el-border-color, ${borderFallback}) !important;
|
||||||
transition: background-color 0.3s ease, border-color 0.3s ease;
|
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { AppDebug } from '../service/appConfig';
|
|||||||
import { handleImageUploadInput } from '../shared/utils/imageUpload';
|
import { handleImageUploadInput } from '../shared/utils/imageUpload';
|
||||||
import { useAdvancedSettingsStore } from './settings/advanced';
|
import { useAdvancedSettingsStore } from './settings/advanced';
|
||||||
import { watchState } from '../service/watchState';
|
import { watchState } from '../service/watchState';
|
||||||
|
import { router } from '../plugin/router';
|
||||||
|
|
||||||
import miscReq from '../api/misc';
|
import miscReq from '../api/misc';
|
||||||
|
|
||||||
@@ -122,8 +123,16 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showGalleryDialog() {
|
function showGalleryPage() {
|
||||||
galleryDialogVisible.value = true;
|
galleryDialogVisible.value = true;
|
||||||
|
if (router.currentRoute.value?.name === 'gallery') {
|
||||||
|
loadGalleryData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.push({ name: 'gallery' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadGalleryData() {
|
||||||
refreshGalleryTable();
|
refreshGalleryTable();
|
||||||
refreshVRCPlusIconsTable();
|
refreshVRCPlusIconsTable();
|
||||||
refreshEmojiTable();
|
refreshEmojiTable();
|
||||||
@@ -572,7 +581,8 @@ export const useGalleryStore = defineStore('Gallery', () => {
|
|||||||
fullscreenImageDialog,
|
fullscreenImageDialog,
|
||||||
cachedEmoji,
|
cachedEmoji,
|
||||||
|
|
||||||
showGalleryDialog,
|
showGalleryPage,
|
||||||
|
loadGalleryData,
|
||||||
refreshGalleryTable,
|
refreshGalleryTable,
|
||||||
refreshVRCPlusIconsTable,
|
refreshVRCPlusIconsTable,
|
||||||
inviteImageUpload,
|
inviteImageUpload,
|
||||||
|
|||||||
@@ -12,9 +12,12 @@ import {
|
|||||||
systemIsDarkMode,
|
systemIsDarkMode,
|
||||||
updateTrustColorClasses
|
updateTrustColorClasses
|
||||||
} from '../../shared/utils/base/ui';
|
} from '../../shared/utils/base/ui';
|
||||||
|
import { THEME_CONFIG } from '../../shared/constants';
|
||||||
import { database } from '../../service/database';
|
import { database } from '../../service/database';
|
||||||
import { getNameColour } from '../../shared/utils';
|
import { getNameColour } from '../../shared/utils';
|
||||||
|
import { languageCodes } from '../../localization';
|
||||||
import { loadLocalizedStrings } from '../../plugin';
|
import { loadLocalizedStrings } from '../../plugin';
|
||||||
|
import { useElementTheme } from '../../composables/useElementTheme';
|
||||||
import { useFeedStore } from '../feed';
|
import { useFeedStore } from '../feed';
|
||||||
import { useGameLogStore } from '../gameLog';
|
import { useGameLogStore } from '../gameLog';
|
||||||
import { useUiStore } from '../ui';
|
import { useUiStore } from '../ui';
|
||||||
@@ -24,7 +27,6 @@ import { useVrcxStore } from '../vrcx';
|
|||||||
import { watchState } from '../../service/watchState';
|
import { watchState } from '../../service/watchState';
|
||||||
|
|
||||||
import configRepository from '../../service/config';
|
import configRepository from '../../service/config';
|
||||||
import { languageCodes } from '../../localization';
|
|
||||||
|
|
||||||
export const useAppearanceSettingsStore = defineStore(
|
export const useAppearanceSettingsStore = defineStore(
|
||||||
'AppearanceSettings',
|
'AppearanceSettings',
|
||||||
@@ -42,6 +44,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
|
|
||||||
const MAX_TABLE_PAGE_SIZE = 1000;
|
const MAX_TABLE_PAGE_SIZE = 1000;
|
||||||
const DEFAULT_TABLE_PAGE_SIZES = [10, 15, 20, 25, 50, 100];
|
const DEFAULT_TABLE_PAGE_SIZES = [10, 15, 20, 25, 50, 100];
|
||||||
|
const { initPrimaryColor } = useElementTheme();
|
||||||
|
|
||||||
const appLanguage = ref('en');
|
const appLanguage = ref('en');
|
||||||
const themeMode = ref('');
|
const themeMode = ref('');
|
||||||
@@ -71,7 +74,8 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
const hideUserMemos = ref(false);
|
const hideUserMemos = ref(false);
|
||||||
const hideUnfriends = ref(false);
|
const hideUnfriends = ref(false);
|
||||||
const randomUserColours = ref(false);
|
const randomUserColours = ref(false);
|
||||||
const trustColor = ref({
|
const compactTableMode = ref(false);
|
||||||
|
const TRUST_COLOR_DEFAULTS = Object.freeze({
|
||||||
untrusted: '#CCCCCC',
|
untrusted: '#CCCCCC',
|
||||||
basic: '#1778FF',
|
basic: '#1778FF',
|
||||||
known: '#2BCF5C',
|
known: '#2BCF5C',
|
||||||
@@ -80,8 +84,10 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
vip: '#FF2626',
|
vip: '#FF2626',
|
||||||
troll: '#782F2F'
|
troll: '#782F2F'
|
||||||
});
|
});
|
||||||
|
const trustColor = ref({ ...TRUST_COLOR_DEFAULTS });
|
||||||
const currentCulture = ref('');
|
const currentCulture = ref('');
|
||||||
const notificationIconDot = ref(false);
|
const notificationIconDot = ref(false);
|
||||||
|
const isNavCollapsed = ref(true);
|
||||||
const isSideBarTabShow = computed(() => {
|
const isSideBarTabShow = computed(() => {
|
||||||
const currentRouteName = router.currentRoute.value?.name;
|
const currentRouteName = router.currentRoute.value?.name;
|
||||||
return !(
|
return !(
|
||||||
@@ -118,8 +124,10 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
hideUserMemosConfig,
|
hideUserMemosConfig,
|
||||||
hideUnfriendsConfig,
|
hideUnfriendsConfig,
|
||||||
randomUserColoursConfig,
|
randomUserColoursConfig,
|
||||||
|
compactTableModeConfig,
|
||||||
trustColorConfig,
|
trustColorConfig,
|
||||||
notificationIconDotConfig
|
notificationIconDotConfig,
|
||||||
|
navIsCollapsedConfig
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
configRepository.getString('VRCX_appLanguage'),
|
configRepository.getString('VRCX_appLanguage'),
|
||||||
configRepository.getString('VRCX_ThemeMode', 'system'),
|
configRepository.getString('VRCX_ThemeMode', 'system'),
|
||||||
@@ -163,19 +171,13 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
configRepository.getBool('VRCX_hideUserMemos', false),
|
configRepository.getBool('VRCX_hideUserMemos', false),
|
||||||
configRepository.getBool('VRCX_hideUnfriends', false),
|
configRepository.getBool('VRCX_hideUnfriends', false),
|
||||||
configRepository.getBool('VRCX_randomUserColours', false),
|
configRepository.getBool('VRCX_randomUserColours', false),
|
||||||
|
configRepository.getBool('VRCX_compactTableMode', false),
|
||||||
configRepository.getString(
|
configRepository.getString(
|
||||||
'VRCX_trustColor',
|
'VRCX_trustColor',
|
||||||
JSON.stringify({
|
JSON.stringify(TRUST_COLOR_DEFAULTS)
|
||||||
untrusted: '#CCCCCC',
|
|
||||||
basic: '#1778FF',
|
|
||||||
known: '#2BCF5C',
|
|
||||||
trusted: '#FF7B42',
|
|
||||||
veteran: '#B18FFF',
|
|
||||||
vip: '#FF2626',
|
|
||||||
troll: '#782F2F'
|
|
||||||
})
|
|
||||||
),
|
),
|
||||||
configRepository.getBool('VRCX_notificationIconDot', true)
|
configRepository.getBool('VRCX_notificationIconDot', true),
|
||||||
|
configRepository.getBool('VRCX_navIsCollapsed', true)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!appLanguageConfig) {
|
if (!appLanguageConfig) {
|
||||||
@@ -193,8 +195,18 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
await changeAppLanguage(appLanguageConfig);
|
await changeAppLanguage(appLanguageConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
themeMode.value = themeModeConfig;
|
const normalizedThemeMode = normalizeThemeMode(themeModeConfig);
|
||||||
|
if (normalizedThemeMode !== themeModeConfig) {
|
||||||
|
configRepository.setString(
|
||||||
|
'VRCX_ThemeMode',
|
||||||
|
normalizedThemeMode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
themeMode.value = normalizedThemeMode;
|
||||||
applyThemeMode();
|
applyThemeMode();
|
||||||
|
await changeAppThemeStyle(themeMode.value);
|
||||||
|
await initPrimaryColor();
|
||||||
|
|
||||||
displayVRCPlusIconsAsAvatar.value =
|
displayVRCPlusIconsAsAvatar.value =
|
||||||
displayVRCPlusIconsAsAvatarConfig;
|
displayVRCPlusIconsAsAvatarConfig;
|
||||||
@@ -222,7 +234,13 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
sidebarSortMethod3.value = sidebarSortMethods.value[2];
|
sidebarSortMethod3.value = sidebarSortMethods.value[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
trustColor.value = JSON.parse(trustColorConfig);
|
if (trustColorConfig !== JSON.stringify(TRUST_COLOR_DEFAULTS)) {
|
||||||
|
await configRepository.setString(
|
||||||
|
'VRCX_trustColor',
|
||||||
|
JSON.stringify(TRUST_COLOR_DEFAULTS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
trustColor.value = { ...TRUST_COLOR_DEFAULTS };
|
||||||
asideWidth.value = asideWidthConfig;
|
asideWidth.value = asideWidthConfig;
|
||||||
isSidebarGroupByInstance.value = isSidebarGroupByInstanceConfig;
|
isSidebarGroupByInstance.value = isSidebarGroupByInstanceConfig;
|
||||||
isHideFriendsInSameInstance.value =
|
isHideFriendsInSameInstance.value =
|
||||||
@@ -234,6 +252,11 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
hideUnfriends.value = hideUnfriendsConfig;
|
hideUnfriends.value = hideUnfriendsConfig;
|
||||||
randomUserColours.value = randomUserColoursConfig;
|
randomUserColours.value = randomUserColoursConfig;
|
||||||
notificationIconDot.value = notificationIconDotConfig;
|
notificationIconDot.value = notificationIconDotConfig;
|
||||||
|
compactTableMode.value = compactTableModeConfig;
|
||||||
|
applyCompactTableMode(compactTableMode.value);
|
||||||
|
isNavCollapsed.value = navIsCollapsedConfig;
|
||||||
|
|
||||||
|
await configRepository.remove('VRCX_navWidth');
|
||||||
|
|
||||||
// Migrate old settings
|
// Migrate old settings
|
||||||
// Assume all exist if one does
|
// Assume all exist if one does
|
||||||
@@ -256,6 +279,12 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
{ flush: 'sync' }
|
{ flush: 'sync' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function normalizeThemeMode(mode) {
|
||||||
|
return Object.prototype.hasOwnProperty.call(THEME_CONFIG, mode)
|
||||||
|
? mode
|
||||||
|
: 'light';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} language
|
* @param {string} language
|
||||||
@@ -421,8 +450,9 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
* @param {string} mode
|
* @param {string} mode
|
||||||
*/
|
*/
|
||||||
function setThemeMode(mode) {
|
function setThemeMode(mode) {
|
||||||
themeMode.value = mode;
|
const normalizedThemeMode = normalizeThemeMode(mode);
|
||||||
configRepository.setString('VRCX_ThemeMode', mode);
|
themeMode.value = normalizedThemeMode;
|
||||||
|
configRepository.setString('VRCX_ThemeMode', normalizedThemeMode);
|
||||||
applyThemeMode();
|
applyThemeMode();
|
||||||
}
|
}
|
||||||
function applyThemeMode() {
|
function applyThemeMode() {
|
||||||
@@ -557,18 +587,24 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
JSON.stringify(methods)
|
JSON.stringify(methods)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/**
|
function setNavCollapsed(collapsed) {
|
||||||
* @param {number} panelNumber
|
isNavCollapsed.value = collapsed;
|
||||||
* @param {Array<number>} widthArray
|
configRepository.setBool('VRCX_navIsCollapsed', collapsed);
|
||||||
*/
|
}
|
||||||
function setAsideWidth(panelNumber, widthArray) {
|
function toggleNavCollapsed() {
|
||||||
if (Array.isArray(widthArray) && widthArray[1]) {
|
setNavCollapsed(!isNavCollapsed.value);
|
||||||
|
}
|
||||||
|
function setAsideWidth(widthOrArray) {
|
||||||
|
let width = null;
|
||||||
|
if (Array.isArray(widthOrArray) && widthOrArray.length) {
|
||||||
|
width = widthOrArray[widthOrArray.length - 1];
|
||||||
|
} else if (typeof widthOrArray === 'number') {
|
||||||
|
width = widthOrArray;
|
||||||
|
}
|
||||||
|
if (width) {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
asideWidth.value = widthArray[1];
|
asideWidth.value = width;
|
||||||
configRepository.setInt(
|
configRepository.setInt('VRCX_sidePanelWidth', width);
|
||||||
'VRCX_sidePanelWidth',
|
|
||||||
widthArray[1]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -614,14 +650,22 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
randomUserColours.value
|
randomUserColours.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
function setCompactTableMode() {
|
||||||
|
compactTableMode.value = !compactTableMode.value;
|
||||||
|
applyCompactTableMode(compactTableMode.value);
|
||||||
|
configRepository.setBool(
|
||||||
|
'VRCX_compactTableMode',
|
||||||
|
compactTableMode.value
|
||||||
|
);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @param {object} color
|
* @param {object} color
|
||||||
*/
|
*/
|
||||||
function setTrustColor(color) {
|
function setTrustColor(color) {
|
||||||
trustColor.value = color;
|
trustColor.value = { ...TRUST_COLOR_DEFAULTS };
|
||||||
configRepository.setString(
|
configRepository.setString(
|
||||||
'VRCX_trustColor',
|
'VRCX_trustColor',
|
||||||
JSON.stringify(color)
|
JSON.stringify(trustColor.value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,6 +785,15 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
await userColourInit();
|
await userColourInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyCompactTableMode(isCompact) {
|
||||||
|
const className = 'is-compact-table';
|
||||||
|
if (isCompact) {
|
||||||
|
document.documentElement.classList.add(className);
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
appLanguage,
|
appLanguage,
|
||||||
themeMode,
|
themeMode,
|
||||||
@@ -766,10 +819,12 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
hideUserMemos,
|
hideUserMemos,
|
||||||
hideUnfriends,
|
hideUnfriends,
|
||||||
randomUserColours,
|
randomUserColours,
|
||||||
|
compactTableMode,
|
||||||
trustColor,
|
trustColor,
|
||||||
currentCulture,
|
currentCulture,
|
||||||
isSideBarTabShow,
|
isSideBarTabShow,
|
||||||
notificationIconDot,
|
notificationIconDot,
|
||||||
|
isNavCollapsed,
|
||||||
|
|
||||||
setAppLanguage,
|
setAppLanguage,
|
||||||
setDisplayVRCPlusIconsAsAvatar,
|
setDisplayVRCPlusIconsAsAvatar,
|
||||||
@@ -793,6 +848,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
setHideUserMemos,
|
setHideUserMemos,
|
||||||
setHideUnfriends,
|
setHideUnfriends,
|
||||||
setRandomUserColours,
|
setRandomUserColours,
|
||||||
|
setCompactTableMode,
|
||||||
setTrustColor,
|
setTrustColor,
|
||||||
saveThemeMode,
|
saveThemeMode,
|
||||||
tryInitUserColours,
|
tryInitUserColours,
|
||||||
@@ -802,7 +858,10 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
applyUserTrustLevel,
|
applyUserTrustLevel,
|
||||||
changeAppLanguage,
|
changeAppLanguage,
|
||||||
promptMaxTableSizeDialog,
|
promptMaxTableSizeDialog,
|
||||||
setNotificationIconDot
|
setNotificationIconDot,
|
||||||
|
applyCompactTableMode,
|
||||||
|
setNavCollapsed,
|
||||||
|
toggleNavCollapsed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="chart" class="x-container">
|
<div id="chart" class="x-container">
|
||||||
<div class="options-container" style="margin-top: 0">
|
|
||||||
<span class="header">{{ t('view.charts.header') }}</span>
|
|
||||||
</div>
|
|
||||||
<el-tabs v-model="activeTab" class="charts-tabs">
|
<el-tabs v-model="activeTab" class="charts-tabs">
|
||||||
<el-tab-pane :label="t('view.charts.instance_activity.header')" name="instance"></el-tab-pane>
|
<el-tab-pane :label="t('view.charts.instance_activity.header')" name="instance"></el-tab-pane>
|
||||||
<el-tab-pane :label="t('view.charts.mutual_friend.tab_label')" name="mutual"></el-tab-pane>
|
<el-tab-pane :label="t('view.charts.mutual_friend.tab_label')" name="mutual"></el-tab-pane>
|
||||||
@@ -33,7 +30,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.charts-tabs {
|
:deep(.el-tabs__header) {
|
||||||
margin-bottom: 12px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div ref="instanceActivityRef" class="pt-12">
|
||||||
<div class="options-container instance-activity" style="margin-top: 0">
|
<div class="options-container instance-activity" style="margin-top: 0">
|
||||||
<div>
|
<div>
|
||||||
<span>{{ t('view.charts.instance_activity.header') }}</span>
|
<span>{{ t('view.charts.instance_activity.header') }}</span>
|
||||||
@@ -151,6 +151,33 @@
|
|||||||
const { currentUser } = storeToRefs(useUserStore());
|
const { currentUser } = storeToRefs(useUserStore());
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const instanceActivityRef = ref(null);
|
||||||
|
|
||||||
|
const instanceActivityResizeObserver = new ResizeObserver(() => {
|
||||||
|
setInstanceActivityHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setInstanceActivityHeight() {
|
||||||
|
if (instanceActivityRef.value) {
|
||||||
|
const availableHeight = window.innerHeight - 100;
|
||||||
|
instanceActivityRef.value.style.height = `${availableHeight}px`;
|
||||||
|
instanceActivityRef.value.style.overflowY = 'auto';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (instanceActivityRef.value) {
|
||||||
|
instanceActivityResizeObserver.observe(instanceActivityRef.value);
|
||||||
|
}
|
||||||
|
setInstanceActivityHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (instanceActivityRef.value) {
|
||||||
|
instanceActivityResizeObserver.unobserve(instanceActivityRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
barWidth,
|
barWidth,
|
||||||
isDetailVisible,
|
isDetailVisible,
|
||||||
@@ -623,7 +650,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 100px;
|
margin-top: 100px;
|
||||||
color: #5c5c5c;
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
.divider {
|
.divider {
|
||||||
padding: 0 400px;
|
padding: 0 400px;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mutual-graph">
|
<div class="mutual-graph pt-12" ref="mutualGraphRef">
|
||||||
<div class="options-container mutual-graph__toolbar">
|
<div class="options-container mutual-graph__toolbar">
|
||||||
<div class="mutual-graph__actions">
|
<div class="mutual-graph__actions">
|
||||||
<el-tooltip :content="t('view.charts.mutual_friend.force_dialog.open_label')" placement="top">
|
<el-tooltip :content="t('view.charts.mutual_friend.force_dialog.open_label')" placement="top">
|
||||||
@@ -207,6 +207,20 @@
|
|||||||
return parsed.invalid ? null : parsed.value;
|
return parsed.invalid ? null : parsed.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mutualGraphRef = ref(null);
|
||||||
|
|
||||||
|
const mutualGraphResizeObserver = new ResizeObserver(() => {
|
||||||
|
setMutualGraphHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setMutualGraphHeight() {
|
||||||
|
if (mutualGraphRef.value) {
|
||||||
|
const availableHeight = window.innerHeight - 100;
|
||||||
|
mutualGraphRef.value.style.height = `${availableHeight}px`;
|
||||||
|
mutualGraphRef.value.style.overflowY = 'auto';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (!chartRef.value) {
|
if (!chartRef.value) {
|
||||||
@@ -215,6 +229,8 @@
|
|||||||
createChartInstance();
|
createChartInstance();
|
||||||
resizeObserver = new ResizeObserver(() => chartInstance?.resize());
|
resizeObserver = new ResizeObserver(() => chartInstance?.resize());
|
||||||
resizeObserver.observe(chartRef.value);
|
resizeObserver.observe(chartRef.value);
|
||||||
|
mutualGraphResizeObserver.observe(mutualGraphRef.value);
|
||||||
|
setMutualGraphHeight();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -227,6 +243,9 @@
|
|||||||
chartInstance.dispose();
|
chartInstance.dispose();
|
||||||
chartInstance = null;
|
chartInstance = null;
|
||||||
}
|
}
|
||||||
|
if (mutualGraphResizeObserver) {
|
||||||
|
mutualGraphResizeObserver.disconnect();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -676,8 +695,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 0;
|
margin-top: 8px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|||||||
@@ -1580,6 +1580,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
margin-bottom: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-section__list {
|
.group-section__list {
|
||||||
|
|||||||
@@ -796,6 +796,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
margin-bottom: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-section__list {
|
.group-section__list {
|
||||||
|
|||||||
@@ -1276,6 +1276,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
margin-bottom: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-section__list {
|
.group-section__list {
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="x-container feed">
|
<div class="x-container feed" ref="feedRef">
|
||||||
<div style="margin: 0 0 10px; display: flex; align-items: center">
|
<div style="margin: 0 0 10px; display: flex; align-items: center">
|
||||||
<div style="flex: none; margin-right: 10px; display: flex; align-items: center">
|
<div style="flex: none; margin-right: 10px; display: flex; align-items: center">
|
||||||
<NativeTooltip
|
<el-tooltip placement="bottom" :content="t('view.feed.favorites_only_tooltip')">
|
||||||
placement="bottom"
|
<el-switch
|
||||||
:content="t('view.feed.favorites_only_tooltip')"
|
v-model="feedTable.vip"
|
||||||
:enter-ms="140"
|
active-color="var(--el-color-success)"
|
||||||
:exit-ms="120">
|
@change="feedTableLookup"></el-switch>
|
||||||
<el-switch v-model="feedTable.vip" active-color="#13ce66" @change="feedTableLookup"></el-switch>
|
</el-tooltip>
|
||||||
</NativeTooltip>
|
|
||||||
</div>
|
</div>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="feedTable.filter"
|
v-model="feedTable.filter"
|
||||||
@@ -33,9 +32,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable v-bind="feedTable" :data="feedDisplayData">
|
<DataTable v-bind="feedTable" :data="feedDisplayData">
|
||||||
<el-table-column type="expand" width="20">
|
<el-table-column type="expand" width="30">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div style="position: relative; font-size: 14px">
|
<div style="position: relative; font-size: 14px" class="pl-5">
|
||||||
<template v-if="scope.row.type === 'GPS'">
|
<template v-if="scope.row.type === 'GPS'">
|
||||||
<Location
|
<Location
|
||||||
v-if="scope.row.previousLocation"
|
v-if="scope.row.previousLocation"
|
||||||
@@ -45,9 +44,7 @@
|
|||||||
timeToText(scope.row.time)
|
timeToText(scope.row.time)
|
||||||
}}</el-tag>
|
}}</el-tag>
|
||||||
<br />
|
<br />
|
||||||
<span style="margin-right: 5px">
|
<span style="margin-right: 5px"> ↓ </span>
|
||||||
<el-icon><Right /></el-icon>
|
|
||||||
</span>
|
|
||||||
<Location
|
<Location
|
||||||
v-if="scope.row.location"
|
v-if="scope.row.location"
|
||||||
:location="scope.row.location"
|
:location="scope.row.location"
|
||||||
@@ -91,7 +88,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<span style="position: relative; margin: 0 10px">
|
<span style="position: relative; margin: 0 10px">
|
||||||
<el-icon><Right /></el-icon>
|
{{ ' → ' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div style="display: inline-block; vertical-align: top; width: 160px">
|
<div style="display: inline-block; vertical-align: top; width: 160px">
|
||||||
@@ -116,9 +113,7 @@
|
|||||||
<i class="x-user-status" :class="statusClass(scope.row.previousStatus)"></i>
|
<i class="x-user-status" :class="statusClass(scope.row.previousStatus)"></i>
|
||||||
<span style="margin-left: 5px" v-text="scope.row.previousStatusDescription"></span>
|
<span style="margin-left: 5px" v-text="scope.row.previousStatusDescription"></span>
|
||||||
<br />
|
<br />
|
||||||
<span>
|
<span> → </span>
|
||||||
<el-icon><Right /></el-icon>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<i class="x-user-status" :class="statusClass(scope.row.status)" style="margin: 0 5px"></i>
|
<i class="x-user-status" :class="statusClass(scope.row.status)" style="margin: 0 5px"></i>
|
||||||
<span v-text="scope.row.statusDescription"></span>
|
<span v-text="scope.row.statusDescription"></span>
|
||||||
@@ -132,27 +127,29 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column :label="t('table.feed.date')" prop="created_at" width="130">
|
<el-table-column :label="t('table.feed.date')" prop="created_at" width="140">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<NativeTooltip placement="right">
|
<el-tooltip placement="right">
|
||||||
<template #content>
|
<template #content>
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'short') }}</span>
|
<span>{{ formatDateFilter(scope.row.created_at, 'short') }}</span>
|
||||||
</NativeTooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column :label="t('table.feed.type')" prop="type" width="80">
|
<el-table-column :label="t('table.feed.type')" prop="type" width="130">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span v-text="t('view.feed.filters.' + scope.row.type)"></span>
|
<el-tag type="info" effect="plain" size="small">{{
|
||||||
|
t('view.feed.filters.' + scope.row.type)
|
||||||
|
}}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column :label="t('table.feed.user')" prop="displayName" width="180">
|
<el-table-column :label="t('table.feed.user')" prop="displayName" width="190">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span
|
<span
|
||||||
class="x-link"
|
class="x-link table-user"
|
||||||
style="padding-right: 10px"
|
style="padding-right: 10px"
|
||||||
@click="showUserDialog(scope.row.userId)"
|
@click="showUserDialog(scope.row.userId)"
|
||||||
v-text="scope.row.displayName"></span>
|
v-text="scope.row.displayName"></span>
|
||||||
@@ -178,17 +175,12 @@
|
|||||||
<template v-else-if="scope.row.type === 'Status'">
|
<template v-else-if="scope.row.type === 'Status'">
|
||||||
<template v-if="scope.row.statusDescription === scope.row.previousStatusDescription">
|
<template v-if="scope.row.statusDescription === scope.row.previousStatusDescription">
|
||||||
<i class="x-user-status" :class="statusClass(scope.row.previousStatus)"></i>
|
<i class="x-user-status" :class="statusClass(scope.row.previousStatus)"></i>
|
||||||
<span style="margin: 0 5px">
|
<span class="mx-2"> → </span>
|
||||||
<el-icon><Right /></el-icon>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<i class="x-user-status" :class="statusClass(scope.row.status)"></i>
|
<i class="x-user-status" :class="statusClass(scope.row.status)"></i>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<i
|
<i class="x-user-status mr-2" :class="statusClass(scope.row.status)"></i>
|
||||||
class="x-user-status"
|
|
||||||
:class="statusClass(scope.row.status)"
|
|
||||||
style="margin-right: 3px"></i>
|
|
||||||
<span v-text="scope.row.statusDescription"></span>
|
<span v-text="scope.row.statusDescription"></span>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -210,13 +202,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Right } from '@element-plus/icons-vue';
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { formatDateFilter, statusClass, timeToText } from '../../shared/utils';
|
import { formatDateFilter, statusClass, timeToText } from '../../shared/utils';
|
||||||
import { useFeedStore, useUserStore } from '../../stores';
|
import { useFeedStore, useUserStore } from '../../stores';
|
||||||
|
import { useTableHeight } from '../../composables/useTableHeight';
|
||||||
|
|
||||||
const { showUserDialog } = useUserStore();
|
const { showUserDialog } = useUserStore();
|
||||||
const { feedTable } = storeToRefs(useFeedStore());
|
const { feedTable } = storeToRefs(useFeedStore());
|
||||||
@@ -226,6 +218,8 @@
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const { containerRef: feedRef } = useTableHeight(feedTable);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function that format the differences between two strings with HTML tags
|
* Function that format the differences between two strings with HTML tags
|
||||||
* markerStartTag and markerEndTag are optional, if emitted, the differences will be highlighted with yellow and underlined.
|
* markerStartTag and markerEndTag are optional, if emitted, the differences will be highlighted with yellow and underlined.
|
||||||
@@ -341,3 +335,9 @@
|
|||||||
.replace(/<br> /g, '<br>');
|
.replace(/<br> /g, '<br>');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.table-user {
|
||||||
|
color: var(--x-table-user-text-color) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,92 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="x-container">
|
<div class="x-container" ref="friendsListRef">
|
||||||
<div style="padding: 0 10px 0 10px">
|
<div>
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<span class="header">{{ t('view.friend_list.header') }}</span>
|
|
||||||
<div style="font-size: 13px; display: flex; align-items: center">
|
|
||||||
<el-button size="small" @click="openChartsTab" style="margin-right: 10px">
|
|
||||||
{{ t('view.friend_list.load_mutual_friends') }}
|
|
||||||
</el-button>
|
|
||||||
<div v-if="friendsListBulkUnfriendMode" style="display: inline-block; margin-right: 10px">
|
|
||||||
<el-button size="small" @click="showBulkUnfriendSelectionConfirm">
|
|
||||||
{{ t('view.friend_list.bulk_unfriend_selection') }}
|
|
||||||
</el-button>
|
|
||||||
<!-- el-button(size="small" @click="showBulkUnfriendAllConfirm" style="margin-right:5px") Bulk Unfriend All-->
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; align-items: center; margin-right: 10px">
|
|
||||||
<span class="name">{{ t('view.friend_list.bulk_unfriend') }}</span>
|
|
||||||
<el-switch
|
|
||||||
v-model="friendsListBulkUnfriendMode"
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@change="toggleFriendsListBulkUnfriendMode"></el-switch>
|
|
||||||
</div>
|
|
||||||
<span>{{ t('view.friend_list.load') }}</span>
|
|
||||||
<template v-if="friendsListLoading">
|
|
||||||
<span style="margin-left: 5px" v-text="friendsListLoadingProgress"></span>
|
|
||||||
<el-tooltip placement="top" :content="t('view.friend_list.cancel_tooltip')">
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
:icon="Loading"
|
|
||||||
circle
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@click="friendsListLoading = false"></el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<el-tooltip placement="top" :content="t('view.friend_list.load_tooltip')">
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
:icon="RefreshLeft"
|
|
||||||
circle
|
|
||||||
style="margin-left: 5px"
|
|
||||||
@click="friendsListLoadUsers"></el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin: 10px 0 0 10px; display: flex; align-items: center">
|
|
||||||
<div style="flex: none; margin-right: 10px; display: flex; align-items: center">
|
<div style="flex: none; margin-right: 10px; display: flex; align-items: center">
|
||||||
<el-tooltip placement="bottom" :content="t('view.friend_list.favorites_only_tooltip')">
|
<el-tooltip placement="bottom" :content="t('view.friend_list.favorites_only_tooltip')">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="friendsListSearchFilterVIP"
|
v-model="friendsListSearchFilterVIP"
|
||||||
active-color="#13ce66"
|
active-color="var(--el-color-success)"
|
||||||
@change="friendsListSearchChange"></el-switch>
|
@change="friendsListSearchChange"></el-switch>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<el-select
|
||||||
|
v-model="friendsListSearchFilters"
|
||||||
|
multiple
|
||||||
|
clearable
|
||||||
|
collapse-tags
|
||||||
|
style="margin: 0 10px; width: 150px"
|
||||||
|
:placeholder="t('view.friend_list.filter_placeholder')"
|
||||||
|
@change="friendsListSearchChange">
|
||||||
|
<el-option
|
||||||
|
v-for="type in ['Display Name', 'User Name', 'Rank', 'Status', 'Bio', 'Note', 'Memo']"
|
||||||
|
:key="type"
|
||||||
|
:label="type"
|
||||||
|
:value="type"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-model="friendsListSearch"
|
||||||
|
:placeholder="t('view.friend_list.search_placeholder')"
|
||||||
|
clearable
|
||||||
|
style="width: 250px"
|
||||||
|
@change="friendsListSearchChange"></el-input>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div v-if="friendsListBulkUnfriendMode" class="inline-block mr-10">
|
||||||
|
<el-button @click="showBulkUnfriendSelectionConfirm">
|
||||||
|
{{ t('view.friend_list.bulk_unfriend_selection') }}
|
||||||
|
</el-button>
|
||||||
|
<!-- el-button(size="small" @click="showBulkUnfriendAllConfirm" style="margin-right:5px") Bulk Unfriend All-->
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center mr-3">
|
||||||
|
<span class="name mr-2 text-xs">{{ t('view.friend_list.bulk_unfriend') }}</span>
|
||||||
|
<el-switch
|
||||||
|
v-model="friendsListBulkUnfriendMode"
|
||||||
|
@change="toggleFriendsListBulkUnfriendMode"></el-switch>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<el-button @click="openChartsTab">
|
||||||
|
{{ t('view.friend_list.load_mutual_friends') }}
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button @click="friendsListLoadUsers">{{ t('view.friend_list.load') }}</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-input
|
|
||||||
v-model="friendsListSearch"
|
|
||||||
:placeholder="t('view.friend_list.search_placeholder')"
|
|
||||||
clearable
|
|
||||||
style="flex: 1"
|
|
||||||
@change="friendsListSearchChange"></el-input>
|
|
||||||
<el-select
|
|
||||||
v-model="friendsListSearchFilters"
|
|
||||||
multiple
|
|
||||||
clearable
|
|
||||||
collapse-tags
|
|
||||||
style="flex: 0.3; margin: 0 10px"
|
|
||||||
:placeholder="t('view.friend_list.filter_placeholder')"
|
|
||||||
@change="friendsListSearchChange">
|
|
||||||
<el-option
|
|
||||||
v-for="type in ['Display Name', 'User Name', 'Rank', 'Status', 'Bio', 'Note', 'Memo']"
|
|
||||||
:key="type"
|
|
||||||
:label="type"
|
|
||||||
:value="type"></el-option>
|
|
||||||
</el-select>
|
|
||||||
<el-tooltip placement="top" :content="t('view.friend_list.refresh_tooltip')">
|
|
||||||
<el-button
|
|
||||||
type="default"
|
|
||||||
:icon="Refresh"
|
|
||||||
circle
|
|
||||||
style="flex: none"
|
|
||||||
@click="friendsListSearchChange"></el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
<DataTable
|
<DataTable
|
||||||
v-loading="friendsListLoading"
|
|
||||||
v-bind="friendsListTable"
|
v-bind="friendsListTable"
|
||||||
:table-props="{ height: 'calc(100vh - 170px)', size: 'small' }"
|
|
||||||
style="margin-top: 10px; cursor: pointer"
|
style="margin-top: 10px; cursor: pointer"
|
||||||
@row-click="selectFriendsListRow">
|
@row-click="selectFriendsListRow">
|
||||||
<el-table-column v-if="friendsListBulkUnfriendMode" width="55">
|
<el-table-column v-if="friendsListBulkUnfriendMode" width="55">
|
||||||
@@ -98,39 +65,38 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="t('table.friendList.no')" width="70" prop="$friendNumber" :sortable="true">
|
<el-table-column width="20"></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
:label="t('table.friendList.no')"
|
||||||
|
width="70"
|
||||||
|
prop="$friendNumber"
|
||||||
|
:sortable="true"
|
||||||
|
fixed="left">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span>{{ row.$friendNumber ? row.$friendNumber : '' }}</span>
|
<span>{{ row.$friendNumber ? row.$friendNumber : '' }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="t('table.friendList.avatar')" width="70" prop="photo">
|
<el-table-column :label="t('table.friendList.avatar')" width="90" prop="photo">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-popover placement="right" :width="500" trigger="hover">
|
<div class="flex items-center">
|
||||||
<template #reference>
|
<img :src="userImage(row, true)" class="friends-list-avatar" loading="lazy" />
|
||||||
<img :src="userImage(row, true)" class="friends-list-avatar" loading="lazy" />
|
</div>
|
||||||
</template>
|
|
||||||
<img
|
|
||||||
:src="userImageFull(row)"
|
|
||||||
:class="['friends-list-avatar', 'x-popover-image']"
|
|
||||||
style="cursor: pointer"
|
|
||||||
@click="showFullscreenImageDialog(userImageFull(row))"
|
|
||||||
loading="lazy" />
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="t('table.friendList.displayName')"
|
:label="t('table.friendList.displayName')"
|
||||||
min-width="140"
|
min-width="200"
|
||||||
prop="displayName"
|
prop="displayName"
|
||||||
sortable
|
sortable
|
||||||
:sort-method="(a, b) => sortAlphabetically(a, b, 'displayName')">
|
:sort-method="(a, b) => sortAlphabetically(a, b, 'displayName')"
|
||||||
|
fixed="left">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span :style="{ color: randomUserColours ? row.$userColour : undefined }" class="name">{{
|
<span :style="{ color: randomUserColours ? row.$userColour : undefined }" class="name">{{
|
||||||
row.displayName
|
row.displayName
|
||||||
}}</span>
|
}}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="t('table.friendList.rank')" width="110" prop="$trustSortNum" :sortable="true">
|
<el-table-column :label="t('table.friendList.rank')" width="140" prop="$trustSortNum" :sortable="true">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span
|
<span
|
||||||
v-if="randomUserColours"
|
v-if="randomUserColours"
|
||||||
@@ -142,7 +108,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="t('table.friendList.status')"
|
:label="t('table.friendList.status')"
|
||||||
min-width="180"
|
min-width="200"
|
||||||
prop="status"
|
prop="status"
|
||||||
sortable
|
sortable
|
||||||
:sort-method="(a, b) => sortStatus(a.status, b.status)">
|
:sort-method="(a, b) => sortStatus(a.status, b.status)">
|
||||||
@@ -157,7 +123,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="t('table.friendList.language')"
|
:label="t('table.friendList.language')"
|
||||||
width="110"
|
width="130"
|
||||||
prop="$languages"
|
prop="$languages"
|
||||||
sortable
|
sortable
|
||||||
:sort-method="(a, b) => sortLanguages(a, b)">
|
:sort-method="(a, b) => sortLanguages(a, b)">
|
||||||
@@ -173,7 +139,7 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="t('table.friendList.bioLink')" width="100" prop="bioLinks">
|
<el-table-column :label="t('table.friendList.bioLink')" width="130" prop="bioLinks">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip v-for="(link, index) in row.bioLinks.filter(Boolean)" :key="index">
|
<el-tooltip v-for="(link, index) in row.bioLinks.filter(Boolean)" :key="index">
|
||||||
<template #content>
|
<template #content>
|
||||||
@@ -197,8 +163,14 @@
|
|||||||
:label="t('table.friendList.joinCount')"
|
:label="t('table.friendList.joinCount')"
|
||||||
width="120"
|
width="120"
|
||||||
prop="$joinCount"
|
prop="$joinCount"
|
||||||
sortable></el-table-column>
|
sortable
|
||||||
<el-table-column :label="t('table.friendList.timeTogether')" width="140" prop="$timeSpent" sortable>
|
align="right"></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
:label="t('table.friendList.timeTogether')"
|
||||||
|
width="140"
|
||||||
|
prop="$timeSpent"
|
||||||
|
sortable
|
||||||
|
align="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.$timeSpent">{{ timeToText(row.$timeSpent) }}</span>
|
<span v-if="row.$timeSpent">{{ timeToText(row.$timeSpent) }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -210,17 +182,26 @@
|
|||||||
sortable
|
sortable
|
||||||
:sort-method="(a, b) => sortAlphabetically(a, b, '$lastSeen')">
|
:sort-method="(a, b) => sortAlphabetically(a, b, '$lastSeen')">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span>{{ formatDateFilter(row.$lastSeen, 'long') }}</span>
|
<span>{{
|
||||||
|
formatDateFilter(row.$lastSeen, 'long') === '-'
|
||||||
|
? ''
|
||||||
|
: formatDateFilter(row.$lastSeen, 'long')
|
||||||
|
}}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="t('table.friendList.mutualFriends')" width="120" prop="$mutualCount" sortable>
|
<el-table-column
|
||||||
|
:label="t('table.friendList.mutualFriends')"
|
||||||
|
width="120"
|
||||||
|
prop="$mutualCount"
|
||||||
|
sortable
|
||||||
|
align="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.$mutualCount">{{ row.$mutualCount }}</span>
|
<span v-if="row.$mutualCount">{{ row.$mutualCount }}</span>
|
||||||
<span v-else></span> </template
|
<span v-else></span> </template
|
||||||
></el-table-column>
|
></el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="t('table.friendList.lastActivity')"
|
:label="t('table.friendList.lastActivity')"
|
||||||
width="170"
|
width="200"
|
||||||
prop="last_activity"
|
prop="last_activity"
|
||||||
sortable
|
sortable
|
||||||
:sort-method="(a, b) => sortAlphabetically(a, b, 'last_activity')">
|
:sort-method="(a, b) => sortAlphabetically(a, b, 'last_activity')">
|
||||||
@@ -230,7 +211,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="t('table.friendList.lastLogin')"
|
:label="t('table.friendList.lastLogin')"
|
||||||
width="170"
|
width="200"
|
||||||
prop="last_login"
|
prop="last_login"
|
||||||
sortable
|
sortable
|
||||||
:sort-method="(a, b) => sortAlphabetically(a, b, 'last_login')">
|
:sort-method="(a, b) => sortAlphabetically(a, b, 'last_login')">
|
||||||
@@ -246,23 +227,42 @@
|
|||||||
:sort-method="(a, b) => sortAlphabetically(a, b, 'date_joined')"></el-table-column>
|
:sort-method="(a, b) => sortAlphabetically(a, b, 'date_joined')"></el-table-column>
|
||||||
<el-table-column :label="t('table.friendList.unfriend')" width="100" align="center">
|
<el-table-column :label="t('table.friendList.unfriend')" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button
|
<i
|
||||||
text
|
class="ri-user-unfollow-line"
|
||||||
:icon="Close"
|
|
||||||
style="color: #f56c6c"
|
style="color: #f56c6c"
|
||||||
size="small"
|
@click.stop="confirmDeleteFriend(row.id)"></i>
|
||||||
@click.stop="confirmDeleteFriend(row.id)"></el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
|
<el-dialog
|
||||||
|
v-model="friendsListLoadDialogVisible"
|
||||||
|
:title="t('view.friend_list.load_dialog_title')"
|
||||||
|
width="420px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:show-close="false"
|
||||||
|
align-center>
|
||||||
|
<div style="margin-bottom: 10px" v-text="t('view.friend_list.load_dialog_message')"></div>
|
||||||
|
<el-progress
|
||||||
|
:percentage="friendsListLoadingPercent"
|
||||||
|
:text-inside="true"
|
||||||
|
:stroke-width="16"></el-progress>
|
||||||
|
<div style="margin-top: 10px; text-align: right">
|
||||||
|
<span>{{ friendsListLoadingCurrent }} / {{ friendsListLoadingTotal }}</span>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="cancelFriendsListLoad">
|
||||||
|
{{ t('view.friend_list.load_cancel') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Close, Loading, Refresh, RefreshLeft } from '@element-plus/icons-vue';
|
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||||
import { nextTick, reactive, ref, watch } from 'vue';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { ElMessageBox } from 'element-plus';
|
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
@@ -276,19 +276,13 @@
|
|||||||
sortStatus,
|
sortStatus,
|
||||||
statusClass,
|
statusClass,
|
||||||
timeToText,
|
timeToText,
|
||||||
userImage,
|
userImage
|
||||||
userImageFull
|
|
||||||
} from '../../shared/utils';
|
} from '../../shared/utils';
|
||||||
import {
|
import { useAppearanceSettingsStore, useFriendStore, useSearchStore, useUserStore } from '../../stores';
|
||||||
useAppearanceSettingsStore,
|
|
||||||
useFriendStore,
|
|
||||||
useGalleryStore,
|
|
||||||
useSearchStore,
|
|
||||||
useUserStore
|
|
||||||
} from '../../stores';
|
|
||||||
import { friendRequest, userRequest } from '../../api';
|
import { friendRequest, userRequest } from '../../api';
|
||||||
import removeConfusables, { removeWhitespace } from '../../service/confusables';
|
import removeConfusables, { removeWhitespace } from '../../service/confusables';
|
||||||
import { router } from '../../plugin/router';
|
import { router } from '../../plugin/router';
|
||||||
|
import { useTableHeight } from '../../composables/useTableHeight';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -299,21 +293,34 @@
|
|||||||
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
|
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
|
||||||
const { showUserDialog } = useUserStore();
|
const { showUserDialog } = useUserStore();
|
||||||
const { stringComparer, friendsListSearch } = storeToRefs(useSearchStore());
|
const { stringComparer, friendsListSearch } = storeToRefs(useSearchStore());
|
||||||
const { showFullscreenImageDialog } = useGalleryStore();
|
|
||||||
|
|
||||||
const friendsListSearchFilters = ref([]);
|
const friendsListSearchFilters = ref([]);
|
||||||
const friendsListTable = reactive({
|
const friendsListTable = reactive({
|
||||||
data: [],
|
data: [],
|
||||||
tableProps: { stripe: true, size: 'small', defaultSort: { prop: '$friendNumber', order: 'descending' } },
|
tableProps: {
|
||||||
|
stripe: true,
|
||||||
|
size: 'small',
|
||||||
|
defaultSort: { prop: '$friendNumber', order: 'descending' },
|
||||||
|
scrollbarAlwaysOn: true
|
||||||
|
},
|
||||||
pageSize: 100,
|
pageSize: 100,
|
||||||
paginationProps: { layout: 'sizes,prev,pager,next,total', pageSizes: [50, 100, 250, 500] }
|
paginationProps: { layout: 'sizes,prev,pager,next,total', pageSizes: [50, 100, 250, 500] }
|
||||||
});
|
});
|
||||||
const friendsListBulkUnfriendMode = ref(false);
|
const friendsListBulkUnfriendMode = ref(false);
|
||||||
const friendsListLoading = ref(false);
|
const friendsListLoading = ref(false);
|
||||||
const friendsListLoadingProgress = ref('');
|
const friendsListLoadingCurrent = ref(0);
|
||||||
|
const friendsListLoadingTotal = ref(0);
|
||||||
|
const friendsListLoadDialogVisible = ref(false);
|
||||||
const friendsListSearchFilterVIP = ref(false);
|
const friendsListSearchFilterVIP = ref(false);
|
||||||
const selectedFriends = ref(new Set());
|
const selectedFriends = ref(new Set());
|
||||||
|
|
||||||
|
const friendsListLoadingPercent = computed(() => {
|
||||||
|
if (!friendsListLoadingTotal.value) return 0;
|
||||||
|
return Math.min(100, Math.round((friendsListLoadingCurrent.value / friendsListLoadingTotal.value) * 100));
|
||||||
|
});
|
||||||
|
|
||||||
|
const { containerRef: friendsListRef } = useTableHeight(ref(friendsListTable));
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -432,27 +439,43 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function friendsListLoadUsers() {
|
async function friendsListLoadUsers() {
|
||||||
friendsListLoading.value = true;
|
|
||||||
let i = 0;
|
|
||||||
const toFetch = Array.from(friends.value.values())
|
const toFetch = Array.from(friends.value.values())
|
||||||
.filter((ctx) => ctx.ref && !ctx.ref.date_joined)
|
.filter((ctx) => ctx.ref && !ctx.ref.date_joined)
|
||||||
.map((ctx) => ctx.id);
|
.map((ctx) => ctx.id);
|
||||||
const total = toFetch.length;
|
const total = toFetch.length;
|
||||||
|
friendsListLoadingTotal.value = total;
|
||||||
|
friendsListLoadingCurrent.value = 0;
|
||||||
|
if (!total) {
|
||||||
|
ElMessage.success(t('view.friend_list.load_complete'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
friendsListLoading.value = true;
|
||||||
|
friendsListLoadDialogVisible.value = true;
|
||||||
|
let cancelled = false;
|
||||||
for (const userId of toFetch) {
|
for (const userId of toFetch) {
|
||||||
if (!friendsListLoading.value) {
|
if (!friendsListLoading.value) {
|
||||||
friendsListLoadingProgress.value = '';
|
cancelled = true;
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
i++;
|
friendsListLoadingCurrent.value += 1;
|
||||||
friendsListLoadingProgress.value = `${i}/${total}`;
|
|
||||||
try {
|
try {
|
||||||
await userRequest.getUser({ userId });
|
await userRequest.getUser({ userId });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
friendsListLoadingProgress.value = '';
|
|
||||||
friendsListLoading.value = false;
|
friendsListLoading.value = false;
|
||||||
|
friendsListLoadDialogVisible.value = false;
|
||||||
|
friendsListLoadingCurrent.value = 0;
|
||||||
|
friendsListLoadingTotal.value = 0;
|
||||||
|
if (!cancelled) {
|
||||||
|
ElMessage.success(t('view.friend_list.load_complete'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelFriendsListLoad() {
|
||||||
|
friendsListLoading.value = false;
|
||||||
|
friendsListLoadDialogVisible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectFriendsListRow(val) {
|
function selectFriendsListRow(val) {
|
||||||
@@ -476,3 +499,11 @@
|
|||||||
router.push({ name: 'charts' });
|
router.push({ name: 'charts' });
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.friends-list-avatar {
|
||||||
|
object-fit: cover;
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="x-container">
|
<div class="x-container" ref="friendLogRef">
|
||||||
<!-- 工具栏 -->
|
|
||||||
<div style="margin: 0 0 10px; display: flex; align-items: center">
|
<div style="margin: 0 0 10px; display: flex; align-items: center">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="friendLogTable.filters[0].value"
|
v-model="friendLogTable.filters[0].value"
|
||||||
@@ -40,27 +39,25 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column :label="t('table.friendLog.type')" prop="type" width="150">
|
<el-table-column :label="t('table.friendLog.type')" prop="type" width="200">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span v-text="t('view.friend_log.filters.' + scope.row.type)"></span>
|
<el-tag type="info" effect="plain" size="small"
|
||||||
|
><span v-text="t('view.friend_log.filters.' + scope.row.type)"></span
|
||||||
|
></el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column :label="t('table.friendLog.user')" prop="displayName">
|
<el-table-column :label="t('table.friendLog.user')" prop="displayName">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span v-if="scope.row.type === 'DisplayName'">
|
<span v-if="scope.row.type === 'DisplayName'">{{ scope.row.previousDisplayName }} → </span>
|
||||||
{{ scope.row.previousDisplayName }} <el-icon><Right /></el-icon>
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
class="x-link"
|
class="x-link table-user"
|
||||||
style="padding-right: 10px"
|
style="padding-right: 10px"
|
||||||
@click="showUserDialog(scope.row.userId)"
|
@click="showUserDialog(scope.row.userId)"
|
||||||
v-text="scope.row.displayName || scope.row.userId"></span>
|
>{{ scope.row.displayName || scope.row.userId }}
|
||||||
|
</span>
|
||||||
<template v-if="scope.row.type === 'TrustLevel'">
|
<template v-if="scope.row.type === 'TrustLevel'">
|
||||||
<span>
|
<span>({{ scope.row.previousTrustLevel }} → {{ scope.row.trustLevel }})</span>
|
||||||
({{ scope.row.previousTrustLevel }} <el-icon><Right /></el-icon>
|
|
||||||
{{ scope.row.trustLevel }})</span
|
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -69,28 +66,27 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="shiftHeld"
|
v-if="shiftHeld"
|
||||||
style="color: #f56c6c"
|
style="color: var(--el-color-danger)"
|
||||||
text
|
text
|
||||||
:icon="Close"
|
:icon="Close"
|
||||||
size="small"
|
size="small"
|
||||||
class="button-pd-0"
|
class="button-pd-0"
|
||||||
@click="deleteFriendLog(scope.row)"></el-button>
|
@click="deleteFriendLog(scope.row)"></el-button>
|
||||||
<el-button
|
<i
|
||||||
v-else
|
v-else
|
||||||
text
|
class="ri-delete-bin-line"
|
||||||
:icon="Delete"
|
style="opacity: 0.85"
|
||||||
size="small"
|
@click="deleteFriendLogPrompt(scope.row)"></i>
|
||||||
class="button-pd-0"
|
|
||||||
@click="deleteFriendLogPrompt(scope.row)"></el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column width="5"></el-table-column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Close, Delete, Right } from '@element-plus/icons-vue';
|
|
||||||
import { computed, watch } from 'vue';
|
import { computed, watch } from 'vue';
|
||||||
|
import { Close } from '@element-plus/icons-vue';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
@@ -100,6 +96,7 @@
|
|||||||
import { useAppearanceSettingsStore, useFriendStore, useUiStore, useUserStore } from '../../stores';
|
import { useAppearanceSettingsStore, useFriendStore, useUiStore, useUserStore } from '../../stores';
|
||||||
import { formatDateFilter, removeFromArray } from '../../shared/utils';
|
import { formatDateFilter, removeFromArray } from '../../shared/utils';
|
||||||
import { database } from '../../service/database';
|
import { database } from '../../service/database';
|
||||||
|
import { useTableHeight } from '../../composables/useTableHeight';
|
||||||
|
|
||||||
import configRepository from '../../service/config';
|
import configRepository from '../../service/config';
|
||||||
|
|
||||||
@@ -108,6 +105,8 @@
|
|||||||
const { friendLogTable } = storeToRefs(useFriendStore());
|
const { friendLogTable } = storeToRefs(useFriendStore());
|
||||||
const { shiftHeld } = storeToRefs(useUiStore());
|
const { shiftHeld } = storeToRefs(useUiStore());
|
||||||
|
|
||||||
|
const { containerRef: friendLogRef } = useTableHeight(friendLogTable);
|
||||||
|
|
||||||
const friendLogDisplayData = computed(() => {
|
const friendLogDisplayData = computed(() => {
|
||||||
const data = friendLogTable.value.data;
|
const data = friendLogTable.value.data;
|
||||||
return data.slice().sort((a, b) => {
|
return data.slice().sort((a, b) => {
|
||||||
@@ -160,4 +159,7 @@
|
|||||||
.button-pd-0 {
|
.button-pd-0 {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
.table-user {
|
||||||
|
color: var(--x-table-user-text-color);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<el-slider
|
<el-slider
|
||||||
v-model="cardScale"
|
v-model="cardScale"
|
||||||
class="friend-view__slider"
|
class="friend-view__slider"
|
||||||
:min="0.6"
|
:min="0.5"
|
||||||
:max="1.0"
|
:max="1.0"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:show-tooltip="false" />
|
:show-tooltip="false" />
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
<el-slider
|
<el-slider
|
||||||
v-model="cardSpacing"
|
v-model="cardSpacing"
|
||||||
class="friend-view__slider"
|
class="friend-view__slider"
|
||||||
:min="0.5"
|
:min="0.25"
|
||||||
:max="1.5"
|
:max="1.0"
|
||||||
:step="0.05"
|
:step="0.05"
|
||||||
:show-tooltip="false" />
|
:show-tooltip="false" />
|
||||||
</div>
|
</div>
|
||||||
@@ -688,7 +688,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped>
|
||||||
.friend-view {
|
.friend-view {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
@@ -699,12 +699,12 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 6px 10px 0 2px;
|
padding: 6px 2px 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__toolbar--loading {
|
.friend-view__toolbar--loading {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
color: rgba(15, 23, 42, 0.55);
|
color: var(--el-text-color-secondary);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -720,7 +720,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
color: rgba(15, 23, 42, 0.65);
|
color: var(--el-text-color-regular);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__settings-label {
|
.friend-view__settings-label {
|
||||||
@@ -746,7 +746,7 @@
|
|||||||
.friend-view__scale-value {
|
.friend-view__scale-value {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: rgba(15, 23, 42, 0.55);
|
color: var(--el-text-color-secondary);
|
||||||
min-width: 42px;
|
min-width: 42px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -762,14 +762,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__scroll {
|
.friend-view__scroll {
|
||||||
padding: 2px 10px 2px 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__initial-loading {
|
.friend-view__initial-loading {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
min-height: 240px;
|
min-height: 240px;
|
||||||
color: rgba(15, 23, 42, 0.45);
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__grid {
|
.friend-view__grid {
|
||||||
@@ -780,7 +780,7 @@
|
|||||||
);
|
);
|
||||||
gap: var(--friend-card-gap, 18px);
|
gap: var(--friend-card-gap, 18px);
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
padding-right: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__instances {
|
.friend-view__instances {
|
||||||
@@ -802,7 +802,7 @@
|
|||||||
margin: 5px 10px;
|
margin: 5px 10px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: rgba(15, 23, 42, 0.75);
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__divider {
|
.friend-view__divider {
|
||||||
@@ -810,7 +810,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin: 16px 4px;
|
margin: 16px 4px;
|
||||||
color: rgba(15, 23, 42, 0.6);
|
color: var(--el-text-color-regular);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -820,7 +820,7 @@
|
|||||||
content: '';
|
content: '';
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: rgba(148, 163, 184, 0.35);
|
background: var(--el-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__divider-text {
|
.friend-view__divider-text {
|
||||||
@@ -829,14 +829,14 @@
|
|||||||
|
|
||||||
.friend-view__instance-count {
|
.friend-view__instance-count {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: rgba(15, 23, 42, 0.45);
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__empty {
|
.friend-view__empty {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
min-height: 240px;
|
min-height: 240px;
|
||||||
color: rgba(0, 0, 0, 0.45);
|
color: var(--el-text-color-secondary);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
@@ -847,7 +847,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 18px 0 12px;
|
padding: 18px 0 12px;
|
||||||
color: rgba(0, 0, 0, 0.55);
|
color: var(--el-text-color-secondary);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,11 @@
|
|||||||
</el-avatar>
|
</el-avatar>
|
||||||
</div>
|
</div>
|
||||||
<span class="friend-card__status-dot" :class="statusDotClass"></span>
|
<span class="friend-card__status-dot" :class="statusDotClass"></span>
|
||||||
<div class="friend-card__name" :title="friend.name">{{ friend.name }}</div>
|
<div class="friend-card__name ml-0.5" :title="friend.name">{{ friend.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="friend-card__body">
|
<div class="friend-card__body">
|
||||||
<div class="friend-card__signature" :title="friend.ref?.statusDescription">
|
<div class="friend-card__signature" :title="friend.ref?.statusDescription">
|
||||||
<i v-if="friend.ref?.statusDescription" class="ri-pencil-line" style="opacity: 0.7"></i>
|
<i v-if="friend.ref?.statusDescription" class="ri-pencil-line mr-0.5" style="opacity: 0.7"></i>
|
||||||
{{ friend.ref?.statusDescription || ' ' }}
|
{{ friend.ref?.statusDescription || ' ' }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="displayInstanceInfo" @click.stop class="friend-card__world" :title="friend.worldName">
|
<div v-if="displayInstanceInfo" @click.stop class="friend-card__world" :title="friend.worldName">
|
||||||
@@ -87,17 +87,17 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped>
|
||||||
.friend-card {
|
.friend-card {
|
||||||
--card-scale: 1;
|
--card-scale: 1;
|
||||||
--card-spacing: 1;
|
--card-spacing: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: calc(14px * var(--card-scale) * var(--card-spacing));
|
gap: calc(14px * var(--card-scale) * var(--card-spacing));
|
||||||
border-radius: calc(8px * var(--card-scale));
|
border-radius: 8px;
|
||||||
background: #fff;
|
background: var(--el-bg-color-overlay);
|
||||||
border: 1px solid var(--el-border-color);
|
border: 1px solid var(--el-border-color);
|
||||||
box-shadow: 0 calc(6px * var(--card-scale)) calc(16px * var(--card-scale)) rgba(15, 23, 42, 0.04);
|
box-shadow: var(--el-box-shadow-lighter);
|
||||||
transition:
|
transition:
|
||||||
box-shadow 0.2s ease,
|
box-shadow 0.2s ease,
|
||||||
transform 0.2s ease;
|
transform 0.2s ease;
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
min-width: var(--friend-card-min-width, 220px);
|
min-width: var(--friend-card-min-width, 220px);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 calc(10px * var(--card-scale)) calc(24px * var(--card-scale)) rgba(15, 23, 42, 0.07);
|
box-shadow: var(--el-box-shadow-light);
|
||||||
transform: translateY(calc(-2px * var(--card-scale)));
|
transform: translateY(calc(-2px * var(--card-scale)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,8 +123,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.friend-card__avatar {
|
.friend-card__avatar {
|
||||||
border: 1px solid rgba(255, 255, 255, 0.85);
|
border: 1px solid var(--el-border-color);
|
||||||
box-shadow: 0 calc(5px * var(--card-scale)) calc(10px * var(--card-scale)) rgba(15, 23, 42, 0.14);
|
box-shadow: var(--el-box-shadow-lighter);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-card__status-dot {
|
.friend-card__status-dot {
|
||||||
@@ -134,8 +134,8 @@
|
|||||||
inline-size: calc(12px * var(--card-scale));
|
inline-size: calc(12px * var(--card-scale));
|
||||||
block-size: calc(12px * var(--card-scale));
|
block-size: calc(12px * var(--card-scale));
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
border: calc(2px * var(--card-scale)) solid #fff;
|
border: calc(2px * var(--card-scale)) solid var(--el-bg-color-overlay);
|
||||||
box-shadow: 0 0 calc(4px * var(--card-scale)) rgba(15, 23, 42, 0.12);
|
box-shadow: var(--el-box-shadow-lighter);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,23 +144,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.friend-card__status-dot--online {
|
.friend-card__status-dot--online {
|
||||||
background: linear-gradient(145deg, #67c23a, #4aa12d);
|
background: #67c23a;
|
||||||
box-shadow: 0 0 calc(8px * var(--card-scale)) rgba(103, 194, 58, 0.4);
|
box-shadow: 0 0 calc(8px * var(--card-scale)) color-mix(in oklch, #67c23a 40%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-card__status-dot--join {
|
.friend-card__status-dot--join {
|
||||||
background: linear-gradient(145deg, #409eff, #2f7ed9);
|
background: #409eff;
|
||||||
box-shadow: 0 0 calc(8px * var(--card-scale)) rgba(64, 158, 255, 0.4);
|
box-shadow: 0 0 calc(8px * var(--card-scale)) color-mix(in oklch, #409eff 40%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-card__status-dot--busy {
|
.friend-card__status-dot--busy {
|
||||||
background: linear-gradient(145deg, #ff2c2c, #d81f1f);
|
background: #ff2c2c;
|
||||||
box-shadow: 0 0 calc(8px * var(--card-scale)) rgba(255, 44, 44, 0.4);
|
box-shadow: 0 0 calc(8px * var(--card-scale)) color-mix(in oklch, #ff2c2c 40%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-card__status-dot--ask {
|
.friend-card__status-dot--ask {
|
||||||
background: linear-gradient(145deg, #ff9500, #d97800);
|
background: #ff9500;
|
||||||
box-shadow: 0 0 calc(8px * var(--card-scale)) rgba(255, 149, 0, 0.4);
|
box-shadow: 0 0 calc(8px * var(--card-scale)) color-mix(in oklch, #ff9500 40%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-card__body {
|
.friend-card__body {
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
.friend-card__name {
|
.friend-card__name {
|
||||||
font-size: calc(17px * var(--card-scale));
|
font-size: calc(17px * var(--card-scale));
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1f2937;
|
color: var(--el-text-color-primary);
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
.friend-card__signature {
|
.friend-card__signature {
|
||||||
margin-top: calc(6px * var(--card-spacing));
|
margin-top: calc(6px * var(--card-spacing));
|
||||||
font-size: calc(13px * var(--card-scale));
|
font-size: calc(13px * var(--card-scale));
|
||||||
color: rgba(31, 41, 55, 0.7);
|
color: var(--el-text-color-secondary);
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -194,12 +194,21 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: calc(40px * var(--card-scale));
|
min-height: calc(40px * var(--card-scale));
|
||||||
padding: calc(6px * var(--card-scale)) calc(10px * var(--card-scale));
|
padding: calc(6px * var(--card-scale)) calc(10px * var(--card-scale));
|
||||||
border-radius: calc(12px * var(--card-scale));
|
border-radius: calc(10px * var(--card-scale));
|
||||||
background: rgba(148, 163, 184, 0.18);
|
background: var(--el-fill-color);
|
||||||
color: rgba(71, 85, 105, 0.95);
|
color: var(--el-text-color-regular);
|
||||||
font-size: calc(12px * var(--card-scale));
|
font-size: calc(12px * var(--card-scale));
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark) .friend-card__world,
|
||||||
|
:global(:root.dark) .friend-card__world,
|
||||||
|
:global(:root[data-theme='dark']) .friend-card__world {
|
||||||
|
color: var(--color-zinc-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-card__location {
|
.friend-card__location {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="x-container">
|
<div class="x-container" ref="gameLogRef">
|
||||||
<div style="margin: 0 0 10px; display: flex; align-items: center">
|
<div style="margin: 0 0 10px; display: flex; align-items: center">
|
||||||
<div style="flex: none; margin-right: 10px; display: flex; align-items: center">
|
<div style="flex: none; margin-right: 10px; display: flex; align-items: center">
|
||||||
<NativeTooltip placement="bottom" :content="t('view.feed.favorites_only_tooltip')">
|
<el-tooltip placement="bottom" :content="t('view.feed.favorites_only_tooltip')">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="gameLogTable.vip"
|
v-model="gameLogTable.vip"
|
||||||
active-color="#13ce66"
|
active-color="var(--el-color-success)"
|
||||||
@change="gameLogTableLookup"></el-switch>
|
@change="gameLogTableLookup"></el-switch>
|
||||||
</NativeTooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="gameLogTable.filter"
|
v-model="gameLogTable.filter"
|
||||||
@@ -41,46 +41,47 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable v-bind="gameLogTable" :data="gameLogDisplayData">
|
<DataTable v-bind="gameLogTable" :data="gameLogDisplayData">
|
||||||
<el-table-column :label="t('table.gameLog.date')" prop="created_at" width="130">
|
<el-table-column :label="t('table.gameLog.date')" prop="created_at" width="140">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<NativeTooltip placement="right">
|
<el-tooltip placement="right">
|
||||||
<template #content>
|
<template #content>
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'short') }}</span>
|
<span>{{ formatDateFilter(scope.row.created_at, 'short') }}</span>
|
||||||
</NativeTooltip>
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column :label="t('table.gameLog.type')" prop="type" width="120">
|
<el-table-column :label="t('table.gameLog.type')" prop="type" width="150">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag
|
||||||
|
v-if="scope.row.location && scope.row.type !== 'Location'"
|
||||||
|
type="info"
|
||||||
|
effect="plain"
|
||||||
|
size="small">
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
@click="showWorldDialog(scope.row.location)"
|
||||||
|
v-text="t('view.game_log.filters.' + scope.row.type)"></span>
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-else type="info" effect="plain" size="small">
|
||||||
|
<span v-text="t('view.game_log.filters.' + scope.row.type)"></span>
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="t('table.gameLog.user')" prop="displayName" width="200">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span
|
<span
|
||||||
v-if="scope.row.location && scope.row.type !== 'Location'"
|
v-if="scope.row.displayName"
|
||||||
class="x-link"
|
class="x-link table-user"
|
||||||
@click="showWorldDialog(scope.row.location)"
|
style="padding-right: 10px"
|
||||||
v-text="t('view.game_log.filters.' + scope.row.type)"></span>
|
@click="lookupUser(scope.row)"
|
||||||
<span v-else v-text="t('view.game_log.filters.' + scope.row.type)"></span>
|
v-text="scope.row.displayName"></span>
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column :label="t('table.gameLog.icon')" prop="isFriend" width="70" align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<template v-if="gameLogIsFriend(scope.row)">
|
<template v-if="gameLogIsFriend(scope.row)">
|
||||||
<span v-if="gameLogIsFavorite(scope.row)">⭐</span>
|
<span v-if="gameLogIsFavorite(scope.row)">⭐</span>
|
||||||
<span v-else>💚</span>
|
<span v-else>💚</span>
|
||||||
</template>
|
</template>
|
||||||
<span v-else></span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column :label="t('table.gameLog.user')" prop="displayName" width="180">
|
|
||||||
<template #default="scope">
|
|
||||||
<span
|
|
||||||
v-if="scope.row.displayName"
|
|
||||||
class="x-link"
|
|
||||||
style="padding-right: 10px"
|
|
||||||
@click="lookupUser(scope.row)"
|
|
||||||
v-text="scope.row.displayName"></span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
@@ -158,31 +159,38 @@
|
|||||||
size="small"
|
size="small"
|
||||||
class="small-button"
|
class="small-button"
|
||||||
@click="deleteGameLogEntry(scope.row)"></el-button>
|
@click="deleteGameLogEntry(scope.row)"></el-button>
|
||||||
<el-button
|
<i
|
||||||
|
class="ri-delete-bin-line small-button"
|
||||||
|
style="opacity: 0.85"
|
||||||
v-else
|
v-else
|
||||||
text
|
@click="deleteGameLogEntryPrompt(scope.row)"></i>
|
||||||
:icon="Delete"
|
|
||||||
size="small"
|
|
||||||
class="small-button"
|
|
||||||
@click="deleteGameLogEntryPrompt(scope.row)"></el-button>
|
|
||||||
</template>
|
</template>
|
||||||
<NativeTooltip placement="top" :content="t('dialog.previous_instances.info')">
|
<el-tooltip
|
||||||
|
v-if="scope.row.type === 'Location'"
|
||||||
|
placement="top"
|
||||||
|
:content="t('dialog.previous_instances.info')">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="scope.row.type === 'Location'"
|
v-if="shiftHeld"
|
||||||
text
|
text
|
||||||
:icon="DataLine"
|
:icon="DataLine"
|
||||||
size="small"
|
size="small"
|
||||||
class="small-button"
|
class="small-button"
|
||||||
@click="showPreviousInstancesInfoDialog(scope.row.location)"></el-button>
|
@click="showPreviousInstancesInfoDialog(scope.row.location)"></el-button>
|
||||||
</NativeTooltip>
|
<i
|
||||||
|
v-else
|
||||||
|
style="opacity: 0.85"
|
||||||
|
class="ri-file-list-2-line small-button"
|
||||||
|
@click="showPreviousInstancesInfoDialog(scope.row.location)"></i>
|
||||||
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column width="5"></el-table-column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Close, DataLine, Delete } from '@element-plus/icons-vue';
|
import { Close, DataLine } from '@element-plus/icons-vue';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
@@ -194,6 +202,7 @@
|
|||||||
import { formatDateFilter, openExternalLink, removeFromArray } from '../../shared/utils';
|
import { formatDateFilter, openExternalLink, removeFromArray } from '../../shared/utils';
|
||||||
import { database } from '../../service/database';
|
import { database } from '../../service/database';
|
||||||
import { useSharedFeedStore } from '../../stores';
|
import { useSharedFeedStore } from '../../stores';
|
||||||
|
import { useTableHeight } from '../../composables/useTableHeight';
|
||||||
|
|
||||||
const { showWorldDialog } = useWorldStore();
|
const { showWorldDialog } = useWorldStore();
|
||||||
const { lookupUser } = useUserStore();
|
const { lookupUser } = useUserStore();
|
||||||
@@ -252,6 +261,8 @@
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const emit = defineEmits(['updateGameLogSessionTable']);
|
const emit = defineEmits(['updateGameLogSessionTable']);
|
||||||
|
|
||||||
|
const { containerRef: gameLogRef } = useTableHeight(gameLogTable);
|
||||||
|
|
||||||
function deleteGameLogEntry(row) {
|
function deleteGameLogEntry(row) {
|
||||||
removeFromArray(gameLogTable.value.data, row);
|
removeFromArray(gameLogTable.value.data, row);
|
||||||
database.deleteGameLogEntry(row);
|
database.deleteGameLogEntry(row);
|
||||||
@@ -281,5 +292,9 @@
|
|||||||
.small-button {
|
.small-button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.table-user {
|
||||||
|
color: var(--x-table-user-text-color) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||