From 02bba16285017f2d28e96f496cd075422cc9e9fe Mon Sep 17 00:00:00 2001 From: arussac Date: Sun, 31 May 2026 15:04:51 +0200 Subject: [PATCH] feat: enhance customization options with new 'Mes Persos' panel and improved context menus --- frontend/src/components/AdBand.vue | 3 + frontend/src/components/MessageItem.vue | 48 ++- frontend/src/components/MessageList.vue | 3 +- frontend/src/components/SendButton.vue | 3 + frontend/src/components/shop/MesPersos.vue | 313 +++++++++++++++++++ frontend/src/components/shop/ProductCard.vue | 20 +- frontend/src/composables/useCustomStyles.ts | 3 +- frontend/src/composables/useMessages.ts | 13 +- frontend/src/views/HomePage.vue | 18 +- frontend/src/views/ShopPage.vue | 34 +- 10 files changed, 426 insertions(+), 32 deletions(-) create mode 100644 frontend/src/components/shop/MesPersos.vue diff --git a/frontend/src/components/AdBand.vue b/frontend/src/components/AdBand.vue index 8d075ff..7e9b812 100644 --- a/frontend/src/components/AdBand.vue +++ b/frontend/src/components/AdBand.vue @@ -33,9 +33,11 @@ import { computed, onMounted, watch } from 'vue'; import { useAds } from '@/composables/useAds'; import { openContextMenu } from '@/composables/useContextMenu'; import { useCustomStyles, AD_FRAME_PRESETS } from '@/composables/useCustomStyles'; +import { useMyPerks } from '@/composables/useMessages'; const { ads, fetchAds, reportImpression } = useAds('band'); const { prefs } = useCustomStyles(); +const { myPerks } = useMyPerks(); const cardStyle = computed(() => { const p = AD_FRAME_PRESETS[prefs.adFrame]; @@ -43,6 +45,7 @@ const cardStyle = computed(() => { }); function onRightClick(e: MouseEvent): void { + if (!myPerks.value.elementSkin) return; e.stopPropagation(); openContextMenu({ x: e.clientX, diff --git a/frontend/src/components/MessageItem.vue b/frontend/src/components/MessageItem.vue index 639635d..5003210 100644 --- a/frontend/src/components/MessageItem.vue +++ b/frontend/src/components/MessageItem.vue @@ -3,7 +3,7 @@
- + {{ petsLeft(message) }} {{ message.authorIp }} {{ petsRight(message) }} @@ -30,7 +30,7 @@ :key="reply.id" class="reply" > - + {{ petsLeft(reply) }} {{ reply.authorIp }} {{ petsRight(reply) }} @@ -55,14 +55,16 @@ import type { Message, Reply } from '@/composables/useMessages'; import { getIpColorWithPerks, getIpGlowWithPerks, getIpColor, getIpGlow } from '@/composables/ipColor'; import { usePerks } from '@/composables/usePerks'; import { openContextMenu } from '@/composables/useContextMenu'; -import { useCustomStyles, IP_COLOR_OPTIONS, PET_OPTIONS } from '@/composables/useCustomStyles'; +import { useCustomStyles, IP_COLOR_OPTIONS } from '@/composables/useCustomStyles'; +import { useMyPerks } from '@/composables/useMessages'; import RichContent from './RichContent.vue'; import MessageAttachments from './MessageAttachments.vue'; -defineProps<{ message: Message }>(); +const props = defineProps<{ message: Message; myIp?: string }>(); defineEmits<{ reply: [payload: { id: string; authorIp: string }] }>(); const { perksFor } = usePerks(); +const { myPerks } = useMyPerks(); const { prefs } = useCustomStyles(); function perksOf(m: Reply): any { @@ -102,19 +104,43 @@ function petsRight(m: Reply): string { } function openIpMenu(e: MouseEvent, ip: string): void { + if (ip !== props.myIp) return; + + const hasElementSkin = !!myPerks.value.elementSkin; + const ownedPets = myPerks.value.pets ?? []; + const hasPets = ownedPets.length > 0; + + // Nothing to show if no perk unlocks customization. + if (!hasElementSkin && !hasPets) return; + const currentColor = prefs.ipColors[ip] ?? 'auto'; const currentPet = ip in prefs.ipPets ? prefs.ipPets[ip] : '__inherit__'; + + const items: import('@/composables/useContextMenu').ContextMenuItem[] = []; + + if (hasElementSkin) { + items.push({ value: '__h_color', label: 'Couleur', isHeader: true }); + items.push(...IP_COLOR_OPTIONS.map((o) => ({ value: `color:${o.value}`, label: o.label, swatch: o.swatch }))); + } + + if (hasPets) { + items.push({ value: '__h_pet', label: 'Pet', isHeader: true }); + items.push({ value: 'pet:__inherit__', label: '↩ défaut' }); + // Show only the pets the user actually owns. + const seen = new Set(); + for (const p of ownedPets) { + if (!seen.has(p.char)) { + seen.add(p.char); + items.push({ value: `pet:${p.char}`, label: p.char }); + } + } + } + openContextMenu({ x: e.clientX, y: e.clientY, title: ip, - items: [ - { value: '__h_color', label: 'Couleur', isHeader: true }, - ...IP_COLOR_OPTIONS.map((o) => ({ value: `color:${o.value}`, label: o.label, swatch: o.swatch })), - { value: '__h_pet', label: 'Pet', isHeader: true }, - { value: 'pet:__inherit__', label: '↩ défaut (perk)' }, - ...PET_OPTIONS.map((o) => ({ value: `pet:${o.value}`, label: o.label })), - ], + items, current: currentColor !== 'auto' ? `color:${currentColor}` : `pet:${currentPet}`, onSelect: (v) => { if (v.startsWith('color:')) { diff --git a/frontend/src/components/MessageList.vue b/frontend/src/components/MessageList.vue index 21cbe0e..8e89da6 100644 --- a/frontend/src/components/MessageList.vue +++ b/frontend/src/components/MessageList.vue @@ -7,6 +7,7 @@ v-for="msg in messages" :key="msg.id" :message="msg" + :my-ip="myIp" @reply="$emit('reply', $event)" />
@@ -25,7 +26,7 @@ import type { Message } from '@/composables/useMessages'; import MessageItem from './MessageItem.vue'; import InlineCasinoAd from './InlineCasinoAd.vue'; -const props = defineProps<{ messages: Message[]; hideAds?: boolean }>(); +const props = defineProps<{ messages: Message[]; hideAds?: boolean; myIp?: string }>(); defineEmits<{ reply: [payload: { id: string; authorIp: string }] }>(); const listEl = ref(null); diff --git a/frontend/src/components/SendButton.vue b/frontend/src/components/SendButton.vue index 109e9e2..88400fb 100644 --- a/frontend/src/components/SendButton.vue +++ b/frontend/src/components/SendButton.vue @@ -19,11 +19,13 @@ import { computed } from 'vue'; import { openContextMenu } from '@/composables/useContextMenu'; import { useCustomStyles, SEND_BUTTON_PRESETS } from '@/composables/useCustomStyles'; +import { useMyPerks } from '@/composables/useMessages'; defineProps<{ disabled?: boolean }>(); defineEmits<{ send: [] }>(); const { prefs } = useCustomStyles(); +const { myPerks } = useMyPerks(); const btnStyle = computed(() => { const p = SEND_BUTTON_PRESETS[prefs.sendButton]; @@ -31,6 +33,7 @@ const btnStyle = computed(() => { }); function onRightClick(e: MouseEvent): void { + if (!myPerks.value.elementSkin) return; openContextMenu({ x: e.clientX, y: e.clientY, diff --git a/frontend/src/components/shop/MesPersos.vue b/frontend/src/components/shop/MesPersos.vue new file mode 100644 index 0000000..9b17c2f --- /dev/null +++ b/frontend/src/components/shop/MesPersos.vue @@ -0,0 +1,313 @@ + + + + + + diff --git a/frontend/src/components/shop/ProductCard.vue b/frontend/src/components/shop/ProductCard.vue index 0090ece..654d207 100644 --- a/frontend/src/components/shop/ProductCard.vue +++ b/frontend/src/components/shop/ProductCard.vue @@ -81,7 +81,15 @@ {{ fmt(effectivePrice) }} cr
+ +