mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 06:56:04 +02:00
adjust navmenu context menu and my avatars ui
This commit is contained in:
@@ -148,6 +148,10 @@
|
|||||||
<ContextMenuItem :disabled="!hasNotifications" @click="clearAllNotifications">
|
<ContextMenuItem :disabled="!hasNotifications" @click="clearAllNotifications">
|
||||||
{{ t('nav_menu.mark_all_read') }}
|
{{ t('nav_menu.mark_all_read') }}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
<ContextMenuSeparator />
|
||||||
|
<ContextMenuItem @click="handleOpenCustomNavDialog">
|
||||||
|
{{ t('nav_menu.custom_nav.header') }}
|
||||||
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
|
|
||||||
@@ -361,7 +365,13 @@
|
|||||||
DropdownMenuTrigger
|
DropdownMenuTrigger
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { computed, defineAsyncComponent, h, onMounted, ref, watch } from 'vue';
|
import { computed, defineAsyncComponent, h, onMounted, ref, watch } from 'vue';
|
||||||
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu';
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuSeparator,
|
||||||
|
ContextMenuTrigger
|
||||||
|
} from '@/components/ui/context-menu';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
import { ChevronRight, Heart } from 'lucide-vue-next';
|
import { ChevronRight, Heart } from 'lucide-vue-next';
|
||||||
import { Kbd } from '@/components/ui/kbd';
|
import { Kbd } from '@/components/ui/kbd';
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
</Badge>
|
</Badge>
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="w-auto p-3" align="start">
|
<PopoverContent class="w-80 p-3" align="start">
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<Field>
|
<Field>
|
||||||
<FieldLabel>{{ t('dialog.avatar.info.visibility') }}</FieldLabel>
|
<FieldLabel>{{ t('dialog.avatar.info.visibility') }}</FieldLabel>
|
||||||
@@ -177,6 +177,17 @@
|
|||||||
class="cursor-pointer min-h-0">
|
class="cursor-pointer min-h-0">
|
||||||
<template #row-context-menu="{ row }">
|
<template #row-context-menu="{ row }">
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
|
<ContextMenuItem @click="handleContextMenuAction('details', row.original)">
|
||||||
|
<Eye class="size-4" />
|
||||||
|
{{ t('dialog.avatar.actions.view_details') }}
|
||||||
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem
|
||||||
|
:disabled="row.original.id === currentAvatarId"
|
||||||
|
@click="handleContextMenuAction('wear', row.original)">
|
||||||
|
<Check class="size-4" />
|
||||||
|
{{ t('view.favorite.select_avatar_tooltip') }}
|
||||||
|
</ContextMenuItem>
|
||||||
|
<ContextMenuSeparator />
|
||||||
<ContextMenuItem @click="handleContextMenuAction('manageTags', row.original)">
|
<ContextMenuItem @click="handleContextMenuAction('manageTags', row.original)">
|
||||||
<Tag class="size-4" />
|
<Tag class="size-4" />
|
||||||
{{ t('dialog.avatar.actions.manage_tags') }}
|
{{ t('dialog.avatar.actions.manage_tags') }}
|
||||||
@@ -284,6 +295,8 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
|
Check,
|
||||||
|
Eye,
|
||||||
Image as ImageIcon,
|
Image as ImageIcon,
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
List,
|
List,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
ArrowUpDown,
|
ArrowUpDown,
|
||||||
Check,
|
Check,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
|
Eye,
|
||||||
Image,
|
Image,
|
||||||
Monitor,
|
Monitor,
|
||||||
Pencil,
|
Pencil,
|
||||||
@@ -94,7 +95,7 @@ export function getColumns({
|
|||||||
<img
|
<img
|
||||||
src={ref.thumbnailImageUrl}
|
src={ref.thumbnailImageUrl}
|
||||||
class="cursor-pointer rounded-sm object-cover"
|
class="cursor-pointer rounded-sm object-cover"
|
||||||
style="width: 36px; height: 24px;"
|
style="width: 34px; height: 22px;"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
onClick={() => onShowAvatarDialog(ref.id)}
|
onClick={() => onShowAvatarDialog(ref.id)}
|
||||||
/>
|
/>
|
||||||
@@ -137,7 +138,7 @@ export function getColumns({
|
|||||||
const tags = row.original.$tags || [];
|
const tags = row.original.$tags || [];
|
||||||
if (!tags.length) return null;
|
if (!tags.length) return null;
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-nowrap gap-1 overflow-hidden">
|
||||||
{tags.map((entry) => {
|
{tags.map((entry) => {
|
||||||
const hashColor = getTagColor(entry.tag);
|
const hashColor = getTagColor(entry.tag);
|
||||||
const storedColor =
|
const storedColor =
|
||||||
@@ -367,14 +368,21 @@ export function getColumns({
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
class="rounded-full"
|
class="rounded-full h-6 w-6"
|
||||||
size="icon-sm"
|
size="icon"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<Ellipsis class="h-4 w-4" />
|
<Ellipsis class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => onShowAvatarDialog(ref.id)}
|
||||||
|
>
|
||||||
|
<Eye class="size-4" />
|
||||||
|
{t('dialog.avatar.actions.view_details')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onContextMenuAction('manageTags', ref)
|
onContextMenuAction('manageTags', ref)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<HoverCard :open-delay="700" :close-delay="100">
|
<HoverCard :open="hoverOpen" :open-delay="700" :close-delay="100" @update:open="handleHoverOpen">
|
||||||
<HoverCardTrigger as="div">
|
<HoverCardTrigger as="div">
|
||||||
<ContextMenu>
|
<ContextMenu @update:open="handleContextMenuOpen">
|
||||||
<ContextMenuTrigger as="div">
|
<ContextMenuTrigger as="div">
|
||||||
<div class="avatar-card-wrapper rounded-lg" @click="$emit('click')">
|
<div class="avatar-card-wrapper rounded-lg" @click="$emit('click')">
|
||||||
<Card
|
<Card
|
||||||
@@ -71,6 +71,10 @@
|
|||||||
<Eye class="size-4" />
|
<Eye class="size-4" />
|
||||||
{{ t('dialog.avatar.actions.view_details') }}
|
{{ t('dialog.avatar.actions.view_details') }}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem :disabled="isActive" @click="emit('context-action', 'wear', avatar)">
|
||||||
|
<Check class="size-4" />
|
||||||
|
{{ t('view.favorite.select_avatar_tooltip') }}
|
||||||
|
</ContextMenuItem>
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
<ContextMenuItem @click="emit('context-action', 'manageTags', avatar)">
|
<ContextMenuItem @click="emit('context-action', 'manageTags', avatar)">
|
||||||
<Tag class="size-4" />
|
<Tag class="size-4" />
|
||||||
@@ -122,7 +126,16 @@
|
|||||||
<HoverCardContent class="w-80 p-3 text-sm" side="right" :side-offset="8" align="start">
|
<HoverCardContent class="w-80 p-3 text-sm" side="right" :side-offset="8" align="start">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<!-- Name -->
|
<!-- Name -->
|
||||||
<div class="font-medium text-base truncate">{{ avatar.name }}</div>
|
<div class="flex items-start gap-1">
|
||||||
|
<div class="font-medium text-base truncate flex-1 min-w-0">{{ avatar.name }}</div>
|
||||||
|
<Button
|
||||||
|
size="icon-sm"
|
||||||
|
variant="ghost"
|
||||||
|
class="shrink-0 size-6 rounded-full"
|
||||||
|
@click="emit('context-action', 'details', avatar)">
|
||||||
|
<ExternalLink class="size-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Tags -->
|
<!-- Tags -->
|
||||||
<div v-if="avatar.$tags?.length" class="flex flex-wrap gap-1">
|
<div v-if="avatar.$tags?.length" class="flex flex-wrap gap-1">
|
||||||
@@ -154,9 +167,6 @@
|
|||||||
</Badge>
|
</Badge>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="text-muted-foreground">{{ t('dialog.avatar.info.version') }}</span>
|
|
||||||
<span>{{ avatar.version ?? '-' }}</span>
|
|
||||||
|
|
||||||
<span class="text-muted-foreground">{{ t('dialog.avatar.info.platform') }}</span>
|
<span class="text-muted-foreground">{{ t('dialog.avatar.info.platform') }}</span>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Badge v-if="platformInfo.isPC" class="x-tag-platform-pc" variant="outline">
|
<Badge v-if="platformInfo.isPC" class="x-tag-platform-pc" variant="outline">
|
||||||
@@ -185,6 +195,9 @@
|
|||||||
<span>{{ iosPerf }}</span>
|
<span>{{ iosPerf }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<span class="text-muted-foreground">{{ t('dialog.avatar.info.version') }}</span>
|
||||||
|
<span>{{ avatar.version ?? '-' }}</span>
|
||||||
|
|
||||||
<template v-if="avatar.$timeSpent">
|
<template v-if="avatar.$timeSpent">
|
||||||
<span class="text-muted-foreground">{{ t('dialog.avatar.info.time_spent') }}</span>
|
<span class="text-muted-foreground">{{ t('dialog.avatar.info.time_spent') }}</span>
|
||||||
<span>{{ timeToText(avatar.$timeSpent) }}</span>
|
<span>{{ timeToText(avatar.$timeSpent) }}</span>
|
||||||
@@ -202,7 +215,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Apple, Eye, Image as ImageIcon, Monitor, Pencil, RefreshCw, Smartphone, Tag, User } from 'lucide-vue-next';
|
import {
|
||||||
|
Apple,
|
||||||
|
Check,
|
||||||
|
ExternalLink,
|
||||||
|
Eye,
|
||||||
|
Image as ImageIcon,
|
||||||
|
Monitor,
|
||||||
|
Pencil,
|
||||||
|
RefreshCw,
|
||||||
|
Smartphone,
|
||||||
|
Tag,
|
||||||
|
User
|
||||||
|
} from 'lucide-vue-next';
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
@@ -212,15 +237,33 @@
|
|||||||
} from '@/components/ui/context-menu';
|
} from '@/components/ui/context-menu';
|
||||||
import { formatDateFilter, getAvailablePlatforms, getPlatformInfo, timeToText } from '@/shared/utils';
|
import { formatDateFilter, getAvailablePlatforms, getPlatformInfo, timeToText } from '@/shared/utils';
|
||||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { computed } from 'vue';
|
|
||||||
import { getTagColor } from '@/shared/constants';
|
import { getTagColor } from '@/shared/constants';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const hoverOpen = ref(false);
|
||||||
|
const contextMenuOpen = ref(false);
|
||||||
|
|
||||||
|
const handleContextMenuOpen = (open) => {
|
||||||
|
contextMenuOpen.value = open;
|
||||||
|
if (open) {
|
||||||
|
hoverOpen.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHoverOpen = (open) => {
|
||||||
|
if (contextMenuOpen.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hoverOpen.value = open;
|
||||||
|
};
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
avatar: {
|
avatar: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
Reference in New Issue
Block a user