This commit is contained in:
pa
2026-03-19 16:58:59 +09:00
parent cb7d6b78b3
commit 4eb781eaff
4 changed files with 172 additions and 100 deletions

View File

@@ -2,12 +2,7 @@
<div class="flex flex-col" style="min-height: 200px">
<div style="display: flex; align-items: center; justify-content: space-between">
<div style="display: flex; align-items: center">
<Button
class="rounded-full"
variant="ghost"
size="icon-sm"
:disabled="isLoading"
@click="loadData">
<Button class="rounded-full" variant="ghost" size="icon-sm" :disabled="isLoading" @click="loadData">
<Spinner v-if="isLoading" />
<RefreshCw v-else />
</Button>
@@ -44,15 +39,16 @@
<div v-if="!isLoading && !hasAnyData" class="flex items-center justify-center flex-1 mt-8">
<DataTableEmpty type="nodata" />
</div>
<div v-if="!isLoading && hasAnyData && filteredEventCount === 0" class="flex items-center justify-center flex-1 mt-8">
<div
v-if="!isLoading && hasAnyData && filteredEventCount === 0"
class="flex items-center justify-center flex-1 mt-8">
<span class="text-muted-foreground text-sm">{{ t('dialog.user.activity.no_data_in_period') }}</span>
</div>
<div
v-show="filteredEventCount > 0"
ref="chartRef"
style="width: 100%; height: 240px"
@contextmenu.prevent="onChartRightClick">
</div>
@contextmenu.prevent="onChartRightClick"></div>
<!-- Online Overlap Section -->
<div v-if="hasAnyData" class="mt-4 border-t border-border pt-3">
@@ -62,16 +58,12 @@
<Spinner v-if="isOverlapLoading" class="h-3.5 w-3.5" />
</div>
<div v-if="hasOverlapData" class="flex items-center gap-1.5 flex-shrink-0">
<Switch
:model-value="excludeHoursEnabled"
class="scale-75"
@update:model-value="onExcludeToggle" />
<span class="text-sm text-muted-foreground whitespace-nowrap">{{ t('dialog.user.activity.overlap.exclude_hours') }}</span>
<Switch :model-value="excludeHoursEnabled" class="scale-75" @update:model-value="onExcludeToggle" />
<span class="text-sm text-muted-foreground whitespace-nowrap">{{
t('dialog.user.activity.overlap.exclude_hours')
}}</span>
<Select v-model="excludeStartHour" @update:model-value="onExcludeRangeChange">
<SelectTrigger
size="sm"
class="w-[78px] h-6 text-xs px-2"
@click.stop>
<SelectTrigger size="sm" class="w-[78px] h-6 text-xs px-2" @click.stop>
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -82,10 +74,7 @@
</Select>
<span class="text-xs text-muted-foreground"></span>
<Select v-model="excludeEndHour" @update:model-value="onExcludeRangeChange">
<SelectTrigger
size="sm"
class="w-[78px] h-6 text-xs px-2"
@click.stop>
<SelectTrigger size="sm" class="w-[78px] h-6 text-xs px-2" @click.stop>
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -99,7 +88,9 @@
<div v-if="!isOverlapLoading && hasOverlapData" class="flex flex-col gap-1 mb-2">
<div class="flex items-center gap-2">
<span class="text-sm font-medium" :class="overlapPercent > 0 ? 'text-accent-foreground' : 'text-muted-foreground'">
<span
class="text-sm font-medium"
:class="overlapPercent > 0 ? 'text-accent-foreground' : 'text-muted-foreground'">
{{ overlapPercent }}%
</span>
<div class="flex-1 h-2 rounded-full bg-muted overflow-hidden">
@@ -207,7 +198,7 @@
dayLabels.value[4], // Thu
dayLabels.value[5], // Fri
dayLabels.value[6], // Sat
dayLabels.value[0] // Sun
dayLabels.value[0] // Sun
]);
const hourLabels = Array.from({ length: 24 }, (_, i) => `${String(i).padStart(2, '0')}:00`);
@@ -218,11 +209,7 @@
echartsInstance = null;
if (hasAnyData.value && chartRef.value) {
nextTick(() => {
echartsInstance = echarts.init(
chartRef.value,
isDarkMode.value ? 'dark' : null,
{ height: 240 }
);
echartsInstance = echarts.init(chartRef.value, isDarkMode.value ? 'dark' : null, { height: 240 });
initChart();
});
}
@@ -232,11 +219,9 @@
overlapEchartsInstance = null;
if (hasOverlapData.value && overlapChartRef.value) {
nextTick(() => {
overlapEchartsInstance = echarts.init(
overlapChartRef.value,
isDarkMode.value ? 'dark' : null,
{ height: 240 }
);
overlapEchartsInstance = echarts.init(overlapChartRef.value, isDarkMode.value ? 'dark' : null, {
height: 240
});
updateOverlapChart();
});
}
@@ -495,11 +480,7 @@
await nextTick();
if (!echartsInstance && chartRef.value) {
echartsInstance = echarts.init(
chartRef.value,
isDarkMode.value ? 'dark' : null,
{ height: 240 }
);
echartsInstance = echarts.init(chartRef.value, isDarkMode.value ? 'dark' : null, { height: 240 });
resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
if (echartsInstance) {
@@ -561,11 +542,10 @@
function onOverlapChartRightClick() {
if (easterEggTimer) {
toast('You can\'t farm this.', { position: 'bottom-center', icon: h(Sprout) });
toast(t('dialog.user.activity.easter_egg_reply'), { position: 'bottom-center', icon: h(Sprout) });
}
}
async function loadOverlapData(userId) {
if (!userId) return;
@@ -590,11 +570,9 @@
await nextTick();
if (!overlapEchartsInstance && overlapChartRef.value) {
overlapEchartsInstance = echarts.init(
overlapChartRef.value,
isDarkMode.value ? 'dark' : null,
{ height: 240 }
);
overlapEchartsInstance = echarts.init(overlapChartRef.value, isDarkMode.value ? 'dark' : null, {
height: 240
});
overlapResizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
if (overlapEchartsInstance) {

View File

@@ -69,7 +69,10 @@
@click="showAvatarDialog(avatar.id)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<Avatar class="size-9">
<AvatarImage v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" class="object-cover" />
<AvatarImage
v-if="avatar.thumbnailImageUrl"
:src="avatar.thumbnailImageUrl"
class="object-cover" />
<AvatarFallback>
<Image class="size-4 text-muted-foreground" />
</AvatarFallback>
@@ -112,6 +115,7 @@
import { Input } from '@/components/ui/input';
import { Spinner } from '@/components/ui/spinner';
import DeprecationAlert from '@/components/DeprecationAlert.vue';
import { refreshUserDialogAvatars } from '@/coordinators/userCoordinator';
import { useAdvancedSettingsStore, useAvatarStore, useUserStore } from '../../../stores';
@@ -119,7 +123,7 @@
const userStore = useUserStore();
const { userDialog, currentUser } = storeToRefs(userStore);
const { sortUserDialogAvatars, refreshUserDialogAvatars } = userStore;
const { sortUserDialogAvatars } = userStore;
import { showAvatarDialog, lookupAvatars } from '../../../coordinators/avatarCoordinator';
const { cachedAvatars } = useAvatarStore();

View File

@@ -1182,7 +1182,10 @@
"purge_option_730": "2 years",
"purge_option_all": "All data",
"purge_confirm_title": "Purge Avatar Feed Data",
"purge_confirm_description": "This will permanently delete avatar change records from the database and reclaim disk space. This action cannot be undone!\n\nIt is strongly recommended to back up your database file before proceeding.\n\nVRCX will restart after the operation completes.",
"purge_confirm_description_1": "This will permanently delete avatar change records from the database and reclaim disk space.",
"purge_confirm_description_2": "This action cannot be undone!",
"purge_confirm_description_3": "VRCX will restart after the operation completes.",
"purge_confirm_alert": "It is strongly recommended to back up your database file before proceeding!",
"purge_confirm_button": "Purge & Restart",
"purge_in_progress": "Purging avatar data...",
"purge_complete": "Avatar data purged successfully. Restarting...",

View File

@@ -1,29 +1,34 @@
<template>
<div class="flex flex-col gap-10 py-2">
<SettingsGroup :title="t('view.settings.advanced.advanced.vrchat_settings.header')">
<SettingsItem :label="t('view.settings.advanced.advanced.relaunch_vrchat.header')"
<SettingsItem
:label="t('view.settings.advanced.advanced.relaunch_vrchat.header')"
:description="t('view.settings.advanced.advanced.relaunch_vrchat.description')">
<Switch :model-value="relaunchVRChatAfterCrash" @update:modelValue="setRelaunchVRChatAfterCrash" />
</SettingsItem>
<SettingsItem :label="t('view.settings.advanced.advanced.vrchat_quit_fix.header')"
<SettingsItem
:label="t('view.settings.advanced.advanced.vrchat_quit_fix.header')"
:description="t('view.settings.advanced.advanced.vrchat_quit_fix.description')">
<Switch :model-value="vrcQuitFix" @update:modelValue="setVrcQuitFix" />
</SettingsItem>
<SettingsItem :label="t('view.settings.advanced.advanced.auto_cache_management.header')"
<SettingsItem
:label="t('view.settings.advanced.advanced.auto_cache_management.header')"
:description="t('view.settings.advanced.advanced.auto_cache_management.description')">
<Switch :model-value="autoSweepVRChatCache" @update:modelValue="setAutoSweepVRChatCache" />
</SettingsItem>
<SettingsItem :label="t('view.settings.advanced.advanced.self_invite.header')"
<SettingsItem
:label="t('view.settings.advanced.advanced.self_invite.header')"
:description="t('view.settings.advanced.advanced.self_invite.description')">
<Switch :model-value="selfInviteOverride" @update:modelValue="setSelfInviteOverride" />
</SettingsItem>
</SettingsGroup>
<SettingsGroup :title="t('view.settings.advanced_groups.security.header')">
<SettingsItem :label="t('view.settings.advanced.advanced.primary_password.header')"
<SettingsItem
:label="t('view.settings.advanced.advanced.primary_password.header')"
:description="t('view.settings.advanced.advanced.primary_password.description')">
<Switch
:model-value="enablePrimaryPassword"
@@ -49,7 +54,8 @@
<Switch :model-value="autoLoginDelayEnabled" @update:modelValue="setAutoLoginDelayEnabled" />
</SettingsItem>
<SettingsItem v-if="autoLoginDelayEnabled"
<SettingsItem
v-if="autoLoginDelayEnabled"
:label="t('view.settings.general.logging.auto_login_delay_button')">
<Button size="sm" variant="outline" @click="promptAutoLoginDelaySeconds">
{{ t('view.settings.general.logging.auto_login_delay_button') }}
@@ -65,7 +71,8 @@
}}</Button>
</SettingsItem>
<SettingsItem :label="t('view.settings.advanced.advanced.remote_database.enable')"
<SettingsItem
:label="t('view.settings.advanced.advanced.remote_database.enable')"
:description="t('view.settings.advanced.advanced.app_launcher.folder_tooltip')">
<Switch :model-value="enableAppLauncher" @update:modelValue="setEnableAppLauncher" />
</SettingsItem>
@@ -87,7 +94,9 @@
<SettingsGroup :title="t('view.settings.advanced.advanced.launch_commands.header')">
<SettingsItem
:label="t('view.settings.advanced.advanced.launch_commands.show_confirmation_on_switch_avatar_enable')"
:description="t('view.settings.advanced.advanced.launch_commands.show_confirmation_on_switch_avatar_tooltip')">
:description="
t('view.settings.advanced.advanced.launch_commands.show_confirmation_on_switch_avatar_tooltip')
">
<Switch
:model-value="showConfirmationOnSwitchAvatar"
@update:modelValue="setShowConfirmationOnSwitchAvatar" />
@@ -128,12 +137,30 @@
</SettingsItem>
<div class="flex flex-col gap-1 text-sm">
<span>{{ t('view.settings.advanced.advanced.cache_debug.user_cache') }} <span v-text="cacheSize.cachedUsers"></span></span>
<span>{{ t('view.settings.advanced.advanced.cache_debug.world_cache') }} <span v-text="cacheSize.cachedWorlds"></span></span>
<span>{{ t('view.settings.advanced.advanced.cache_debug.avatar_cache') }} <span v-text="cacheSize.cachedAvatars"></span></span>
<span>{{ t('view.settings.advanced.advanced.cache_debug.group_cache') }} <span v-text="cacheSize.cachedGroups"></span></span>
<span>{{ t('view.settings.advanced.advanced.cache_debug.avatar_name_cache') }} <span v-text="cacheSize.cachedAvatarNames"></span></span>
<span>{{ t('view.settings.advanced.advanced.cache_debug.instance_cache') }} <span v-text="cacheSize.cachedInstances"></span></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.user_cache') }}
<span v-text="cacheSize.cachedUsers"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.world_cache') }}
<span v-text="cacheSize.cachedWorlds"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.avatar_cache') }}
<span v-text="cacheSize.cachedAvatars"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.group_cache') }}
<span v-text="cacheSize.cachedGroups"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.avatar_name_cache') }}
<span v-text="cacheSize.cachedAvatarNames"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.cache_debug.instance_cache') }}
<span v-text="cacheSize.cachedInstances"></span
></span>
</div>
<SettingsItem :label="t('view.settings.advanced.advanced.cache_debug.show_console')">
@@ -151,18 +178,54 @@
</SettingsItem>
<div class="flex flex-col gap-1 text-sm">
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.gps') }} <span v-text="sqliteTableSizes.gps"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.status') }} <span v-text="sqliteTableSizes.status"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.bio') }} <span v-text="sqliteTableSizes.bio"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.avatar') }} <span v-text="sqliteTableSizes.avatar"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.online_offline') }} <span v-text="sqliteTableSizes.onlineOffline"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.friend_log_history') }} <span v-text="sqliteTableSizes.friendLogHistory"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.notification') }} <span v-text="sqliteTableSizes.notification"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.location') }} <span v-text="sqliteTableSizes.location"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.join_leave') }} <span v-text="sqliteTableSizes.joinLeave"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.portal_spawn') }} <span v-text="sqliteTableSizes.portalSpawn"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.video_play') }} <span v-text="sqliteTableSizes.videoPlay"></span></span>
<span>{{ t('view.settings.advanced.advanced.sqlite_table_size.event') }} <span v-text="sqliteTableSizes.event"></span></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.gps') }}
<span v-text="sqliteTableSizes.gps"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.status') }}
<span v-text="sqliteTableSizes.status"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.bio') }}
<span v-text="sqliteTableSizes.bio"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.avatar') }}
<span v-text="sqliteTableSizes.avatar"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.online_offline') }}
<span v-text="sqliteTableSizes.onlineOffline"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.friend_log_history') }}
<span v-text="sqliteTableSizes.friendLogHistory"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.notification') }}
<span v-text="sqliteTableSizes.notification"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.location') }}
<span v-text="sqliteTableSizes.location"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.join_leave') }}
<span v-text="sqliteTableSizes.joinLeave"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.portal_spawn') }}
<span v-text="sqliteTableSizes.portalSpawn"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.video_play') }}
<span v-text="sqliteTableSizes.videoPlay"></span
></span>
<span
>{{ t('view.settings.advanced.advanced.sqlite_table_size.event') }}
<span v-text="sqliteTableSizes.event"></span
></span>
</div>
<SettingsItem
@@ -174,11 +237,21 @@
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="Off">{{ t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_off') }}</SelectItem>
<SelectItem value="30">{{ t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_30') }}</SelectItem>
<SelectItem value="90">{{ t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_90') }}</SelectItem>
<SelectItem value="180">{{ t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_180') }}</SelectItem>
<SelectItem value="365">{{ t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_365') }}</SelectItem>
<SelectItem value="Off">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_off')
}}</SelectItem>
<SelectItem value="30">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_30')
}}</SelectItem>
<SelectItem value="90">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_90')
}}</SelectItem>
<SelectItem value="180">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_180')
}}</SelectItem>
<SelectItem value="365">{{
t('view.settings.advanced.advanced.database_cleanup.auto_cleanup_365')
}}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
@@ -190,22 +263,35 @@
{{ t('view.settings.advanced.advanced.database_cleanup.purge') }}
</Button>
</SettingsItem>
</SettingsGroup>
<Dialog :open="isPurgeDialogVisible" @update:open="(open) => { if (!open) isPurgeDialogVisible = false; }">
<Dialog
:open="isPurgeDialogVisible"
@update:open="
(open) => {
if (!open) isPurgeDialogVisible = false;
}
">
<DialogContent class="x-dialog sm:max-w-md">
<DialogHeader>
<DialogTitle>{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_title') }}</DialogTitle>
<DialogTitle>{{
t('view.settings.advanced.advanced.database_cleanup.purge_confirm_title')
}}</DialogTitle>
</DialogHeader>
<Alert variant="warning" class="mb-3">
<TriangleAlert />
<AlertDescription>
{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_description') }}
{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_alert') }}
</AlertDescription>
</Alert>
<div class="flex flex-col gap-1 text-sm text-muted-foreground mb-3">
<p>{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_description_1') }}</p>
<p>{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_description_2') }}</p>
<p>{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_description_3') }}</p>
</div>
<SettingsItem :label="t('view.settings.advanced.advanced.database_cleanup.purge_older_than')">
<Select v-model="selectedPurgePeriod">
<SelectTrigger class="w-36">
@@ -213,10 +299,18 @@
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="180">{{ t('view.settings.advanced.advanced.database_cleanup.purge_option_180') }}</SelectItem>
<SelectItem value="365">{{ t('view.settings.advanced.advanced.database_cleanup.purge_option_365') }}</SelectItem>
<SelectItem value="730">{{ t('view.settings.advanced.advanced.database_cleanup.purge_option_730') }}</SelectItem>
<SelectItem value="all">{{ t('view.settings.advanced.advanced.database_cleanup.purge_option_all') }}</SelectItem>
<SelectItem value="180">{{
t('view.settings.advanced.advanced.database_cleanup.purge_option_180')
}}</SelectItem>
<SelectItem value="365">{{
t('view.settings.advanced.advanced.database_cleanup.purge_option_365')
}}</SelectItem>
<SelectItem value="730">{{
t('view.settings.advanced.advanced.database_cleanup.purge_option_730')
}}</SelectItem>
<SelectItem value="all">{{
t('view.settings.advanced.advanced.database_cleanup.purge_option_all')
}}</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
@@ -226,18 +320,13 @@
<Button variant="outline" size="sm" @click="isPurgeDialogVisible = false">
{{ t('confirm.cancel_button') }}
</Button>
<Button
size="sm"
variant="destructive"
:disabled="purgeInProgress"
@click="handlePurge">
<Button size="sm" variant="destructive" :disabled="purgeInProgress" @click="handlePurge">
<Trash2 class="h-4 w-4 mr-1" />
{{ t('view.settings.advanced.advanced.database_cleanup.purge_confirm_button') }}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<SettingsGroup :title="t('view.settings.advanced_groups.diagnostics.header')">
<SettingsGroup :title="t('view.profile.game_info.header')">
@@ -283,7 +372,8 @@
<template v-if="branch === 'Nightly'">
<SettingsGroup :title="t('view.settings.advanced_groups.nightly.header')">
<SettingsItem :label="t('view.settings.advanced.advanced.anonymous_error_reporting.header')"
<SettingsItem
:label="t('view.settings.advanced.advanced.anonymous_error_reporting.header')"
:description="t('view.settings.advanced.advanced.anonymous_error_reporting.description')">
<Switch :model-value="sentryErrorReporting" @update:modelValue="setSentryErrorReporting()" />
</SettingsItem>
@@ -296,7 +386,7 @@
</template>
<script setup>
import { RefreshCcw, Trash2,TriangleAlert } from 'lucide-vue-next';
import { RefreshCcw, Trash2, TriangleAlert } from 'lucide-vue-next';
import { computed, reactive, ref } from 'vue';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
@@ -412,10 +502,7 @@
const isLinux = computed(() => LINUX);
function handlePurge() {
const days =
selectedPurgePeriod.value === 'all'
? null
: parseInt(selectedPurgePeriod.value, 10);
const days = selectedPurgePeriod.value === 'all' ? null : parseInt(selectedPurgePeriod.value, 10);
isPurgeDialogVisible.value = false;
purgeAvatarFeedData(days);
}