import { DialogButton, Focusable, ModalRoot, PanelSection, ScrollPanelGroup, showModal } from '@decky/ui'; import { lazy, useEffect, useMemo, useState } from 'react'; import { FaInfo, FaTimes } from 'react-icons/fa'; import { Announcement, getAnnouncements } from '../store'; import { useSetting } from '../utils/hooks/useSetting'; import WithSuspense from './WithSuspense'; const SEVERITIES = { High: { color: '#bb1414', text: '#fff', }, Medium: { color: '#bbbb14', text: '#fff', }, Low: { color: '#1488bb', text: '#fff', }, }; const welcomeAnnouncement: Announcement = { id: 'welcomeAnnouncement', title: 'Welcome to Decky!', text: 'We hope you enjoy using Decky! If you have any questions or feedback, please let us know.', created: Date.now().toString(), updated: Date.now().toString(), }; const welcomeAnnouncement2: Announcement = { id: 'welcomeAnnouncement2', title: 'Test With mkdown content and a slightly long title', text: '# Lorem Ipsum\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n## Features\n\n- **Bold text** for emphasis\n- *Italic text* for style\n- `Code snippets` for technical content\n\n### Getting Started\n\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\n> This is a blockquote with some important information.\n\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.', created: Date.now().toString(), updated: Date.now().toString(), }; export function AnnouncementsDisplay() { const [announcements, setAnnouncements] = useState([welcomeAnnouncement, welcomeAnnouncement2]); const [hiddenAnnouncementIds, setHiddenAnnouncementIds] = useSetting('hiddenAnnouncementIds', []); function addAnnouncements(newAnnouncements: Announcement[]) { // Removes any duplicates and sorts by created date setAnnouncements((oldAnnouncements) => { const newArr = [...oldAnnouncements, ...newAnnouncements]; const setOfIds = new Set(newArr.map((a) => a.id)); return ( ( Array.from(setOfIds) .map((id) => newArr.find((a) => a.id === id)) // Typescript doesn't type filter(Boolean) correctly, so I have to assert this .filter(Boolean) as Announcement[] ).sort((a, b) => { return new Date(b.created).getTime() - new Date(a.created).getTime(); }) ); }); } async function fetchAnnouncement() { const announcements = await getAnnouncements(); announcements && addAnnouncements(announcements); } useEffect(() => { void fetchAnnouncement(); }, []); const currentlyDisplayingAnnouncements: Announcement[] = useMemo(() => { return announcements.filter((announcement) => !hiddenAnnouncementIds.includes(announcement.id)); }, [announcements, hiddenAnnouncementIds]); function hideAnnouncement(id: string) { setHiddenAnnouncementIds([...hiddenAnnouncementIds, id]); void fetchAnnouncement(); } if (currentlyDisplayingAnnouncements.length === 0) { return null; } return ( {currentlyDisplayingAnnouncements.map((announcement) => ( hideAnnouncement(announcement.id)} /> ))} ); } function Announcement({ announcement, onHide }: { announcement: Announcement; onHide: () => void }) { // Severity is not implemented in the API currently const severity = SEVERITIES['Low']; return ( {announcement.title} showModal( { onHide(); }} />, ) } > onHide()} > ); } const MarkdownRenderer = lazy(() => import('./Markdown')); function AnnouncementModal({ announcement, closeModal, onHide, }: { announcement: Announcement; closeModal?: () => void; onHide: () => void; }) { return ( {announcement.title} Use your finger to scroll { if (!evt?.detail?.button) return; if (evt.detail.button === 2) { closeModal?.(); } }} > { closeModal?.(); }} > {announcement.text} closeModal?.()}>Close { // onHide(); closeModal?.(); }} > Close and Hide Announcement ); }