monicaeverett

anitabi_api_test(REACT)

Oct 27th, 2024 (edited)
178
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 7.02 KB | Source Code | 0 0
  1. import React, { useState, useEffect } from 'react';
  2. import { Search, MapPin, Image as ImageIcon } from 'lucide-react';
  3.  
  4. const BangumiIds = {
  5.   "負けヒロインが多すぎる!": "464376",
  6.   "ゆるキャン△": "207195",
  7. };
  8.  
  9. const AnimeLocationViewer = () => {
  10.   const [bangumiId, setBangumiId] = useState("");
  11.   const [animeData, setAnimeData] = useState(null);
  12.   const [detailData, setDetailData] = useState([]);
  13.   const [loading, setLoading] = useState(false);
  14.   const [error, setError] = useState(null);
  15.   const [expandedLocations, setExpandedLocations] = useState({});
  16.  
  17.   const fetchData = async (id) => {
  18.     setLoading(true);
  19.     setError(null);
  20.     try {
  21.       const basicResponse = await fetch(`https://api.anitabi.cn/bangumi/${id}/lite`);
  22.       if (!basicResponse.ok) throw new Error('Failed to fetch basic data');
  23.       const basicData = await basicResponse.json();
  24.       setAnimeData(basicData);
  25.  
  26.       const detailResponse = await fetch(`https://api.anitabi.cn/bangumi/${id}/points/detail`);
  27.       if (!detailResponse.ok) throw new Error('Failed to fetch detailed data');
  28.       const detailData = await detailResponse.json();
  29.  
  30.       detailData.sort((a, b) => {
  31.         if (a.ep === "OP") return -1;
  32.         if (b.ep === "OP") return 1;
  33.  
  34.         const epA = parseInt(a.ep);
  35.         const epB = parseInt(b.ep);
  36.  
  37.         if (isNaN(epA) || epA > 12) return 1;
  38.         if (isNaN(epB) || epB > 12) return "-";
  39.  
  40.         return epA - epB;
  41.       });
  42.  
  43.       setDetailData(detailData);
  44.     } catch (err) {
  45.       setError('Failed to load anime location data. Please try again.');
  46.     }
  47.     setLoading(false);
  48.   };
  49.  
  50.   useEffect(() => {
  51.     if (bangumiId) {
  52.       fetchData(bangumiId);
  53.     }
  54.   }, [bangumiId]);
  55.  
  56.   const AnimeSelect = (event) => {
  57.     setBangumiId(event.target.value);
  58.   };
  59.  
  60.   const toggleExpandLocation = (locationId) => {
  61.     setExpandedLocations((prevState) => ({
  62.       ...prevState,
  63.       [locationId]: !prevState[locationId]
  64.     }));
  65.   };
  66.  
  67.   if (loading) {
  68.     return (
  69.       <div className="flex items-center justify-center min-h-screen">
  70.         <div className="text-lg">Loading...</div>
  71.       </div>
  72.     );
  73.   }
  74.  
  75.   return (
  76.     <div className="min-h-screen bg-black p-4">
  77.       <div className="max-w-6xl mx-auto">
  78.         <div className="mb-6">
  79.           <h1 className="text-3xl font-bold mb-4 text-white">Anime Location Viewer</h1>
  80.  
  81.           <div className="flex gap-4 mb-4">
  82.             <select
  83.               className="px-4 py-2 border rounded-lg bg-white"
  84.               value={BangumiIds}
  85.               onChange={AnimeSelect}
  86.             >
  87.               <option value="">Please select anime you want to view the location</option>
  88.               {Object.entries(BangumiIds).map(([name, id]) => (
  89.                 <option key={id} value={id}>{name}</option>
  90.               ))}
  91.             </select>
  92.            
  93.             <button
  94.               onClick={() => bangumiId && fetchData(bangumiId)}
  95.               className="bg-blue-500 text-white px-4 py-2 rounded-lg flex items-center gap-2 hover:bg-blue-600 transition-colors"
  96.               disabled={!bangumiId}
  97.             >
  98.               <Search size={20} />
  99.               Load Locations
  100.             </button>
  101.           </div>
  102.         </div>
  103.  
  104.         {error && (
  105.           <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
  106.             {error}
  107.           </div>
  108.         )}
  109.  
  110.         {animeData && (
  111.           <div className="space-y-6">
  112.             <div className="bg-white rounded-lg shadow p-6">
  113.               <div className="flex items-center gap-4">
  114.                 <img
  115.                   src={animeData.cover}
  116.                   alt={animeData.title}
  117.                   className="w-[10vh] h-[50] object-cover rounded"
  118.                 />
  119.                 <div>
  120.                   <div className="text-2xl font-bold">{animeData.title}</div>
  121.                   <div className="text-gray-500">{animeData.city || 'N/A'}</div>
  122.                 </div>
  123.               </div>
  124.               <div className="grid grid-cols-3 gap-4 text-sm mt-4">
  125.                 <div>
  126.                   <span className="font-semibold">City:</span> {animeData.city || 'N/A'}
  127.                 </div>
  128.               </div>
  129.             </div>
  130.  
  131.             <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  132.               {detailData.map((point) => (
  133.                 <div key={point.id} className="bg-white rounded-lg shadow overflow-hidden">
  134.                   <div className="relative h-48">
  135.                     <img
  136.                       src={point.image ? point.image.replace('h160', 'h360') : 'https://via.placeholder.com/360'}
  137.                       alt={point.name}
  138.                       className="w-full h-full object-cover"
  139.                     />
  140.                   </div>
  141.                   <div className="p-4">
  142.                     <h3 className="font-semibold mb-2">{point.name}</h3>
  143.                     {point.origin && (
  144.                       <a
  145.                         href={point.originURL}
  146.                         target="_blank"
  147.                         rel="noopener noreferrer"
  148.                         className="text-sm text-blue-500 hover:text-blue-600 mb-2 block"
  149.                       >
  150.                         Source: {point.origin}
  151.                       </a>
  152.                     )}
  153.                     <div className="flex items-center gap-4 text-sm text-gray-500">
  154.                       <div className="flex items-center gap-1">
  155.                         <ImageIcon size={16} />
  156.                         Episode {point.ep || '-'}
  157.                       </div>
  158.                       <div className="flex items-center gap-1">
  159.                         <MapPin size={16} />
  160.                         {point.geo.map(coord => coord.toFixed(4)).join(', ')}
  161.                       </div>
  162.                     </div>
  163.  
  164.                     {point.moreImages && point.moreImages.length > 0 && (
  165.                       <button
  166.                         onClick={() => toggleExpandLocation(point.id)}
  167.                         className="text-blue-500 mt-2"
  168.                       >
  169.                         {expandedLocations[point.id] ? 'Hide Images' : 'View More Images'}
  170.                       </button>
  171.                     )}
  172.                     {expandedLocations[point.id] && point.moreImages && (
  173.                       <div className="grid grid-cols-2 gap-2 mt-2">
  174.                         {point.moreImages.map((image, idx) => (
  175.                           <img
  176.                             key={idx}
  177.                             src={image.replace('h160', 'h360')}
  178.                             alt={`Additional view ${idx + 1}`}
  179.                             className="w-full h-24 object-cover"
  180.                           />
  181.                         ))}
  182.                       </div>
  183.                     )}
  184.                   </div>
  185.                 </div>
  186.               ))}
  187.             </div>
  188.           </div>
  189.         )}
  190.       </div>
  191.     </div>
  192.   );
  193. };
  194.  
  195. export default AnimeLocationViewer;
  196.  
Tags: Code
Advertisement
Add Comment
Please, Sign In to add comment