From a1d37ac517bc22b077c9648ab9241a4e63ef6494 Mon Sep 17 00:00:00 2001 From: xds Date: Tue, 24 Feb 2026 12:04:17 +0300 Subject: [PATCH] likes --- src/components/GenerationPreviewModal.vue | 46 +++++++++++- src/services/dataService.js | 5 ++ src/views/AlbumDetailView.vue | 61 ++++++++++++---- src/views/AssetsView.vue | 54 ++++++++++---- src/views/CharacterDetailView.vue | 86 ++++++++++++++++++----- src/views/FlexibleGenerationView.vue | 42 +++++++++++ src/views/IdeaDetailView.vue | 60 +++++++++++++++- src/views/ImageGenerationView.vue | 43 ++++++++++-- 8 files changed, 341 insertions(+), 56 deletions(-) diff --git a/src/components/GenerationPreviewModal.vue b/src/components/GenerationPreviewModal.vue index 6d0d2b8..6cb1a22 100644 --- a/src/components/GenerationPreviewModal.vue +++ b/src/components/GenerationPreviewModal.vue @@ -3,6 +3,7 @@ import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue' import Dialog from 'primevue/dialog' import Button from 'primevue/button' import Tag from 'primevue/tag' +import { dataService } from '../services/dataService' const props = defineProps({ visible: { @@ -23,11 +24,40 @@ const props = defineProps({ } }) -const emit = defineEmits(['update:visible', 'reusePrompt', 'reuseAsset', 'useResultAsAsset']) +const emit = defineEmits(['update:visible', 'reusePrompt', 'reuseAsset', 'useResultAsAsset', 'liked']) const previewIndex = ref(0) const previewImage = computed(() => props.previewImages[previewIndex.value] || null) +// Like state management +const isTogglingLike = ref(false) +const localLikedStates = ref({}) // id -> bool + +const isLiked = computed(() => { + if (!previewImage.value?.gen) return false + const id = previewImage.value.gen.id || previewImage.value.gen._id + if (localLikedStates.value[id] !== undefined) return localLikedStates.value[id] + return previewImage.value.gen.is_liked || false +}) + +const toggleLike = async () => { + const id = previewImage.value?.gen?.id || previewImage.value?.gen?._id + if (!id || isTogglingLike.value) return + + isTogglingLike.value = true + try { + const response = await dataService.toggleLike(id) + // Assume response returns the new state or we just toggle it + const newState = response.is_liked !== undefined ? response.is_liked : !isLiked.value + localLikedStates.value[id] = newState + emit('liked', { id, is_liked: newState }) + } catch (e) { + console.error('Failed to toggle like', e) + } finally { + isTogglingLike.value = false + } +} + // Reset index when modal opens watch(() => props.visible, (newVal) => { if (newVal) { @@ -125,6 +155,13 @@ const onUseResultAsAsset = () => { class="max-w-full max-h-[85vh] object-contain shadow-2xl transition-transform duration-300" draggable="false" /> + + +
@@ -158,6 +195,13 @@ const onUseResultAsAsset = () => {
+
- +
diff --git a/src/views/AssetsView.vue b/src/views/AssetsView.vue index 282c774..ab0629b 100644 --- a/src/views/AssetsView.vue +++ b/src/views/AssetsView.vue @@ -17,6 +17,8 @@ import InputText from 'primevue/inputtext' import Textarea from 'primevue/textarea' import Image from 'primevue/image' +import GenerationPreviewModal from '../components/GenerationPreviewModal.vue' + const router = useRouter() const confirm = useConfirm() const toast = useToast() @@ -26,6 +28,28 @@ const activeFilter = ref('all') // @ts-ignore const API_URL = import.meta.env.VITE_API_URL +// Preview Modal State +const isImagePreviewVisible = ref(false) +const previewIndex = ref(0) +const previewImages = computed(() => { + return assets.value.map(asset => ({ + url: API_URL + (asset.url || asset.link), + assetId: asset.id, + is_liked: asset.is_liked || asset.liked, + name: asset.name, + created_at: asset.created_at, + // For assets, we might not have a full 'gen' object, but modal expects it for technical data + gen: asset.generation_id ? { id: asset.generation_id, prompt: asset.prompt } : null + })) +}) + +const handleLiked = ({ id, is_liked }: { id: string, is_liked: boolean }) => { + const asset = assets.value.find(a => a.id === id) + if (asset) { + asset.is_liked = is_liked + } +} + // Albums Logic const albumStore = useAlbumStore() const { albums, loading: albumsLoading } = storeToRefs(albumStore) @@ -34,7 +58,6 @@ const showCreateDialog = ref(false) const newAlbum = ref({ name: '', description: '' }) const submittingAlbum = ref(false) -const selectedAsset = ref(null) const isModalVisible = ref(false) const first = ref(0) @@ -69,8 +92,9 @@ const handleFileUpload = async (event: Event) => { } const openModal = (asset: Asset) => { - selectedAsset.value = asset - isModalVisible.value = true + const idx = assets.value.findIndex(a => a.id === asset.id) + previewIndex.value = idx >= 0 ? idx : 0 + isImagePreviewVisible.value = true } const loadAssets = async () => { @@ -245,6 +269,12 @@ const formatDate = (dateString: string) => { :alt="asset.name" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" /> + +
+ +
+
@@ -368,17 +398,13 @@ const formatDate = (dateString: string) => {
- -
- -
-

{{ selectedAsset.name }}

-

{{ formatDate(selectedAsset.created_at) }}

-
-
-
+ { + previewImages.value = images + previewIndex.value = index + isImagePreviewVisible.value = true +} + +const handleLiked = ({ id, is_liked }) => { + // Update local state in history + historyGenerations.value.forEach(gen => { + if (gen.result_list?.includes(id)) { + if (!gen.liked_assets) gen.liked_assets = [] + if (is_liked) { + if (!gen.liked_assets.includes(id)) gen.liked_assets.push(id) + } else { + gen.liked_assets = gen.liked_assets.filter(aid => aid !== id) + } + } + }) + // Update in character assets + characterAssets.value.forEach(asset => { + if (asset.id === id) { + asset.is_liked = is_liked + } + }) + // Update in generatedResult if it's the one liked + if (generatedResult.value) { + if (generatedResult.value.type === 'assets') { + const found = generatedResult.value.assets.find(a => a.id === id) + if (found) { + if (!generatedResult.value.liked_assets) generatedResult.value.liked_assets = [] + if (is_liked) { + if (!generatedResult.value.liked_assets.includes(id)) generatedResult.value.liked_assets.push(id) + } else { + generatedResult.value.liked_assets = generatedResult.value.liked_assets.filter(aid => aid !== id) + } + } + } + } +} + const selectedEnvironment = ref(null) const isEnvModalVisible = ref(false) const isEnvAssetPickerVisible = ref(false) @@ -282,8 +329,14 @@ const openModal = (asset) => { toggleBulkSelection(asset.id) return } - selectedAsset.value = asset - isModalVisible.value = true + const idx = characterAssets.value.findIndex(a => a.id === asset.id) + const images = characterAssets.value.map(a => ({ + url: API_URL + a.url, + assetId: a.id, + is_liked: a.is_liked || a.liked, + gen: a.generation_id ? { id: a.generation_id, prompt: a.prompt } : null + })) + openImagePreview(images, idx >= 0 ? idx : 0) } const toggleBulkSelection = (id) => { @@ -1267,7 +1320,7 @@ const handleGenerate = async () => { :class="gen.children.length > 2 ? 'grid-cols-4' : 'grid-cols-2'">
+ @click.stop="openImagePreview([{ url: API_URL + '/assets/' + child.result_list[0], assetId: child.result_list[0], is_liked: child.liked_assets?.includes(child.result_list[0]), gen: child }])"> {
+ @mouseleave="onThumbnailLeave" + @click.stop="openImagePreview([{ url: API_URL + '/assets/' + gen.result_list[0], assetId: gen.result_list[0], is_liked: gen.liked_assets?.includes(gen.result_list[0]), gen: gen }])"> + class="w-full h-full object-cover rounded opacity-100 cursor-pointer" />
@@ -1559,18 +1613,16 @@ const handleGenerate = async () => { Character not found.
- -
- -
-

{{ selectedAsset.name }}

-

{{ selectedAsset.type }}

-
-
-
+ diff --git a/src/views/FlexibleGenerationView.vue b/src/views/FlexibleGenerationView.vue index aa23496..0f92e21 100644 --- a/src/views/FlexibleGenerationView.vue +++ b/src/views/FlexibleGenerationView.vue @@ -592,6 +592,8 @@ const allPreviewImages = computed(() => { for (const assetId of gen.result_list) { images.push({ url: API_URL + '/assets/' + assetId, + assetId: assetId, + is_liked: gen.liked_assets?.includes(assetId), genId: gen.id, prompt: gen.prompt, gen: gen @@ -602,6 +604,25 @@ const allPreviewImages = computed(() => { return images }) +const handleLiked = ({ id, is_liked }) => { + // Update local state in history + historyGenerations.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) + } +} + const openImagePreview = (url) => { const idx = allPreviewImages.value.findIndex(img => img.url === url) previewIndex.value = idx >= 0 ? idx : 0 @@ -895,6 +916,12 @@ const confirmAddToAlbum = async () => { + + +
+ +
{
+
+ -