add img fallback

This commit is contained in:
pa
2026-03-15 18:04:20 +09:00
parent 0357e81a78
commit cc08f29800
37 changed files with 469 additions and 138 deletions
+2 -2
View File
@@ -7,7 +7,7 @@
<Avatar v-else class="rounded" :style="{ width: size + 'px', height: size + 'px' }">
<AvatarImage :src="imageUrl" class="object-cover" />
<AvatarFallback class="rounded">
<ImageOff class="size-4 text-muted-foreground" />
<Image class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
@@ -15,7 +15,7 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { ImageOff } from 'lucide-vue-next';
import { Image } from 'lucide-vue-next';
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
import { extractFileId, generateEmojiStyle } from '../shared/utils';
+20 -14
View File
@@ -278,18 +278,26 @@
<ContextMenuSub>
<ContextMenuSubTrigger>{{ t('status_bar.clocks') }}</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuRadioGroup :model-value="String(clockCount)" @update:modelValue="setClockCount">
<ContextMenuRadioItem value="0">
{{ t('status_bar.clocks_none') }}
</ContextMenuRadioItem>
<ContextMenuRadioItem value="1"> 1 {{ t('status_bar.clock') }} </ContextMenuRadioItem>
<ContextMenuRadioItem value="2">
2 {{ t('status_bar.clocks_label') }}
</ContextMenuRadioItem>
<ContextMenuRadioItem value="3">
3 {{ t('status_bar.clocks_label') }}
</ContextMenuRadioItem>
</ContextMenuRadioGroup>
<ContextMenuCheckboxItem
:model-value="clockCount === 0"
@update:model-value="setClockCount('0')">
{{ t('status_bar.clocks_none') }}
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem
:model-value="clockCount === 1"
@update:model-value="setClockCount('1')">
1 {{ t('status_bar.clock') }}
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem
:model-value="clockCount === 2"
@update:model-value="setClockCount('2')">
2 {{ t('status_bar.clocks_label') }}
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem
:model-value="clockCount === 3"
@update:model-value="setClockCount('3')">
3 {{ t('status_bar.clocks_label') }}
</ContextMenuCheckboxItem>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuContent>
@@ -302,8 +310,6 @@
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuSub,
ContextMenuSubContent,
+1 -1
View File
@@ -37,7 +37,7 @@ vi.mock('../ui/avatar', () => ({
}));
vi.mock('lucide-vue-next', () => ({
ImageOff: { template: '<i data-testid="image-off" />' }
Image: { template: '<i data-testid="image" />' }
}));
import Emoji from '../Emoji.vue';
@@ -10,11 +10,19 @@
<div class="flex">
<div style="flex: none; width: 160px; height: 120px">
<img
v-if="!imageError"
:src="avatarDialog.ref.thumbnailImageUrl"
class="cursor-pointer"
@click="showFullscreenImageDialog(avatarDialog.ref.imageUrl)"
style="width: 160px; height: 120px; border-radius: var(--radius-xl); object-fit: cover"
@error="imageError = true"
loading="lazy" />
<div
v-else
class="flex items-center justify-center bg-muted"
style="width: 160px; height: 120px; border-radius: var(--radius-xl)">
<Image class="size-8 text-muted-foreground" />
</div>
</div>
<div class="ml-4" style="flex: 1; display: flex; align-items: flex-start">
<div style="flex: 1">
@@ -346,7 +354,16 @@
:src="imageUrl"
style="width: 100%; height: 100%; object-fit: contain"
@click="showFullscreenImageDialog(imageUrl)"
@error="
$event.target.style.display = 'none';
$event.target.nextElementSibling.style.display = 'flex';
"
loading="lazy" />
<div
class="absolute inset-0 items-center justify-center bg-muted"
style="display: none">
<Image class="size-8 text-muted-foreground" />
</div>
</div>
</CarouselItem>
</CarouselContent>
@@ -663,6 +680,14 @@
const treeData = ref({});
const memo = ref('');
const imageError = ref(false);
watch(
() => avatarDialog.value.id,
() => {
imageError.value = false;
}
);
const setAvatarTagsDialog = ref({
visible: false,
loading: false,
@@ -10,12 +10,19 @@
<div style="display: flex">
<div style="flex: none; width: 120px; height: 120px">
<img
v-if="!groupDialog.loading"
v-if="!groupDialog.loading && !imageError"
:src="groupDialog.ref.iconUrl"
style="width: 120px; height: 120px; border-radius: var(--radius-xl)"
class="cursor-pointer"
@click="showFullscreenImageDialog(groupDialog.ref.iconUrl)"
@error="imageError = true"
loading="lazy" />
<div
v-else-if="!groupDialog.loading"
class="flex items-center justify-center bg-muted"
style="width: 120px; height: 120px; border-radius: var(--radius-xl)">
<Image class="size-8 text-muted-foreground" />
</div>
</div>
<div class="ml-4" style="flex: 1; display: flex; align-items: flex-start">
<div class="group-header" style="flex: 1">
@@ -374,6 +381,7 @@
Check,
CheckCircle,
Eye,
Image,
MessageSquare,
MoreHorizontal,
RefreshCw,
@@ -469,6 +477,14 @@
const groupDialogTabCurrentName = ref('0');
const treeData = ref({});
const imageError = ref(false);
watch(
() => groupDialog.value.id,
() => {
imageError.value = false;
}
);
const membersTabRef = ref(null);
const photosTabRef = ref(null);
@@ -1,12 +1,19 @@
<template>
<div>
<img
v-if="!groupDialog.loading"
v-if="!groupDialog.loading && !bannerError"
:src="groupDialog.ref.bannerUrl"
class="cursor-pointer"
style="flex: none; width: 100%; aspect-ratio: 6/1; object-fit: cover; border-radius: var(--radius-md)"
@click="showFullscreenImageDialog(groupDialog.ref.bannerUrl)"
@error="bannerError = true"
loading="lazy" />
<div
v-else-if="!groupDialog.loading"
class="flex items-center justify-center bg-muted"
style="width: 100%; aspect-ratio: 6/1; border-radius: var(--radius-md)">
<Image class="size-8 text-muted-foreground" />
</div>
</div>
<div class="flex flex-wrap items-start px-2.5" style="max-height: none">
<span v-if="groupDialog.instances.length" class="text-xs font-bold" style="margin: 6px">
@@ -34,7 +41,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showUserDialog(user.id)">
<div class="relative inline-block flex-none size-9 mr-2.5" :class="userStatusClass(user)">
<img class="size-full rounded-full object-cover" :src="userImage(user)" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="userImage(user)" class="object-cover" />
<AvatarFallback>
<User class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span
@@ -58,6 +70,7 @@
<span style="display: block" v-text="groupDialog.announcement.title" />
<div v-if="groupDialog.announcement.imageUrl" style="display: inline-block; margin-right: 6px">
<img
v-if="!announcementPhotoError"
:src="groupDialog.announcement.imageUrl"
class="cursor-pointer"
style="
@@ -68,7 +81,14 @@
object-fit: cover;
"
@click="showFullscreenImageDialog(groupDialog.announcement.imageUrl)"
@error="announcementPhotoError = true"
loading="lazy" />
<div
v-else
class="flex items-center justify-center bg-muted"
style="width: 60px; height: 60px; border-radius: var(--radius-md)">
<Image class="size-5 text-muted-foreground" />
</div>
</div>
<pre
class="text-xs font-[inherit]"
@@ -340,9 +360,11 @@
</template>
<script setup>
import { Copy, Eye, MoreHorizontal } from 'lucide-vue-next';
import { Copy, Eye, Image, MoreHorizontal, User } from 'lucide-vue-next';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
@@ -356,14 +378,14 @@
userStatusClass
} from '../../../shared/utils';
import { refreshInstancePlayerCount } from '../../../coordinators/instanceCoordinator';
import { useGalleryStore, useGroupStore, useInstanceStore, useLocationStore, useUserStore } from '../../../stores';
import { useGalleryStore, useGroupStore, useInstanceStore, useLocationStore } from '../../../stores';
import { useGroupCalendarEvents } from './useGroupCalendarEvents';
import GroupCalendarEventCard from '../../../views/Tools/components/GroupCalendarEventCard.vue';
import InstanceActionBar from '../../InstanceActionBar.vue';
import { showUserDialog } from '../../../coordinators/userCoordinator';
const props = defineProps({
defineProps({
showGroupPostEditDialog: {
type: Function,
required: true
@@ -383,6 +405,17 @@
const { pastCalenderEvents, upcomingCalenderEvents, updateFollowingCalendarData } = useGroupCalendarEvents(groupDialog);
const bannerError = ref(false);
const announcementPhotoError = ref(false);
watch(
() => groupDialog.value.id,
() => {
bannerError.value = false;
announcementPhotoError.value = false;
}
);
/**
*
* @param groupRef
@@ -82,7 +82,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showUserDialog(user.userId)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<img class="size-full rounded-full object-cover" :src="userImage(user.user)" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="userImage(user.user)" class="object-cover" />
<AvatarFallback>
<User class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span
@@ -139,7 +144,12 @@
class="infinite-list-item box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showUserDialog(user.userId)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<img class="size-full rounded-full object-cover" :src="userImage(user.user)" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="userImage(user.user)" class="object-cover" />
<AvatarFallback>
<User class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span
@@ -201,7 +211,8 @@
</template>
<script setup>
import { Download, Eye, MessageSquare, Pencil, RefreshCcw, Tag } from 'lucide-vue-next';
import { Download, Eye, MessageSquare, Pencil, RefreshCcw, Tag, User } from 'lucide-vue-next';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from '@/components/ui/button';
import { InputGroupField } from '@/components/ui/input-group';
@@ -41,11 +41,21 @@
v-for="image in groupDialog.galleries[gallery.id]"
:key="image.id"
class="p-0 overflow-hidden transition-shadow hover:shadow-md">
<img
:src="image.imageUrl"
:class="[' cursor-pointer', 'max-w-full', 'max-h-full']"
@click="showFullscreenImageDialog(image.imageUrl)"
loading="lazy" />
<div class="cursor-pointer" @click="showFullscreenImageDialog(image.imageUrl)">
<img
:src="image.imageUrl"
:class="['max-w-full', 'max-h-full']"
@error="
$event.target.style.display = 'none';
$event.target.nextElementSibling.style.display = 'flex';
"
loading="lazy" />
<div
class="hidden h-[200px] w-full items-center justify-center bg-muted"
style="display: none">
<Image class="size-8 text-muted-foreground" />
</div>
</div>
</Card>
</div>
</template>
@@ -55,7 +65,7 @@
<script setup>
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { RefreshCw } from 'lucide-vue-next';
import { Image, RefreshCw } from 'lucide-vue-next';
import { Spinner } from '@/components/ui/spinner';
import { TabsUnderline } from '@/components/ui/tabs';
import { storeToRefs } from 'pinia';
@@ -18,18 +18,29 @@
<div class="flex-1 overflow-hidden">
<span style="display: block" v-text="post.title" />
<div v-if="post.imageUrl" style="display: inline-block; margin-right: 6px">
<img
:src="post.imageUrl"
<div
class="cursor-pointer"
style="
flex: none;
width: 60px;
height: 60px;
border-radius: var(--radius-md);
object-fit: cover;
"
@click="showFullscreenImageDialog(post.imageUrl)"
loading="lazy" />
style="flex: none; width: 60px; height: 60px"
@click="showFullscreenImageDialog(post.imageUrl)">
<img
:src="post.imageUrl"
style="
width: 60px;
height: 60px;
border-radius: var(--radius-md);
object-fit: cover;
"
@error="
$event.target.style.display = 'none';
$event.target.nextElementSibling.style.display = 'flex';
"
loading="lazy" />
<div
class="items-center justify-center bg-muted"
style="width: 60px; height: 60px; border-radius: var(--radius-md); display: none">
<Image class="size-5 text-muted-foreground" />
</div>
</div>
</div>
<pre
class="text-xs font-[inherit]"
@@ -101,7 +112,7 @@
</template>
<script setup>
import { Eye, Pencil, Trash2 } from 'lucide-vue-next';
import { Eye, Image, Pencil, Trash2 } from 'lucide-vue-next';
import { Button } from '@/components/ui/button';
import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia';
@@ -110,7 +121,7 @@
import { formatDateFilter, hasGroupPermission } from '../../../shared/utils';
import { useGalleryStore, useGroupStore } from '../../../stores';
const props = defineProps({
defineProps({
showGroupPostEditDialog: {
type: Function,
required: true
@@ -68,11 +68,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showAvatarDialog(avatar.id)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<img
v-if="avatar.thumbnailImageUrl"
class="size-full rounded-full object-cover"
:src="avatar.thumbnailImageUrl"
loading="lazy" />
<Avatar class="size-9">
<AvatarImage v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" class="object-cover" />
<AvatarFallback>
<Image class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span class="block truncate font-medium leading-[18px]" v-text="avatar.name"></span>
@@ -103,7 +104,8 @@
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { RefreshCw } from 'lucide-vue-next';
import { Image, RefreshCw } from 'lucide-vue-next';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from '@/components/ui/button';
import { DataTableEmpty } from '@/components/ui/data-table';
@@ -45,10 +45,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showWorldDialog(world.id)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<img
class="size-full rounded-full object-cover"
:src="world.thumbnailImageUrl"
loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="world.thumbnailImageUrl" class="object-cover" />
<AvatarFallback>
<Image class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span class="block truncate font-medium leading-[18px]" v-text="world.name"></span>
@@ -68,6 +70,8 @@
<script setup>
import { computed, ref } from 'vue';
import { Image } from 'lucide-vue-next';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { DataTableEmpty } from '@/components/ui/data-table';
import { TabsUnderline } from '@/components/ui/tabs';
import { storeToRefs } from 'pinia';
@@ -75,7 +79,7 @@
import DeprecationAlert from '@/components/DeprecationAlert.vue';
import { useFavoriteStore, useUserStore, useWorldStore } from '../../../stores';
import { useFavoriteStore, useUserStore } from '../../../stores';
import { showWorldDialog } from '../../../coordinators/worldCoordinator';
import { handleFavoriteWorldList } from '../../../coordinators/favoriteCoordinator';
import { favoriteRequest } from '../../../api';
@@ -142,7 +142,12 @@
</Button>
</div>
<div class="relative inline-block flex-none size-9 mr-2.5">
<img class="size-full rounded-full object-cover" :src="group.iconUrl" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="group.iconUrl" class="object-cover" />
<AvatarFallback>
<Users class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span class="block truncate font-medium leading-[18px]" v-text="group.name"></span>
@@ -247,7 +252,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showGroupDialog(group.id)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<img class="size-full rounded-full object-cover" :src="group.iconUrl" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="group.iconUrl" class="object-cover" />
<AvatarFallback>
<Users class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span class="block truncate font-medium leading-[18px]" v-text="group.name"></span>
@@ -283,7 +293,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showGroupDialog(group.id)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<img class="size-full rounded-full object-cover" :src="group.iconUrl" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="group.iconUrl" class="object-cover" />
<AvatarFallback>
<Users class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span class="block truncate font-medium leading-[18px]" v-text="group.name"></span>
@@ -330,7 +345,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showGroupDialog(group.id)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<img class="size-full rounded-full object-cover" :src="group.iconUrl" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="group.iconUrl" class="object-cover" />
<AvatarFallback>
<Users class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span class="block truncate font-medium leading-[18px]" v-text="group.name"></span>
@@ -361,7 +381,8 @@
</template>
<script setup>
import { ArrowDown, ArrowUp, DownloadIcon, Eye, LogOut, RefreshCw, Tag } from 'lucide-vue-next';
import { ArrowDown, ArrowUp, DownloadIcon, Eye, LogOut, RefreshCw, Tag, Users } from 'lucide-vue-next';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { nextTick, ref } from 'vue';
import { Button } from '@/components/ui/button';
@@ -30,10 +30,12 @@
<div
class="relative inline-block flex-none size-9 mr-2.5"
:class="userStatusClass(userDialog.$location.user)">
<img
class="size-full rounded-full object-cover"
:src="userImage(userDialog.$location.user, true)"
loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="userImage(userDialog.$location.user, true)" class="object-cover" />
<AvatarFallback>
<User class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span
@@ -51,7 +53,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showUserDialog(user.id)">
<div class="relative inline-block flex-none size-9 mr-2.5" :class="userStatusClass(user)">
<img class="size-full rounded-full object-cover" :src="userImage(user, true)" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="userImage(user, true)" class="object-cover" />
<AvatarFallback>
<User class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span
@@ -145,7 +152,9 @@
:src="userDialog.representedGroup.$thumbnailUrl"
@load="userDialog.isRepresentedGroupLoading = false"
@error="userDialog.isRepresentedGroupLoading = false" />
<AvatarFallback class="rounded-lg!" />
<AvatarFallback class="rounded-lg!">
<Image class="size-5 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<span
@@ -461,7 +470,7 @@
</template>
<script setup>
import { Copy, Info, Languages, MoreHorizontal, Pencil, Trash2 } from 'lucide-vue-next';
import { Copy, Image, Info, Languages, MoreHorizontal, Pencil, Trash2, User } from 'lucide-vue-next';
import {
DropdownMenu,
DropdownMenuContent,
@@ -41,7 +41,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showUserDialog(user.id)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<img class="size-full rounded-full object-cover" :src="userImage(user)" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="userImage(user)" class="object-cover" />
<AvatarFallback>
<User class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span
@@ -54,9 +59,10 @@
</template>
<script setup>
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from '@/components/ui/button';
import { RefreshCw } from 'lucide-vue-next';
import { RefreshCw, User } from 'lucide-vue-next';
import { Spinner } from '@/components/ui/spinner';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
@@ -59,7 +59,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showWorldDialog(world.id)">
<div class="relative inline-block flex-none size-9 mr-2.5">
<img class="size-full rounded-full object-cover" :src="world.thumbnailImageUrl" loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="world.thumbnailImageUrl" class="object-cover" />
<AvatarFallback>
<Image class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span class="block truncate font-medium leading-[18px]" v-text="world.name"></span>
@@ -79,7 +84,8 @@
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from '@/components/ui/button';
import { DataTableEmpty } from '@/components/ui/data-table';
import { RefreshCw } from 'lucide-vue-next';
import { Image, RefreshCw } from 'lucide-vue-next';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Spinner } from '@/components/ui/spinner';
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
@@ -4,20 +4,29 @@
<img
v-if="
!userDialog.loading &&
!profileImageError &&
(userDialog.ref.profilePicOverrideThumbnail || userDialog.ref.profilePicOverride)
"
class="cursor-pointer"
:src="userDialog.ref.profilePicOverrideThumbnail || userDialog.ref.profilePicOverride"
style="height: 120px; width: 213.33px; border-radius: var(--radius-xl); object-fit: cover"
@click="showFullscreenImageDialog(userDialog.ref.profilePicOverride)"
@error="profileImageError = true"
loading="lazy" />
<img
v-else-if="!userDialog.loading"
v-else-if="!userDialog.loading && !profileImageError && userDialog.ref.currentAvatarThumbnailImageUrl"
class="cursor-pointer"
:src="userDialog.ref.currentAvatarThumbnailImageUrl"
style="height: 120px; width: 160px; border-radius: var(--radius-xl); object-fit: cover"
@click="showFullscreenImageDialog(userDialog.ref.currentAvatarImageUrl)"
@error="profileImageError = true"
loading="lazy" />
<div
v-else-if="!userDialog.loading"
class="flex items-center justify-center bg-muted"
style="height: 120px; width: 160px; border-radius: var(--radius-xl)">
<Image class="size-8 text-muted-foreground" />
</div>
</div>
<div class="ml-4" style="flex: 1; display: flex; align-items: flex-start">
<div style="flex: 1">
@@ -232,11 +241,19 @@
<div v-if="userDialog.ref.userIcon" style="flex: none; margin-right: 8px">
<img
v-if="!userIconError"
class="cursor-pointer"
:src="userImage(userDialog.ref, true, '256', true)"
style="flex: none; width: 120px; height: 120px; border-radius: var(--radius-xl); object-fit: cover"
@click="showFullscreenImageDialog(userDialog.ref.userIcon)"
@error="userIconError = true"
loading="lazy" />
<div
v-else
class="flex items-center justify-center bg-muted"
style="width: 120px; height: 120px; border-radius: var(--radius-xl)">
<Image class="size-8 text-muted-foreground" />
</div>
</div>
<UserActionDropdown class="ml-2 mt-12" :user-dialog-command="userDialogCommand" />
@@ -245,7 +262,8 @@
</template>
<script setup>
import { Apple, ChevronDown, IdCard, Monitor, Shield, Smartphone, UserPlus, Users } from 'lucide-vue-next';
import { Apple, ChevronDown, IdCard, Image, Monitor, Shield, Smartphone, UserPlus, Users } from 'lucide-vue-next';
import { ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
@@ -287,6 +305,17 @@
const { showFullscreenImageDialog } = useGalleryStore();
const profileImageError = ref(false);
const userIconError = ref(false);
watch(
() => userDialog.value.id,
() => {
profileImageError.value = false;
userIconError.value = false;
}
);
const getUserStateText = props.getUserStateText;
const copyUserDisplayName = props.copyUserDisplayName;
const toggleBadgeVisibility = props.toggleBadgeVisibility;
@@ -10,12 +10,19 @@
<div style="display: flex">
<div style="flex: none; width: 160px; height: 120px">
<img
v-if="!worldDialog.loading"
v-if="!worldDialog.loading && !imageError"
:src="worldDialog.ref.thumbnailImageUrl"
class="cursor-pointer"
style="width: 160px; height: 120px; border-radius: var(--radius-xl)"
@click="showFullscreenImageDialog(worldDialog.ref.imageUrl)"
@error="imageError = true"
loading="lazy" />
<div
v-else-if="!worldDialog.loading"
class="flex items-center justify-center bg-muted"
style="width: 160px; height: 120px; border-radius: var(--radius-xl)">
<Image class="size-8 text-muted-foreground" />
</div>
</div>
<div class="ml-4" style="flex: 1; display: flex; align-items: flex-start">
<div style="flex: 1">
@@ -480,6 +487,14 @@
const treeData = ref({});
const translatedDescription = ref('');
const isTranslating = ref(false);
const imageError = ref(false);
watch(
() => worldDialog.value.id,
() => {
imageError.value = false;
}
);
const isDialogVisible = computed({
get() {
@@ -53,10 +53,12 @@
<div
class="relative inline-block flex-none size-9 mr-2.5"
:class="userStatusClass(room.$location.user)">
<img
class="size-full rounded-full object-cover"
:src="userImage(room.$location.user, true)"
loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="userImage(room.$location.user, true)" class="object-cover" />
<AvatarFallback>
<User class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span
@@ -76,10 +78,12 @@
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
@click="showUserDialog(user.id)">
<div class="relative inline-block flex-none size-9 mr-2.5" :class="userStatusClass(user)">
<img
class="size-full rounded-full object-cover"
:src="userImage(user, true)"
loading="lazy" />
<Avatar class="size-9">
<AvatarImage :src="userImage(user, true)" class="object-cover" />
<AvatarFallback>
<User class="size-4 text-muted-foreground" />
</AvatarFallback>
</Avatar>
</div>
<div class="flex-1 overflow-hidden">
<span
@@ -104,6 +108,7 @@
<script setup>
import { Check, User } from 'lucide-vue-next';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Spinner } from '@/components/ui/spinner';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';