This commit is contained in:
xds
2026-02-24 12:04:17 +03:00
parent 1a7295aa77
commit a1d37ac517
8 changed files with 341 additions and 56 deletions

View File

@@ -674,6 +674,11 @@ const groupedGenerations = computed(() => {
return result
})
const getChildByAssetId = (group, assetId) => {
if (!group.isGroup) return group;
return group.children.find(c => c.result_list?.includes(assetId)) || group;
}
const hasActiveGenerations = computed(() => {
return generations.value.some(g => ['processing', 'starting', 'running'].includes(g.status))
})
@@ -688,7 +693,8 @@ const allGalleryImages = computed(() => {
assetId,
url: API_URL + '/assets/' + assetId,
thumbnailUrl: API_URL + '/assets/' + assetId + '?thumbnail=true',
gen
gen,
is_liked: gen.liked_assets?.includes(assetId)
})
}
}
@@ -889,6 +895,25 @@ async function confirmAddToPlan() {
}
}
const handleLiked = ({ id, is_liked }) => {
// Update local state in generations
generations.value.forEach(gen => {
if (gen.id === id) {
gen.is_liked = is_liked
}
})
}
const toggleLike = async (gen) => {
if (!gen || !gen.id) return
try {
const response = await dataService.toggleLike(gen.id)
handleLiked({ id: gen.id, is_liked: response.is_liked })
} catch (e) {
console.error('Failed to toggle like', e)
}
}
// Exit select mode when switching to feed
watch(viewMode, (v) => {
if (v !== 'gallery') {
@@ -994,6 +1019,10 @@ watch(viewMode, (v) => {
</div>
<div
class="flex gap-1 opacity-100 md:opacity-0 group-hover:opacity-100 transition-opacity">
<Button :icon="gen.is_liked ? 'pi pi-heart-fill' : 'pi pi-heart'" text rounded size="small"
class="!w-7 !h-7 !p-0"
:class="gen.is_liked ? '!text-pink-500' : '!text-slate-400 hover:!text-pink-500'"
v-tooltip.top="gen.is_liked ? 'Unlike' : 'Like'" @click="toggleLike(gen)" />
<Button icon="pi pi-copy" text rounded size="small"
class="!w-7 !h-7 !text-slate-400 hover:!text-white"
v-tooltip.top="'Reuse Prompt'" @click="reusePrompt(gen)" />
@@ -1016,19 +1045,32 @@ watch(viewMode, (v) => {
class="relative group/img cursor-pointer aspect-[4/3]">
<img :src="API_URL + '/assets/' + res + '?thumbnail=true'"
class="w-full h-full object-cover"
@click="openImagePreview(gen.result_list.map(r => ({ url: API_URL + '/assets/' + r, gen: gen })), resIdx)" />
@click="openImagePreview(gen.result_list.map(r => ({ url: API_URL + '/assets/' + r, gen: getChildByAssetId(gen, r), assetId: r, is_liked: getChildByAssetId(gen, r).is_liked })), resIdx)" />
<!-- Liked indicator -->
<div v-if="getChildByAssetId(gen, res).is_liked"
class="absolute top-1.5 left-1.5 w-5 h-5 rounded-full bg-pink-500 shadow-lg flex items-center justify-center border border-pink-400 z-10">
<i class="pi pi-heart-fill text-white text-[8px]"></i>
</div>
<!-- Per-image hover overlay -->
<div
class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover/img:opacity-100 transition-opacity duration-200 pointer-events-none">
</div>
<div
class="absolute bottom-1 right-1 flex gap-1 opacity-0 group-hover/img:opacity-100 transition-opacity duration-200">
<button @click.stop="toggleLike(getChildByAssetId(gen, res))"
class="w-6 h-6 rounded-md backdrop-blur-sm flex items-center justify-center text-white transition-all hover:scale-110 shadow-lg"
:class="getChildByAssetId(gen, res).is_liked ? 'bg-pink-500 hover:bg-pink-400' : 'bg-slate-700/80 hover:bg-pink-500'"
v-tooltip.top="getChildByAssetId(gen, res).is_liked ? 'Unlike' : 'Like'">
<i :class="getChildByAssetId(gen, res).is_liked ? 'pi pi-heart-fill' : 'pi pi-heart'" style="font-size: 10px"></i>
</button>
<button @click.stop="setAsReference(res)"
class="w-6 h-6 rounded-md bg-violet-600/80 hover:bg-violet-500 backdrop-blur-sm flex items-center justify-center text-white transition-all hover:scale-110 shadow-lg"
v-tooltip.top="'Use as Reference'">
<i class="pi pi-pencil" style="font-size: 10px"></i>
</button>
<button @click.stop="deleteAssetFromGeneration(gen, res)"
<button @click.stop="deleteAssetFromGeneration(getChildByAssetId(gen, res), res)"
class="w-6 h-6 rounded-md bg-red-600/80 hover:bg-red-500 backdrop-blur-sm flex items-center justify-center text-white transition-all hover:scale-110 shadow-lg"
v-tooltip.top="'Remove Image'">
<i class="pi pi-trash" style="font-size: 10px"></i>
@@ -1099,6 +1141,12 @@ watch(viewMode, (v) => {
<img :src="img.thumbnailUrl" class="w-full h-full object-cover" />
<!-- Liked Badge -->
<div v-if="img.is_liked"
class="absolute top-2 right-2 z-10 w-6 h-6 rounded-full bg-pink-500 shadow-lg flex items-center justify-center border border-pink-400">
<i class="pi pi-heart-fill text-white text-[10px]"></i>
</div>
<!-- Selection checkmark (always visible in select mode) -->
<div v-if="isSelectMode"
class="absolute top-2 left-2 w-7 h-7 rounded-full flex items-center justify-center transition-all shadow-lg z-10"
@@ -1111,6 +1159,11 @@ watch(viewMode, (v) => {
class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex flex-col justify-end p-3">
<p class="text-xs text-white line-clamp-2 mb-2">{{ img.gen.prompt }}</p>
<div class="flex gap-2 justify-end">
<Button :icon="img.gen.is_liked ? 'pi pi-heart-fill' : 'pi pi-heart'" rounded text size="small"
class="!text-white transition-colors"
:class="img.gen.is_liked ? '!text-pink-500' : 'hover:!text-pink-500 hover:!bg-white/20'"
v-tooltip.top="img.gen.is_liked ? 'Unlike' : 'Like'"
@click.stop="toggleLike(img.gen)" />
<Button icon="pi pi-pencil" rounded text size="small"
class="!text-white hover:!bg-white/20" v-tooltip.top="'Use as Reference'"
@click.stop="setAsReference(img.assetId)" />
@@ -1425,6 +1478,7 @@ watch(viewMode, (v) => {
@reuse-prompt="reusePrompt"
@reuse-asset="reuseAssets"
@use-result-as-asset="useResultAsAsset"
@liked="handleLiked"
/>
<!-- Add to Content Plan Dialog -->