mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-04 22:06:06 +02:00
custom fonts
This commit is contained in:
Generated
+10
@@ -7,6 +7,7 @@
|
|||||||
"name": "VRCX",
|
"name": "VRCX",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource-variable/noto-sans": "^5.2.10",
|
||||||
"hazardous": "^0.3.0",
|
"hazardous": "^0.3.0",
|
||||||
"node-api-dotnet": "^0.9.18"
|
"node-api-dotnet": "^0.9.18"
|
||||||
},
|
},
|
||||||
@@ -2075,6 +2076,15 @@
|
|||||||
"url": "https://github.com/sponsors/ayuhito"
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fontsource-variable/noto-sans": {
|
||||||
|
"version": "5.2.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans/-/noto-sans-5.2.10.tgz",
|
||||||
|
"integrity": "sha512-wyFgKkFu7jki5kEL8qv7avjQ8rxHX0J/nhLWvbR9T0hOH1HRKZEvb9EW9lMjZfWHHfEzKkYf5J+NadwgCS7TXA==",
|
||||||
|
"license": "OFL-1.1",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@fontsource-variable/noto-sans-jp": {
|
"node_modules/@fontsource-variable/noto-sans-jp": {
|
||||||
"version": "5.2.10",
|
"version": "5.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans-jp/-/noto-sans-jp-5.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans-jp/-/noto-sans-jp-5.2.10.tgz",
|
||||||
|
|||||||
@@ -178,6 +178,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource-variable/noto-sans": "^5.2.10",
|
||||||
"hazardous": "^0.3.0",
|
"hazardous": "^0.3.0",
|
||||||
"node-api-dotnet": "^0.9.18"
|
"node-api-dotnet": "^0.9.18"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ html {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
overflow: hidden auto;
|
overflow: hidden auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
background: var(--sidebar);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div @click="confirm" class="cursor-pointer w-fit align-top flex items-center">
|
<div @click="confirm" class="cursor-pointer w-fit align-top flex items-center">
|
||||||
<span v-if="avatarType" :class="color" class="mr-1.5">
|
<span>{{ avatarName }}</span>
|
||||||
<Lock v-if="avatarType === '(own)'" class="h-4 w-4" />
|
<span v-if="avatarType === '(own)'" :class="color" class="mx-1">
|
||||||
<Unlock v-else-if="avatarType === '(public)'" class="h-4 w-4" />
|
<Lock v-if="avatarType" class="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
<span class="mr-2">{{ avatarName }}</span>
|
|
||||||
<span v-if="avatarTags" style="font-size: 12px">{{ avatarTags }}</span>
|
<span v-if="avatarTags" style="font-size: 12px">{{ avatarTags }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Lock, Unlock } from 'lucide-vue-next';
|
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
import { Lock } from 'lucide-vue-next';
|
||||||
|
|
||||||
import { useAvatarStore } from '../stores';
|
import { useAvatarStore } from '../stores';
|
||||||
|
|
||||||
|
|||||||
@@ -132,9 +132,10 @@
|
|||||||
<div class="flex min-w-0 flex-col">
|
<div class="flex min-w-0 flex-col">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="text-left text-sm font-medium truncate"
|
class="text-left text-sm font-medium truncate flex items-center gap-1"
|
||||||
@click="openGithub">
|
@click="openGithub">
|
||||||
VRCX
|
VRCX
|
||||||
|
<Heart class="text-primary fill-current stroke-none" />
|
||||||
</button>
|
</button>
|
||||||
<span class="text-xs text-muted-foreground">{{ version }}</span>
|
<span class="text-xs text-muted-foreground">{{ version }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -288,7 +289,7 @@
|
|||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
|
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
import { ChevronRight } from 'lucide-vue-next';
|
import { ChevronRight, Heart } from 'lucide-vue-next';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|||||||
@@ -82,12 +82,22 @@
|
|||||||
><Apple class="h-4 w-4 text-[#8e8e93]" />
|
><Apple class="h-4 w-4 text-[#8e8e93]" />
|
||||||
<span
|
<span
|
||||||
v-if="avatarDialog.platformInfo.ios"
|
v-if="avatarDialog.platformInfo.ios"
|
||||||
:class="['x-grey', 'x-tag-border-left', 'text-[#8e8e93]', 'border-[#8e8e93]']"
|
:class="[
|
||||||
|
'x-grey',
|
||||||
|
'x-tag-border-left',
|
||||||
|
'text-[#8e8e93]',
|
||||||
|
'border-[#8e8e93]'
|
||||||
|
]"
|
||||||
>{{ avatarDialog.platformInfo.ios.performanceRating }}</span
|
>{{ avatarDialog.platformInfo.ios.performanceRating }}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="avatarDialog.bundleSizes['ios']"
|
v-if="avatarDialog.bundleSizes['ios']"
|
||||||
:class="['x-grey', 'x-tag-border-left', 'text-[#8e8e93]', 'border-[#8e8e93]']"
|
:class="[
|
||||||
|
'x-grey',
|
||||||
|
'x-tag-border-left',
|
||||||
|
'text-[#8e8e93]',
|
||||||
|
'border-[#8e8e93]'
|
||||||
|
]"
|
||||||
>{{ avatarDialog.bundleSizes['ios'].fileSize }}</span
|
>{{ avatarDialog.bundleSizes['ios'].fileSize }}</span
|
||||||
>
|
>
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -491,14 +501,14 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #JSON>
|
<template #JSON>
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full h-6 w-6 mr-2"
|
class="rounded-full mr-2"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="refreshAvatarDialogTreeData()">
|
@click="refreshAvatarDialogTreeData()">
|
||||||
<RefreshCw />
|
<RefreshCw />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full h-6 w-6"
|
class="rounded-full"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="downloadAndSaveJson(avatarDialog.id, avatarDialog.ref)">
|
@click="downloadAndSaveJson(avatarDialog.id, avatarDialog.ref)">
|
||||||
|
|||||||
@@ -1139,14 +1139,14 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #JSON>
|
<template #JSON>
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full h-6 w-6 mr-2"
|
class="rounded-full mr-2"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="refreshGroupDialogTreeData()">
|
@click="refreshGroupDialogTreeData()">
|
||||||
<RefreshCw />
|
<RefreshCw />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full h-6 w-6"
|
class="rounded-full"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="downloadAndSaveJson(groupDialog.id, groupDialog.ref)">
|
@click="downloadAndSaveJson(groupDialog.id, groupDialog.ref)">
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model:open="groupMemberModeration.visible">
|
<Dialog v-model:open="groupMemberModeration.visible">
|
||||||
<DialogContent class="x-dialog max-w-none w-[90vw]">
|
<DialogContent class="x-dialog max-w-none sm:min-w-[90vw] sm:max-w-[90vw] sm:min-h-[90vh] sm:max-h-[90vh]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{{ t('dialog.group_member_moderation.header') }}</DialogTitle>
|
<DialogTitle>{{ t('dialog.group_member_moderation.header') }}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ groupMemberModeration.groupRef.name }}</h3>
|
<h3>{{ groupMemberModeration.groupRef.name }}</h3>
|
||||||
<TabsUnderline
|
<TabsUnderline default-value="members" :items="groupModerationTabs" :unmount-on-hide="false">
|
||||||
default-value="members"
|
|
||||||
:items="groupModerationTabs"
|
|
||||||
:unmount-on-hide="false"
|
|
||||||
style="height: 100%">
|
|
||||||
<template #members>
|
<template #members>
|
||||||
<div style="margin-top: 10px">
|
<div style="margin-top: 10px">
|
||||||
<Button
|
<Button
|
||||||
@@ -54,10 +50,8 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
@click.stop>
|
@click.stop>
|
||||||
<span>
|
{{ t(memberSortOrder.name) }}
|
||||||
{{ t(memberSortOrder.name) }}
|
<ArrowDown style="margin-left: 5px" />
|
||||||
<ArrowDown style="margin-left: 5px" />
|
|
||||||
</span>
|
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
@@ -96,10 +90,8 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
@click.stop>
|
@click.stop>
|
||||||
<span>
|
{{ t(memberFilter.name) }}
|
||||||
{{ t(memberFilter.name) }}
|
<ArrowDown style="margin-left: 5px" />
|
||||||
<ArrowDown style="margin-left: 5px" />
|
|
||||||
</span>
|
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
|
|||||||
@@ -193,9 +193,7 @@
|
|||||||
(userDialog.representedGroup && userDialog.representedGroup.isRepresenting)
|
(userDialog.representedGroup && userDialog.representedGroup.isRepresenting)
|
||||||
"
|
"
|
||||||
class="extra">
|
class="extra">
|
||||||
<div
|
<div style="display: inline-block; flex: none; margin-right: 5px">
|
||||||
|
|
||||||
style="display: inline-block; flex: none; margin-right: 5px">
|
|
||||||
<Avatar
|
<Avatar
|
||||||
class="x-link size-15! rounded-lg!"
|
class="x-link size-15! rounded-lg!"
|
||||||
:style="{
|
:style="{
|
||||||
@@ -1055,10 +1053,7 @@
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="x-friend-list" style="margin-top: 10px; min-height: 60px">
|
||||||
|
|
||||||
class="x-friend-list"
|
|
||||||
style="margin-top: 10px; min-height: 60px">
|
|
||||||
<div
|
<div
|
||||||
v-for="world in userDialog.worlds"
|
v-for="world in userDialog.worlds"
|
||||||
:key="world.id"
|
:key="world.id"
|
||||||
@@ -1092,7 +1087,6 @@
|
|||||||
v-model="favoriteWorldsTab"
|
v-model="favoriteWorldsTab"
|
||||||
:items="favoriteWorldTabs"
|
:items="favoriteWorldTabs"
|
||||||
:unmount-on-hide="false"
|
:unmount-on-hide="false"
|
||||||
|
|
||||||
class="zero-margin-tabs"
|
class="zero-margin-tabs"
|
||||||
style="margin-top: 10px; height: 50vh">
|
style="margin-top: 10px; height: 50vh">
|
||||||
<template
|
<template
|
||||||
@@ -1257,14 +1251,14 @@
|
|||||||
|
|
||||||
<template #JSON>
|
<template #JSON>
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full h-6 w-6 mr-2"
|
class="rounded-full mr-2"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="refreshUserDialogTreeData()">
|
@click="refreshUserDialogTreeData()">
|
||||||
<RefreshCw />
|
<RefreshCw />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full h-6 w-6"
|
class="rounded-full"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="downloadAndSaveJson(userDialog.id, userDialog.ref)">
|
@click="downloadAndSaveJson(userDialog.id, userDialog.ref)">
|
||||||
|
|||||||
@@ -93,7 +93,12 @@
|
|||||||
<Apple class="h-4 w-4 text-[#8e8e93]" />
|
<Apple class="h-4 w-4 text-[#8e8e93]" />
|
||||||
><span
|
><span
|
||||||
v-if="worldDialog.bundleSizes['ios']"
|
v-if="worldDialog.bundleSizes['ios']"
|
||||||
:class="['x-grey', 'x-tag-border-left', 'text-[#8e8e93]', 'border-[#8e8e93]']">
|
:class="[
|
||||||
|
'x-grey',
|
||||||
|
'x-tag-border-left',
|
||||||
|
'text-[#8e8e93]',
|
||||||
|
'border-[#8e8e93]'
|
||||||
|
]">
|
||||||
{{ worldDialog.bundleSizes['ios'].fileSize }}
|
{{ worldDialog.bundleSizes['ios'].fileSize }}
|
||||||
</span>
|
</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -680,14 +685,14 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #JSON>
|
<template #JSON>
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full h-6 w-6 mr-2"
|
class="rounded-full mr-2"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="refreshWorldDialogTreeData()">
|
@click="refreshWorldDialogTreeData()">
|
||||||
<RefreshCw />
|
<RefreshCw />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full h-6 w-6"
|
class="rounded-full"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="downloadAndSaveJson(worldDialog.id, worldDialog.ref)">
|
@click="downloadAndSaveJson(worldDialog.id, worldDialog.ref)">
|
||||||
|
|||||||
@@ -65,7 +65,8 @@
|
|||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||||
props.class
|
props.class,
|
||||||
|
'max-h-[85vh] overflow-y-auto scrollbar-hidden'
|
||||||
)
|
)
|
||||||
">
|
">
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -552,6 +552,10 @@
|
|||||||
"language": "Language",
|
"language": "Language",
|
||||||
"bio_language": "Target Language",
|
"bio_language": "Target Language",
|
||||||
"theme_mode": "Theme",
|
"theme_mode": "Theme",
|
||||||
|
"font_family": "Font",
|
||||||
|
"font_family_inter": "Inter",
|
||||||
|
"font_family_noto_sans": "Noto Sans",
|
||||||
|
"font_family_harmonyos_sans": "HarmonyOS Sans",
|
||||||
"theme_mode_system": "System",
|
"theme_mode_system": "System",
|
||||||
"theme_mode_light": "Light",
|
"theme_mode_light": "Light",
|
||||||
"theme_mode_dark": "Dark",
|
"theme_mode_dark": "Dark",
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const APP_FONT_DEFAULT_KEY = 'noto_sans';
|
||||||
|
|
||||||
|
const APP_FONT_CONFIG = Object.freeze({
|
||||||
|
inter: {
|
||||||
|
cssName: "'Inter'",
|
||||||
|
link: null
|
||||||
|
},
|
||||||
|
noto_sans: {
|
||||||
|
cssName: "'Noto Sans Variable'",
|
||||||
|
link: null
|
||||||
|
},
|
||||||
|
harmonyos_sans: {
|
||||||
|
cssName: "'HarmonyOS Sans'",
|
||||||
|
cssImport:
|
||||||
|
"@import url('https://fonts.cdnfonts.com/css/harmonyos-sans');"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const APP_FONT_FAMILIES = Object.freeze(Object.keys(APP_FONT_CONFIG));
|
||||||
|
|
||||||
|
export { APP_FONT_CONFIG, APP_FONT_DEFAULT_KEY, APP_FONT_FAMILIES };
|
||||||
@@ -8,6 +8,7 @@ export * from './instance';
|
|||||||
export * from './world';
|
export * from './world';
|
||||||
export * from './moderation';
|
export * from './moderation';
|
||||||
export * from './themes';
|
export * from './themes';
|
||||||
|
export * from './fonts';
|
||||||
export * from './link';
|
export * from './link';
|
||||||
export * from './ui';
|
export * from './ui';
|
||||||
export * from './accessType';
|
export * from './accessType';
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import { ref } from 'vue';
|
|||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
|
|
||||||
import { THEME_COLORS, THEME_CONFIG } from '../../constants';
|
import {
|
||||||
|
APP_FONT_CONFIG,
|
||||||
|
APP_FONT_DEFAULT_KEY,
|
||||||
|
THEME_COLORS,
|
||||||
|
THEME_CONFIG
|
||||||
|
} from '../../constants';
|
||||||
import { i18n } from '../../../plugin/i18n';
|
import { i18n } from '../../../plugin/i18n';
|
||||||
import { router } from '../../../plugin/router';
|
import { router } from '../../../plugin/router';
|
||||||
import { textToHex } from './string';
|
import { textToHex } from './string';
|
||||||
@@ -14,6 +19,8 @@ const THEME_COLOR_STORAGE_KEY = 'VRCX_themeColor';
|
|||||||
const THEME_COLOR_STYLE_ID = 'app-theme-color-style';
|
const THEME_COLOR_STYLE_ID = 'app-theme-color-style';
|
||||||
const DEFAULT_THEME_COLOR_KEY = 'default';
|
const DEFAULT_THEME_COLOR_KEY = 'default';
|
||||||
|
|
||||||
|
const APP_FONT_LINK_ATTR = 'data-app-font';
|
||||||
|
|
||||||
const themeColors = THEME_COLORS.map((theme) => ({
|
const themeColors = THEME_COLORS.map((theme) => ({
|
||||||
...theme,
|
...theme,
|
||||||
href: theme.file
|
href: theme.file
|
||||||
@@ -132,6 +139,51 @@ function applyThemeFonts(themeKey, fontLinks = []) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveAppFontFamily(fontKey) {
|
||||||
|
const normalized = String(fontKey || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
if (APP_FONT_CONFIG[normalized]) {
|
||||||
|
return { key: normalized, ...APP_FONT_CONFIG[normalized] };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: APP_FONT_DEFAULT_KEY,
|
||||||
|
...APP_FONT_CONFIG[APP_FONT_DEFAULT_KEY]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAppFontLinks() {
|
||||||
|
const head = document.head;
|
||||||
|
if (!head) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.entries(APP_FONT_CONFIG).forEach(([key, config]) => {
|
||||||
|
if (!config?.cssImport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const existing = document.querySelector(
|
||||||
|
`style[${APP_FONT_LINK_ATTR}="${key}"]`
|
||||||
|
);
|
||||||
|
if (existing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const styleEl = document.createElement('style');
|
||||||
|
styleEl.setAttribute(APP_FONT_LINK_ATTR, key);
|
||||||
|
styleEl.textContent = config.cssImport;
|
||||||
|
head.appendChild(styleEl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAppFontFamily(fontKey) {
|
||||||
|
const resolved = resolveAppFontFamily(fontKey);
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.style.setProperty('--font-western-primary', resolved.cssName);
|
||||||
|
|
||||||
|
ensureAppFontLinks();
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
function changeAppThemeStyle(themeMode) {
|
function changeAppThemeStyle(themeMode) {
|
||||||
if (themeMode === 'system') {
|
if (themeMode === 'system') {
|
||||||
themeMode = systemIsDarkMode() ? 'dark' : 'light';
|
themeMode = systemIsDarkMode() ? 'dark' : 'light';
|
||||||
@@ -379,6 +431,7 @@ export {
|
|||||||
updateTrustColorClasses,
|
updateTrustColorClasses,
|
||||||
refreshCustomCss,
|
refreshCustomCss,
|
||||||
refreshCustomScript,
|
refreshCustomScript,
|
||||||
|
applyAppFontFamily,
|
||||||
HueToHex,
|
HueToHex,
|
||||||
HSVtoRGB,
|
HSVtoRGB,
|
||||||
formatJsonVars,
|
formatJsonVars,
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ import { useRouter } from 'vue-router';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
HueToHex,
|
HueToHex,
|
||||||
|
applyAppFontFamily,
|
||||||
changeAppThemeStyle,
|
changeAppThemeStyle,
|
||||||
changeHtmlLangAttribute,
|
changeHtmlLangAttribute,
|
||||||
getThemeMode,
|
getThemeMode,
|
||||||
updateTrustColorClasses
|
updateTrustColorClasses
|
||||||
} from '../../shared/utils/base/ui';
|
} from '../../shared/utils/base/ui';
|
||||||
|
import {
|
||||||
|
APP_FONT_DEFAULT_KEY,
|
||||||
|
APP_FONT_FAMILIES
|
||||||
|
} 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 { languageCodes } from '../../localization';
|
||||||
@@ -46,6 +51,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
const appLanguage = ref('en');
|
const appLanguage = ref('en');
|
||||||
const themeMode = ref('');
|
const themeMode = ref('');
|
||||||
const isDarkMode = ref(false);
|
const isDarkMode = ref(false);
|
||||||
|
const appFontFamily = ref('inter');
|
||||||
const displayVRCPlusIconsAsAvatar = ref(false);
|
const displayVRCPlusIconsAsAvatar = ref(false);
|
||||||
const hideNicknames = ref(false);
|
const hideNicknames = ref(false);
|
||||||
const showInstanceIdInLocation = ref(false);
|
const showInstanceIdInLocation = ref(false);
|
||||||
@@ -130,7 +136,8 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
compactTableModeConfig,
|
compactTableModeConfig,
|
||||||
trustColorConfig,
|
trustColorConfig,
|
||||||
notificationIconDotConfig,
|
notificationIconDotConfig,
|
||||||
navIsCollapsedConfig
|
navIsCollapsedConfig,
|
||||||
|
appFontFamilyConfig
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
configRepository.getString('VRCX_appLanguage'),
|
configRepository.getString('VRCX_appLanguage'),
|
||||||
configRepository.getBool('displayVRCPlusIconsAsAvatar', true),
|
configRepository.getBool('displayVRCPlusIconsAsAvatar', true),
|
||||||
@@ -185,7 +192,11 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
JSON.stringify(TRUST_COLOR_DEFAULTS)
|
JSON.stringify(TRUST_COLOR_DEFAULTS)
|
||||||
),
|
),
|
||||||
configRepository.getBool('VRCX_notificationIconDot', true),
|
configRepository.getBool('VRCX_notificationIconDot', true),
|
||||||
configRepository.getBool('VRCX_navIsCollapsed', true)
|
configRepository.getBool('VRCX_navIsCollapsed', true),
|
||||||
|
configRepository.getString(
|
||||||
|
'VRCX_fontFamily',
|
||||||
|
APP_FONT_DEFAULT_KEY
|
||||||
|
)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!appLanguageConfig) {
|
if (!appLanguageConfig) {
|
||||||
@@ -205,6 +216,8 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
|
|
||||||
themeMode.value = initThemeMode;
|
themeMode.value = initThemeMode;
|
||||||
isDarkMode.value = initDarkMode;
|
isDarkMode.value = initDarkMode;
|
||||||
|
appFontFamily.value = normalizeAppFontFamily(appFontFamilyConfig);
|
||||||
|
applyAppFontFamily(appFontFamily.value);
|
||||||
|
|
||||||
displayVRCPlusIconsAsAvatar.value =
|
displayVRCPlusIconsAsAvatar.value =
|
||||||
displayVRCPlusIconsAsAvatarConfig;
|
displayVRCPlusIconsAsAvatarConfig;
|
||||||
@@ -456,6 +469,19 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
updateTrustColor(undefined, undefined);
|
updateTrustColor(undefined, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeAppFontFamily(value) {
|
||||||
|
return APP_FONT_FAMILIES.includes(value)
|
||||||
|
? value
|
||||||
|
: APP_FONT_DEFAULT_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAppFontFamily(value) {
|
||||||
|
const normalized = normalizeAppFontFamily(value);
|
||||||
|
appFontFamily.value = normalized;
|
||||||
|
configRepository.setString('VRCX_fontFamily', normalized);
|
||||||
|
applyAppFontFamily(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
function setDisplayVRCPlusIconsAsAvatar() {
|
function setDisplayVRCPlusIconsAsAvatar() {
|
||||||
displayVRCPlusIconsAsAvatar.value =
|
displayVRCPlusIconsAsAvatar.value =
|
||||||
!displayVRCPlusIconsAsAvatar.value;
|
!displayVRCPlusIconsAsAvatar.value;
|
||||||
@@ -820,6 +846,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
appLanguage,
|
appLanguage,
|
||||||
themeMode,
|
themeMode,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
|
appFontFamily,
|
||||||
displayVRCPlusIconsAsAvatar,
|
displayVRCPlusIconsAsAvatar,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
showInstanceIdInLocation,
|
showInstanceIdInLocation,
|
||||||
@@ -886,6 +913,7 @@ export const useAppearanceSettingsStore = defineStore(
|
|||||||
applyTableDensity,
|
applyTableDensity,
|
||||||
setNavCollapsed,
|
setNavCollapsed,
|
||||||
toggleNavCollapsed,
|
toggleNavCollapsed,
|
||||||
|
setAppFontFamily,
|
||||||
setThemeMode
|
setThemeMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import '@fontsource-variable/inter';
|
@import '@fontsource-variable/inter';
|
||||||
|
@import '@fontsource-variable/noto-sans';
|
||||||
|
|
||||||
@import '@fontsource-variable/noto-sans-jp';
|
@import '@fontsource-variable/noto-sans-jp';
|
||||||
@import '@fontsource-variable/noto-sans-kr';
|
@import '@fontsource-variable/noto-sans-kr';
|
||||||
|
|||||||
+16
-2
@@ -48,9 +48,10 @@
|
|||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
--font-western-primary: 'Inter';
|
||||||
--font-western:
|
--font-western:
|
||||||
'ellipsis-font', -apple-system, 'Inter', 'Segoe UI', 'Roboto', 'Ubuntu',
|
'ellipsis-font', -apple-system, var(--font-western-primary), 'Segoe UI',
|
||||||
'Cantarell', 'DejaVu Sans', sans-serif;
|
'Roboto', 'Ubuntu', 'Cantarell', 'DejaVu Sans', sans-serif;
|
||||||
--font-symbol: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
--font-symbol: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
--font-fallback-cjk: sans-serif;
|
--font-fallback-cjk: sans-serif;
|
||||||
--font-primary-cjk:
|
--font-primary-cjk:
|
||||||
@@ -171,6 +172,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.scrollbar-hidden {
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-hidden::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[data-slot='table-header'],
|
[data-slot='table-header'],
|
||||||
[data-slot='table-header'] [data-slot='table-row'],
|
[data-slot='table-header'] [data-slot='table-row'],
|
||||||
[data-slot='table-head'] {
|
[data-slot='table-head'] {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/* Noty.js */
|
|
||||||
|
|
||||||
.noty_layout {
|
.noty_layout {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="favorites-search-card__detail">
|
<div class="favorites-search-card__detail">
|
||||||
<div class="favorites-search-card__title">
|
<div class="favorites-search-card__title">
|
||||||
<span class="name">{{ localFavFakeRef.name }}</span>
|
<span class="name text-sm">{{ localFavFakeRef.name }}</span>
|
||||||
<span class="favorites-search-card__badges">
|
<span class="favorites-search-card__badges">
|
||||||
<TooltipWrapper
|
<TooltipWrapper
|
||||||
v-if="favorite.deleted"
|
v-if="favorite.deleted"
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs">{{ localFavFakeRef.authorName }}</span>
|
<span class="text-xs text-gray-600">{{ localFavFakeRef.authorName }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="favorites-search-card__actions">
|
<div class="favorites-search-card__actions">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
:traveling="favorite.ref.travelingToLocation"
|
:traveling="favorite.ref.travelingToLocation"
|
||||||
:link="false" />
|
:link="false" />
|
||||||
</div>
|
</div>
|
||||||
<span v-else class="text-xs">{{ favorite.ref.statusDescription }}</span>
|
<span v-else class="text-xs text-gray-600">{{ favorite.ref.statusDescription }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="favorites-search-card__actions">
|
<div class="favorites-search-card__actions">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="favorites-search-card__detail" v-once>
|
<div class="favorites-search-card__detail" v-once>
|
||||||
<div class="favorites-search-card__title">
|
<div class="favorites-search-card__title">
|
||||||
<span class="name">{{ props.favorite.ref.name }}</span>
|
<span class="name text-sm">{{ props.favorite.ref.name }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="favorite.deleted || favorite.ref.releaseStatus === 'private'"
|
v-if="favorite.deleted || favorite.ref.releaseStatus === 'private'"
|
||||||
class="favorites-search-card__badges">
|
class="favorites-search-card__badges">
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
class="h-4 w-4" />
|
class="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs">
|
<span class="text-xs text-gray-600">
|
||||||
{{ props.favorite.ref.authorName }}
|
{{ props.favorite.ref.authorName }}
|
||||||
<template v-if="props.favorite.ref.occupants"> ({{ props.favorite.ref.occupants }}) </template>
|
<template v-if="props.favorite.ref.occupants"> ({{ props.favorite.ref.occupants }}) </template>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'created_at',
|
accessorKey: 'created_at',
|
||||||
size: 100,
|
size: 120,
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -65,7 +65,7 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
{
|
{
|
||||||
accessorKey: 'type',
|
accessorKey: 'type',
|
||||||
|
|
||||||
size: 120,
|
size: 160,
|
||||||
header: () => t('table.friendLog.type'),
|
header: () => t('table.friendLog.type'),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const type = row.getValue('type');
|
const type = row.getValue('type');
|
||||||
|
|||||||
@@ -60,40 +60,29 @@
|
|||||||
</div>
|
</div>
|
||||||
<ScrollArea v-if="settingsReady" ref="scrollbarRef" class="friend-view__scroll">
|
<ScrollArea v-if="settingsReady" ref="scrollbarRef" class="friend-view__scroll">
|
||||||
<div v-if="virtualRows.length" class="friend-view__virtual" :style="virtualListStyle">
|
<div v-if="virtualRows.length" class="friend-view__virtual" :style="virtualListStyle">
|
||||||
<div class="friend-view__virtual-spacer" :style="virtualSpacerStyle">
|
<div v-for="row in virtualRows" :key="String(row.key)" class="friend-view__virtual-row">
|
||||||
<div
|
<template v-if="row.type === 'header'">
|
||||||
v-for="vRow in virtualItems"
|
<header class="friend-view__instance-header">
|
||||||
:key="String(virtualRows[vRow.index]?.key ?? vRow.key)"
|
<Location class="text-xs" :location="row.instanceId" style="display: inline" />
|
||||||
class="friend-view__virtual-row"
|
<span class="friend-view__instance-count">{{ row.count }}</span>
|
||||||
:data-index="vRow.index"
|
</header>
|
||||||
:ref="(el) => onVirtualRowRef(el)"
|
</template>
|
||||||
:style="virtualRowStyle(vRow.start)">
|
|
||||||
<template v-if="virtualRows[vRow.index]?.type === 'header'">
|
|
||||||
<header class="friend-view__instance-header">
|
|
||||||
<Location
|
|
||||||
class="text-xs"
|
|
||||||
:location="virtualRows[vRow.index].instanceId"
|
|
||||||
style="display: inline" />
|
|
||||||
<span class="friend-view__instance-count">{{ virtualRows[vRow.index].count }}</span>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else-if="virtualRows[vRow.index]?.type === 'divider'">
|
<template v-else-if="row.type === 'divider'">
|
||||||
<div class="friend-view__divider"><span class="friend-view__divider-text"></span></div>
|
<div class="friend-view__divider"><span class="friend-view__divider-text"></span></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="friend-view__row">
|
<div class="friend-view__row">
|
||||||
<FriendLocationCard
|
<FriendLocationCard
|
||||||
v-for="item in virtualRows[vRow.index]?.items ?? []"
|
v-for="item in row.items ?? []"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:friend="item.friend"
|
:friend="item.friend"
|
||||||
:card-scale="cardScale"
|
:card-scale="cardScale"
|
||||||
:card-spacing="cardSpacing"
|
:card-spacing="cardSpacing"
|
||||||
:display-instance-info="item.displayInstanceInfo" />
|
:display-instance-info="item.displayInstanceInfo" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="friend-view__empty">{{ t('view.friends_locations.no_matching_friends') }}</div>
|
<div v-else class="friend-view__empty">{{ t('view.friends_locations.no_matching_friends') }}</div>
|
||||||
@@ -113,7 +102,6 @@
|
|||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useVirtualizer } from '@tanstack/vue-virtual';
|
|
||||||
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
|
||||||
import { Slider } from '../../components/ui/slider';
|
import { Slider } from '../../components/ui/slider';
|
||||||
@@ -598,46 +586,6 @@
|
|||||||
return rows;
|
return rows;
|
||||||
});
|
});
|
||||||
|
|
||||||
const estimatedRowHeight = computed(() => {
|
|
||||||
const base = 148;
|
|
||||||
return Math.max(64, Math.round(base * cardScale.value * cardSpacing.value));
|
|
||||||
});
|
|
||||||
|
|
||||||
const virtualizerRef = useVirtualizer(
|
|
||||||
computed(() => ({
|
|
||||||
count: virtualRows.value.length,
|
|
||||||
getScrollElement: () => scrollViewportRef.value,
|
|
||||||
estimateSize: (index) => {
|
|
||||||
const row = virtualRows.value[index];
|
|
||||||
if (row?.type === 'header') return 34;
|
|
||||||
if (row?.type === 'divider') return 18;
|
|
||||||
return estimatedRowHeight.value;
|
|
||||||
},
|
|
||||||
overscan: 10
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const virtualizer = computed(() => virtualizerRef.value);
|
|
||||||
const virtualItems = computed(() => virtualizer.value?.getVirtualItems?.() ?? []);
|
|
||||||
|
|
||||||
const virtualSpacerStyle = computed(() => {
|
|
||||||
const height = `${virtualizer.value?.getTotalSize?.() ?? 0}px`;
|
|
||||||
return `height:${height};position:relative;width:100%;`;
|
|
||||||
});
|
|
||||||
|
|
||||||
function virtualRowStyle(start) {
|
|
||||||
const y = Number(start) || 0;
|
|
||||||
return `transform:translateY(${y}px);position:absolute;top:0;left:0;width:100%;`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVirtualRowRef(el) {
|
|
||||||
const target = el?.$el ?? el;
|
|
||||||
if (!target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
virtualizer.value?.measureElement?.(/** @type {Element} */ (target));
|
|
||||||
}
|
|
||||||
|
|
||||||
const virtualListStyle = computed(() => {
|
const virtualListStyle = computed(() => {
|
||||||
const styleFn = gridStyle.value;
|
const styleFn = gridStyle.value;
|
||||||
const total = filteredFriends.value.length;
|
const total = filteredFriends.value.length;
|
||||||
@@ -650,11 +598,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
watch([searchTerm, activeSegment], () => {
|
watch([searchTerm, activeSegment], () => {
|
||||||
virtualizer.value?.scrollToOffset?.(0);
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
resolveScrollViewport();
|
resolveScrollViewport();
|
||||||
updateGridWidth();
|
updateGridWidth();
|
||||||
virtualizer.value?.measure?.();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -666,11 +612,9 @@
|
|||||||
activeSegment.value = 'online';
|
activeSegment.value = 'online';
|
||||||
}
|
}
|
||||||
|
|
||||||
virtualizer.value?.scrollToOffset?.(0);
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
resolveScrollViewport();
|
resolveScrollViewport();
|
||||||
updateGridWidth();
|
updateGridWidth();
|
||||||
virtualizer.value?.measure?.();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -680,7 +624,6 @@
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
resolveScrollViewport();
|
resolveScrollViewport();
|
||||||
updateGridWidth();
|
updateGridWidth();
|
||||||
virtualizer.value?.measure?.();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -691,7 +634,6 @@
|
|||||||
}
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
updateGridWidth();
|
updateGridWidth();
|
||||||
virtualizer.value?.measure?.();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -700,7 +642,6 @@
|
|||||||
resolveScrollViewport();
|
resolveScrollViewport();
|
||||||
setupResizeHandling();
|
setupResizeHandling();
|
||||||
updateGridWidth();
|
updateGridWidth();
|
||||||
virtualizer.value?.measure?.();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -740,7 +681,6 @@
|
|||||||
resolveScrollViewport();
|
resolveScrollViewport();
|
||||||
setupResizeHandling();
|
setupResizeHandling();
|
||||||
updateGridWidth();
|
updateGridWidth();
|
||||||
virtualizer.value?.measure?.();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -791,6 +731,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
display: grid;
|
||||||
|
row-gap: calc(var(--friend-card-gap) - 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__virtual-spacer {
|
.friend-view__virtual-spacer {
|
||||||
@@ -803,10 +745,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.friend-view__row {
|
.friend-view__row {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: nowrap;
|
grid-template-columns: repeat(
|
||||||
|
var(--friend-grid-columns, 1),
|
||||||
|
minmax(var(--friend-card-min-width, 200px), var(--friend-card-target-width, 1fr))
|
||||||
|
);
|
||||||
gap: var(--friend-card-gap, 14px);
|
gap: var(--friend-card-gap, 14px);
|
||||||
align-items: stretch;
|
justify-content: start;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,8 +98,10 @@
|
|||||||
transition:
|
transition:
|
||||||
box-shadow 0.2s ease,
|
box-shadow 0.2s ease,
|
||||||
transform 0.2s ease;
|
transform 0.2s ease;
|
||||||
|
width: 100%;
|
||||||
max-width: var(--friend-card-target-width, 220px);
|
max-width: var(--friend-card-target-width, 220px);
|
||||||
min-width: var(--friend-card-min-width, 220px);
|
min-width: var(--friend-card-min-width, 220px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(calc(-2px * var(--card-scale)));
|
transform: translateY(calc(-2px * var(--card-scale)));
|
||||||
@@ -118,9 +120,6 @@
|
|||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.friend-card__avatar {
|
|
||||||
}
|
|
||||||
|
|
||||||
.friend-card__status-dot {
|
.friend-card__status-dot {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(8px * var(--card-scale));
|
top: calc(8px * var(--card-scale));
|
||||||
|
|||||||
@@ -175,8 +175,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
panelGroupRef,
|
|
||||||
asidePanelRef,
|
|
||||||
asideDefaultSize,
|
asideDefaultSize,
|
||||||
asideMaxSize,
|
asideMaxSize,
|
||||||
mainDefaultSize,
|
mainDefaultSize,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'created',
|
accessorKey: 'created',
|
||||||
size: 100,
|
size: 120,
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -65,7 +65,7 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'type',
|
accessorKey: 'type',
|
||||||
size: 100,
|
size: 140,
|
||||||
header: () => t('table.moderation.type'),
|
header: () => t('table.moderation.type'),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const type = row.getValue('type');
|
const type = row.getValue('type');
|
||||||
|
|||||||
@@ -32,6 +32,25 @@
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="options-container-item">
|
||||||
|
<span class="name">{{ t('view.settings.appearance.appearance.font_family') }}</span>
|
||||||
|
<Select :model-value="appFontFamily" @update:modelValue="setAppFontFamily">
|
||||||
|
<SelectTrigger size="sm">
|
||||||
|
<SelectValue
|
||||||
|
:placeholder="t(`view.settings.appearance.appearance.font_family_${appFontFamily}`)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem
|
||||||
|
v-for="fontKey in appFontFamilyOptions"
|
||||||
|
:key="fontKey"
|
||||||
|
:value="fontKey">
|
||||||
|
{{ t(`view.settings.appearance.appearance.font_family_${fontKey}`) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
<div v-if="!isLinux" class="options-container-item">
|
<div v-if="!isLinux" class="options-container-item">
|
||||||
<span class="name">{{ t('view.settings.appearance.appearance.zoom') }}</span>
|
<span class="name">{{ t('view.settings.appearance.appearance.zoom') }}</span>
|
||||||
<NumberField
|
<NumberField
|
||||||
@@ -431,7 +450,7 @@
|
|||||||
|
|
||||||
import { useAppearanceSettingsStore, useFavoriteStore, useVrStore } from '../../../../stores';
|
import { useAppearanceSettingsStore, useFavoriteStore, useVrStore } from '../../../../stores';
|
||||||
import { getLanguageName, languageCodes } from '../../../../localization';
|
import { getLanguageName, languageCodes } from '../../../../localization';
|
||||||
import { THEME_CONFIG } from '../../../../shared/constants';
|
import { APP_FONT_FAMILIES, THEME_CONFIG } from '../../../../shared/constants';
|
||||||
|
|
||||||
import SimpleSwitch from '../SimpleSwitch.vue';
|
import SimpleSwitch from '../SimpleSwitch.vue';
|
||||||
|
|
||||||
@@ -444,6 +463,7 @@
|
|||||||
appLanguage,
|
appLanguage,
|
||||||
themeMode,
|
themeMode,
|
||||||
displayVRCPlusIconsAsAvatar,
|
displayVRCPlusIconsAsAvatar,
|
||||||
|
appFontFamily,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
showInstanceIdInLocation,
|
showInstanceIdInLocation,
|
||||||
isAgeGatedInstancesVisible,
|
isAgeGatedInstancesVisible,
|
||||||
@@ -492,9 +512,12 @@
|
|||||||
changeAppLanguage,
|
changeAppLanguage,
|
||||||
promptMaxTableSizeDialog,
|
promptMaxTableSizeDialog,
|
||||||
setNotificationIconDot,
|
setNotificationIconDot,
|
||||||
setTablePageSizes
|
setTablePageSizes,
|
||||||
|
setAppFontFamily
|
||||||
} = appearanceSettingsStore;
|
} = appearanceSettingsStore;
|
||||||
|
|
||||||
|
const appFontFamilyOptions = APP_FONT_FAMILIES;
|
||||||
|
|
||||||
const zoomLevel = ref(100);
|
const zoomLevel = ref(100);
|
||||||
const isLinux = computed(() => LINUX);
|
const isLinux = computed(() => LINUX);
|
||||||
let cleanupWheel = null;
|
let cleanupWheel = null;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-if="friend.pendingOffline" class="text-xs">
|
<div v-if="friend.pendingOffline" class="text-xs">
|
||||||
<AlertTriangle class="inline-block" /> {{ t('side_panel.pending_offline') }}
|
{{ t('side_panel.pending_offline') }}
|
||||||
</div>
|
</div>
|
||||||
<template v-else-if="isGroupByInstance">
|
<template v-else-if="isGroupByInstance">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -65,9 +65,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { AlertTriangle, Loader2, Trash2 } from 'lucide-vue-next';
|
import { Loader2, Trash2 } from 'lucide-vue-next';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
|
||||||
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';
|
||||||
|
|||||||
Reference in New Issue
Block a user