From 4d84014f7c69e3a8074f47f2fd7688af90feeb01 Mon Sep 17 00:00:00 2001
From: Pinapelz <yukais@pinapelz.com>
Date: Thu, 17 Apr 2025 18:33:33 -0700
Subject: frontend: add image gallery and MUSIC DIVER

---
 site/src/components/NewsFeed.tsx | 176 +++++++++++++++++++++++++--------------
 1 file changed, 113 insertions(+), 63 deletions(-)

(limited to 'site/src/components/NewsFeed.tsx')

diff --git a/site/src/components/NewsFeed.tsx b/site/src/components/NewsFeed.tsx
index 3057d61..ec682be 100644
--- a/site/src/components/NewsFeed.tsx
+++ b/site/src/components/NewsFeed.tsx
@@ -24,15 +24,32 @@ interface NewsFeedProps {
 export const NewsFeed: React.FC<NewsFeedProps> = ({ newsItems }) => {
   // Track which items are showing English content
   const [showEnglish, setShowEnglish] = useState<Record<string, boolean>>({});
+  // Track which items are expanded beyond the preview
+  const [expanded, setExpanded] = useState<Record<string, boolean>>({});
+  // Track the current image index for each news item
+  const [currentImageIndex, setCurrentImageIndex] = useState<Record<string, number>>({});
+  // Track loading state for images
+  const [loadingImages, setLoadingImages] = useState<Record<string, boolean>>({});
 
-  // Toggle language for a specific news item
   const toggleLanguage = (itemId: string) => {
-    setShowEnglish(prev => ({
-      ...prev,
-      [itemId]: !prev[itemId]
-    }));
+    setShowEnglish((prev) => ({ ...prev, [itemId]: !prev[itemId] }));
   };
 
+  const toggleExpand = (itemId: string) => {
+    setExpanded((prev) => ({ ...prev, [itemId]: !prev[itemId] }));
+  };
+
+  const changeImage = (itemId: string, index: number) => {
+    setCurrentImageIndex((prev) => ({ ...prev, [itemId]: index }));
+    setLoadingImages((prev) => ({ ...prev, [itemId]: true })); // Set loading state for the image
+  };
+
+  const handleImageLoad = (itemId: string) => {
+    setLoadingImages((prev) => ({ ...prev, [itemId]: false })); // Clear loading state when image loads
+  };
+
+  const PREVIEW_CHAR_LIMIT = 600;
+
   return (
     <div className="max-w-[600px] w-full mx-auto py-8 space-y-4">
       {newsItems.map((news) => {
@@ -42,14 +59,19 @@ export const NewsFeed: React.FC<NewsFeedProps> = ({ newsItems }) => {
           day: "2-digit",
         });
 
-        const gameId = news.identifier;
         const newsId = `${news.identifier}-${news.timestamp}-${news.content.substring(0, 20)}`;
-        const isEnglish = showEnglish[newsId] || false;
+        const isEnglish = !!showEnglish[newsId];
         const hasTranslation = news.en_headline || news.en_content;
 
-        // Choose content based on language selection
         const displayHeadline = isEnglish && news.en_headline ? news.en_headline : news.headline;
-        const displayContent = isEnglish && news.en_content ? news.en_content : news.content;
+        const displayContent = isEnglish && news.en_content ? news.en_content! : news.content;
+
+        // Read‑more logic
+        const isLong = displayContent.length > PREVIEW_CHAR_LIMIT;
+        const isExpanded = !!expanded[newsId];
+        const contentToShow = isLong && !isExpanded
+          ? displayContent.slice(0, PREVIEW_CHAR_LIMIT) + "…"
+          : displayContent;
 
         return (
           <div
@@ -59,28 +81,19 @@ export const NewsFeed: React.FC<NewsFeedProps> = ({ newsItems }) => {
             {/* Header (Game Icon + Info) */}
             <div className="flex items-center p-3 justify-between">
               <div className="flex items-center space-x-3">
-                {/* Game Icon */}
                 <div className="bg-purple-700 rounded-full h-8 w-8 flex items-center justify-center text-white text-xs font-bold flex-shrink-0">
-                  {gameId.substring(0, 1)}
+                  {news.identifier.charAt(0)}
                 </div>
-
                 <div className="flex flex-col leading-tight">
                   <span className="text-sm font-semibold text-gray-200">
                     {getGameTitle(news.identifier)}
                   </span>
-                  <span className="text-xs text-gray-400">
-                    {formattedDate}
-                  </span>
-                  {/* Display News Type */}
+                  <span className="text-xs text-gray-400">{formattedDate}</span>
                   {news.type && (
-                    <span className="text-xs text-gray-500 italic bold">
-                      {news.type}
-                    </span>
+                    <span className="text-xs text-gray-500 italic bold">{news.type}</span>
                   )}
                 </div>
               </div>
-
-              {/* Language Toggle Button */}
               {hasTranslation && (
                 <button
                   onClick={() => toggleLanguage(newsId)}
@@ -93,66 +106,103 @@ export const NewsFeed: React.FC<NewsFeedProps> = ({ newsItems }) => {
 
             {/* Content Area */}
             <div className="px-3 pt-1 pb-3">
-              {/* Headline */}
               {displayHeadline && (
-                <p className="font-semibold text-white text-sm mb-2">
-                  {displayHeadline}
-                </p>
+                <p className="font-semibold text-white text-sm mb-2">{displayHeadline}</p>
               )}
-
-              {/* Content */}
               <p className="text-sm text-gray-200 whitespace-pre-line mb-2">
-                {displayContent.split(/(\[.*?\]\(.*?\)|https?:\/\/[^\s]+)/g).map((part, index) => {
-                  const match = part.match(/\[(.*?)\]\((.*?)\)/);
-                  const urlMatch = part.match(/https?:\/\/[^\s]+/);
-                  if (match) {
+                {contentToShow.split(/(\[.*?\]\(.*?\)|https?:\/\/[^\s]+)/g).map((part, idx) => {
+                  const m = part.match(/\[(.*?)\]\((.*?)\)/);
+                  const u = part.match(/https?:\/\/[^\s]+/);
+                  if (m) {
                     return (
-                      <a key={index} href={match[2]} target="_blank" rel="noopener noreferrer" className="text-blue-400 hover:underline">
-                        {match[1]}
+                      <a
+                        key={idx}
+                        href={m[2]}
+                        target="_blank"
+                        rel="noopener noreferrer"
+                        className="text-blue-400 hover:underline"
+                      >
+                        {m[1]}
                       </a>
                     );
-                  } else if (urlMatch) {
+                  } else if (u) {
                     return (
-                      <a key={index} href={urlMatch[0]} target="_blank" rel="noopener noreferrer" className="text-blue-400 hover:underline">
-                        {urlMatch[0]}
+                      <a
+                        key={idx}
+                        href={u[0]}
+                        target="_blank"
+                        rel="noopener noreferrer"
+                        className="text-blue-400 hover:underline"
+                      >
+                        {u[0]}
                       </a>
                     );
                   }
                   return part;
                 })}
               </p>
+
+              {isLong && (
+                <button
+                  onClick={() => toggleExpand(newsId)}
+                  className="text-m text-blue-400 hover:underline"
+                >
+                  {isExpanded ? "Show less" : "Show more"}
+                </button>
+              )}
             </div>
 
-            {/* Post Image(s) */}
+            {/* Images */}
             <div className="w-full">
-              {news.images.map((img, i) => (
-                img.link ? (
-                  <a
-                    key={i}
-                    href={img.link}
-                    target="_blank"
-                    rel="noopener noreferrer"
-                    className="block hover:opacity-75"
-                  >
-                    <img
-                      src={img.image}
-                      alt="news visual"
-                      className="w-full object-cover py-2"
-                    />
-                  </a>
-                ) : (
-                  <div key={i} className="block">
-                    <img
-                      src={img.image}
-                      alt="news visual"
-                      className="w-full object-cover py-2"
-                    />
-                  </div>
-                )
-              ))}
+              {news.images.length > 0 && (
+                <>
+                  {/* Display only the current image */}
+                  {(() => {
+                    const currentIdx = currentImageIndex[newsId] || 0;
+                    const img = news.images[currentIdx];
+
+                    return (
+                      <div className="relative">
+                        {loadingImages[newsId] && (
+                          <div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-50">
+                            <div className="loader border-t-2 border-b-2 border-white w-6 h-6 rounded-full animate-spin"></div>
+                          </div>
+                        )}
+                        <img
+                          src={img.image}
+                          alt="news visual"
+                          className={`w-full object-cover py-2 ${
+                            loadingImages[newsId] ? "opacity-0" : "opacity-100"
+                          }`}
+                          onLoad={() => handleImageLoad(newsId)}
+                        />
+                      </div>
+                    );
+                  })()}
+
+                  {/* Image selector buttons (only shown if there are multiple images) */}
+                  {news.images.length > 1 && (
+                    <div className="flex justify-center gap-2 pb-3">
+                      {news.images.map((_, idx) => (
+                        <button
+                          key={idx}
+                          onClick={() => changeImage(newsId, idx)}
+                          className={`px-3 py-1 rounded ${
+                            (currentImageIndex[newsId] || 0) === idx
+                              ? "bg-blue-600 text-white"
+                              : "bg-gray-700 text-gray-300 hover:bg-gray-600"
+                          }`}
+                        >
+                          {idx + 1}
+                        </button>
+                      ))}
+                    </div>
+                  )}
+                </>
+              )}
             </div>
 
-            {/* Footer with Read More Link */}
+            {/* Footer */}
             {news.url && (
               <div className="px-3 py-2 bg-gray-800 text-center">
                 <a
-- 
cgit v1.2.3

