added sender sprite next to petpet
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
import { receivedInteractions, clearInteraction } from "$lib/stores/interaction-store";
|
import { receivedInteractions, clearInteraction } from "$lib/stores/interaction-store";
|
||||||
import { INTERACTION_TYPE_HEADPAT } from "$lib/constants/interaction";
|
import { INTERACTION_TYPE_HEADPAT } from "$lib/constants/interaction";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { getSpriteSheetUrl } from "$lib/utils/sprite-utils";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import type { PresenceStatus } from "../../types/bindings/PresenceStatus";
|
import type { PresenceStatus } from "../../types/bindings/PresenceStatus";
|
||||||
import type { DollDto } from "../../types/bindings/DollDto";
|
import type { DollDto } from "../../types/bindings/DollDto";
|
||||||
@@ -28,27 +29,57 @@
|
|||||||
// Fullscreen modal state for headpats
|
// Fullscreen modal state for headpats
|
||||||
let showFullscreenModal = $state(false);
|
let showFullscreenModal = $state(false);
|
||||||
let fullscreenImageSrc = $state("");
|
let fullscreenImageSrc = $state("");
|
||||||
|
let headpatSenderSpriteUrl = $state("");
|
||||||
let headpatSenderId = $state<string | null>(null);
|
let headpatSenderId = $state<string | null>(null);
|
||||||
let headpatTimer: ReturnType<typeof setTimeout> | null = null;
|
let headpatTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
// Queue for pending headpats (when modal is already showing)
|
// Queue for pending headpats (when modal is already showing)
|
||||||
let headpatQueue = $state<Array<{ userId: string; content: string }>>([]);
|
let headpatQueue = $state<Array<{ userId: string; content: string }>>([]);
|
||||||
|
let headpatSpriteToken = 0;
|
||||||
|
|
||||||
// Process next headpat in queue
|
// Process next headpat in queue
|
||||||
function processNextHeadpat() {
|
function processNextHeadpat() {
|
||||||
if (headpatQueue.length > 0) {
|
if (headpatQueue.length > 0) {
|
||||||
const next = headpatQueue.shift()!;
|
const next = headpatQueue.shift()!;
|
||||||
clearInteraction(next.userId);
|
clearInteraction(next.userId);
|
||||||
fullscreenImageSrc = `data:image/gif;base64,${next.content}`;
|
|
||||||
headpatSenderId = next.userId;
|
headpatSenderId = next.userId;
|
||||||
|
void loadHeadpatSprites(next.userId);
|
||||||
showFullscreenModal = true;
|
showFullscreenModal = true;
|
||||||
scheduleHeadpatDismiss();
|
scheduleHeadpatDismiss();
|
||||||
} else {
|
} else {
|
||||||
fullscreenImageSrc = "";
|
fullscreenImageSrc = "";
|
||||||
|
headpatSenderSpriteUrl = "";
|
||||||
headpatSenderId = null;
|
headpatSenderId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadHeadpatSprites(senderId: string) {
|
||||||
|
const token = ++headpatSpriteToken;
|
||||||
|
const senderDoll = getFriendDoll(senderId);
|
||||||
|
const userDoll = getUserDoll();
|
||||||
|
|
||||||
|
let userPetpetGif = "";
|
||||||
|
if (userDoll) {
|
||||||
|
try {
|
||||||
|
const gifBase64 = await invoke<string>("encode_pet_doll_gif_base64", { doll: userDoll });
|
||||||
|
userPetpetGif = `data:image/gif;base64,${gifBase64}`;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to generate user petpet:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderSpriteUrl = senderDoll
|
||||||
|
? await getSpriteSheetUrl({
|
||||||
|
bodyColor: senderDoll.configuration.colorScheme.body,
|
||||||
|
outlineColor: senderDoll.configuration.colorScheme.outline,
|
||||||
|
})
|
||||||
|
: await getSpriteSheetUrl();
|
||||||
|
|
||||||
|
if (token !== headpatSpriteToken) return;
|
||||||
|
fullscreenImageSrc = userPetpetGif;
|
||||||
|
headpatSenderSpriteUrl = senderSpriteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
function scheduleHeadpatDismiss() {
|
function scheduleHeadpatDismiss() {
|
||||||
if (headpatTimer) {
|
if (headpatTimer) {
|
||||||
clearTimeout(headpatTimer);
|
clearTimeout(headpatTimer);
|
||||||
@@ -81,8 +112,8 @@
|
|||||||
} else {
|
} else {
|
||||||
// Show immediately and clear from store
|
// Show immediately and clear from store
|
||||||
clearInteraction(userId);
|
clearInteraction(userId);
|
||||||
fullscreenImageSrc = `data:image/gif;base64,${interaction.content}`;
|
|
||||||
headpatSenderId = userId;
|
headpatSenderId = userId;
|
||||||
|
void loadHeadpatSprites(userId);
|
||||||
showFullscreenModal = true;
|
showFullscreenModal = true;
|
||||||
scheduleHeadpatDismiss();
|
scheduleHeadpatDismiss();
|
||||||
}
|
}
|
||||||
@@ -266,6 +297,7 @@
|
|||||||
<FullscreenModal
|
<FullscreenModal
|
||||||
bind:visible={showFullscreenModal}
|
bind:visible={showFullscreenModal}
|
||||||
imageSrc={fullscreenImageSrc}
|
imageSrc={fullscreenImageSrc}
|
||||||
|
senderSpriteUrl={headpatSenderSpriteUrl}
|
||||||
senderName={getHeadpatSenderName(headpatSenderId)}
|
senderName={getHeadpatSenderName(headpatSenderId)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cubicOut } from "svelte/easing";
|
import { cubicOut } from "svelte/easing";
|
||||||
import { type TransitionConfig } from "svelte/transition";
|
import { type TransitionConfig } from "svelte/transition";
|
||||||
|
import PetSprite from "$lib/components/PetSprite.svelte";
|
||||||
|
import { SPRITE_SETS, SPRITE_SIZE } from "$lib/constants/pet-sprites";
|
||||||
|
|
||||||
function fadeSlide(
|
function fadeSlide(
|
||||||
node: HTMLElement,
|
node: HTMLElement,
|
||||||
@@ -14,11 +16,22 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const idleSprite = {
|
||||||
|
x: SPRITE_SETS.idle[0][0] * SPRITE_SIZE,
|
||||||
|
y: SPRITE_SETS.idle[0][1] * SPRITE_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
let {
|
let {
|
||||||
imageSrc,
|
imageSrc,
|
||||||
|
senderSpriteUrl = "",
|
||||||
visible = $bindable(false),
|
visible = $bindable(false),
|
||||||
senderName = "",
|
senderName = "",
|
||||||
}: { imageSrc: string; visible: boolean; senderName?: string } = $props();
|
}: {
|
||||||
|
imageSrc: string;
|
||||||
|
senderSpriteUrl?: string;
|
||||||
|
visible: boolean;
|
||||||
|
senderName?: string;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -30,6 +43,24 @@
|
|||||||
{#if senderName}
|
{#if senderName}
|
||||||
<div class="mb-4 text-white text-lg font-medium">{senderName} gave you a headpat!</div>
|
<div class="mb-4 text-white text-lg font-medium">{senderName} gave you a headpat!</div>
|
||||||
{/if}
|
{/if}
|
||||||
<img src={imageSrc} alt="Headpat" class="max-w-full max-h-full object-contain" />
|
<div class="flex items-center justify-center gap-6">
|
||||||
|
{#if senderSpriteUrl}
|
||||||
|
<div class="flex items-center justify-center size-32">
|
||||||
|
<div style="transform: scale(4); transform-origin: center;">
|
||||||
|
<PetSprite
|
||||||
|
spriteSheetUrl={senderSpriteUrl}
|
||||||
|
spriteX={idleSprite.x}
|
||||||
|
spriteY={idleSprite.y}
|
||||||
|
size={32}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<img
|
||||||
|
src={imageSrc}
|
||||||
|
alt="Headpat"
|
||||||
|
class="max-w-full max-h-full object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user