Skip to content

Commit 19645a0

Browse files
committed
viewAll page & logic for adding any live VT to customList
1 parent 53c9334 commit 19645a0

File tree

6 files changed

+176
-37
lines changed

6 files changed

+176
-37
lines changed

src/Components/DisplayMyLivers.tsx

+5-20
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { database } from '@/database';
22
import axios from 'axios';
3-
import moment from 'moment';
4-
import React, { useEffect, useMemo, useState } from 'react';
3+
import React, { useEffect, useState } from 'react';
54
import { DisplayedLiver } from './DisplayedLiver';
5+
import { databaseSearch, flattenedDatabase } from './SelectLiver';
6+
//TODO convert all format -> new format of myLivers
67

78
export interface vtuberInfo {
89
name: string;
@@ -23,20 +24,6 @@ interface vtubersFromDB {
2324
[key: string]: vtuberInfo;
2425
}
2526

26-
interface Members {
27-
name: string;
28-
imageURL: string;
29-
channelID: string;
30-
retired: boolean;
31-
twitter: string;
32-
}
33-
34-
interface Branch {
35-
branchID: string;
36-
debut: string;
37-
members: Members[];
38-
}
39-
4027
export default function DisplayMyLivers({
4128
showStreamTitle,
4229
setShowStreamTitle,
@@ -48,10 +35,6 @@ export default function DisplayMyLivers({
4835
const [loading, setLoading] = useState(true);
4936
const [APIResponse, setAPIResponse] = useState<any>([]);
5037

51-
const flattenedDatabase = Object.values(database).flatMap((group: any) =>
52-
group.flatMap((branch: any) => branch.members)
53-
);
54-
5538
useEffect(() => {
5639
getMyLivers();
5740
getAPIData();
@@ -77,6 +60,7 @@ export default function DisplayMyLivers({
7760
};
7861
try {
7962
const response = await axios.get(url, config);
63+
console.log(response);
8064
setAPIResponse(response.data);
8165
} catch (error) {
8266
console.log(error);
@@ -130,6 +114,7 @@ export default function DisplayMyLivers({
130114
<DisplayedLiver
131115
member={displayLivers[channelID]}
132116
loading={loading}
117+
path="Home"
133118
/>
134119
</div>
135120
);

src/Components/DisplayedLiver.tsx

+88-6
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,88 @@
1+
import axios from 'axios';
12
import React, { useEffect, useState } from 'react';
23
import ClipLoader from 'react-spinners/ClipLoader';
34
import { vtuberInfo } from './DisplayMyLivers';
45
import LiveStatus from './DisplayedLiver/LiveStatus';
56

7+
import { BookmarkIcon, BookmarkSlashIcon } from '@heroicons/react/24/solid';
68
import { AiFillTwitterCircle } from 'react-icons/ai';
79

810
export function DisplayedLiver({
911
member,
1012
loading,
13+
path,
1114
}: {
12-
member: vtuberInfo;
15+
member: any;
1316
loading: boolean;
17+
path: string;
1418
}) {
1519
const [showLiveStatus, setShowLiveStatus] = useState(false);
20+
const [customList, setCustomList] = useState<any>({});
21+
const [fetchingVTuber, setFetchingVTuber] = useState(false);
22+
23+
function checkMyLivers() {
24+
chrome.storage.local.get(['myLivers', 'customList'], function (data) {
25+
setCustomList(data.customList);
26+
});
27+
}
28+
29+
useEffect(() => {
30+
if (path === 'viewAll') checkMyLivers();
31+
}, []);
32+
33+
const deleteFromMyLivers = (channelID: string) => {
34+
let tempCustomList = customList;
35+
delete tempCustomList[channelID];
36+
chrome.storage.local.set({ customList: tempCustomList });
37+
setCustomList(tempCustomList);
38+
};
39+
40+
const addToMyLivers = async (channelID: string) => {
41+
let vtuberTwitter = '';
42+
setFetchingVTuber(true);
43+
const config = {
44+
url: `https://holodex.net/api/v2/channels/${channelID}`,
45+
headers: {
46+
'X-APIKEY': process.env.NEXT_PUBLIC_HOLODEX,
47+
'Content-Type': 'application/json',
48+
},
49+
};
50+
try {
51+
const { data } = await axios.request(config);
52+
vtuberTwitter = data.twitter;
53+
setFetchingVTuber(false);
54+
} catch (error) {
55+
console.log(error);
56+
}
57+
58+
let tempCustomList = customList;
59+
60+
tempCustomList = {
61+
...tempCustomList,
62+
[channelID]: {
63+
name: member.channel.english_name,
64+
imageURL: member.channel.photo,
65+
channelID: member.channel.id,
66+
twitter: vtuberTwitter,
67+
status: member.status,
68+
title: member.title,
69+
started: member.start_actual,
70+
org: member.channel.org,
71+
viewers: member.live_viewers,
72+
},
73+
};
74+
chrome.storage.local.set({ customList: tempCustomList });
75+
setCustomList(tempCustomList);
76+
};
1677

1778
function handleTwitter(twitterID: string) {
1879
const url = 'https://twitter.com/';
1980
chrome.tabs.create({ url: url + twitterID });
2081
}
2182

22-
const url = `https://youtube.com/channel/${member.channelID}/live`;
83+
const url = `https://youtube.com/channel/${
84+
member.channelID || member.channel.id
85+
}/live`;
2386
const isLive = member.status === 'live';
2487

2588
return (
@@ -35,8 +98,8 @@ export function DisplayedLiver({
3598
}}
3699
>
37100
<img
38-
src={member.imageURL}
39-
alt={member.name}
101+
src={member.imageURL || member.channel.photo}
102+
alt={member.name || member.channel.english_name}
40103
className={`rounded-full h-20 liver border-4 ${
41104
isLive
42105
? 'border-red-500'
@@ -46,7 +109,7 @@ export function DisplayedLiver({
46109
chrome.tabs.create({ url: url });
47110
}}
48111
/>
49-
{!loading && (
112+
{!loading && member.twitter && (
50113
<div
51114
className={`${
52115
isLive
@@ -63,7 +126,26 @@ export function DisplayedLiver({
63126
></span>
64127
</div>
65128
)}
66-
129+
{path === 'viewAll' && !fetchingVTuber && (
130+
<div className="absolute left-[4.8em] bottom-[4.8em] rounded-full fade-in text-red-500">
131+
{Object.keys(customList).includes(member.channel.id) ? (
132+
<BookmarkSlashIcon
133+
onClick={() => deleteFromMyLivers(member.channel.id)}
134+
className="h-5 w-5 p-0.5 bg-white border-2 hover:text-red-300 border-red-500 rounded-full cursor-pointer"
135+
/>
136+
) : (
137+
<BookmarkIcon
138+
onClick={() => addToMyLivers(member.channel.id)}
139+
className="h-5 w-5 p-0.5 text-black hover:text-neutral-800 bg-white border-2 border-red-500 rounded-full cursor-pointer"
140+
/>
141+
)}
142+
</div>
143+
)}
144+
{fetchingVTuber && (
145+
<div className="absolute left-[4.7em] bottom-[4.6em] bg-red-500 p-1 pb-0 rounded-full fade-in">
146+
<ClipLoader size={15} color="white" />
147+
</div>
148+
)}
67149
{loading && (
68150
<div>
69151
<div className="absolute left-[4.5em] bottom-[4.5em] bg-white p-1 pb-0 rounded-full dark:bg-slate-700 fade-in">

src/Components/DisplayedLiver/LiveStatus.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,24 @@ export default function LiveStatus({ member }: { member: any }) {
1616
<div className="flex items-center justify-between">
1717
<div>
1818
<div className="flex flex-col">
19-
<span className="text-[10px] font-light">{member.org}</span>
20-
<span>{member.name}</span>
19+
<span className="text-[10px] font-light">
20+
{member.org || member.channel.org}
21+
</span>
22+
<span>{member.name || member.channel.english_name}</span>
2123
</div>
2224
</div>
2325
<div className="flex flex-col justify-end items-end">
2426
<div className="flex items-center gap-1.5">
2527
<div className="bg-red-500 h-2 w-2 rounded-full">
2628
<div className=" bg-red-500 h-2 w-2 rounded-full animate-ping"></div>
2729
</div>
28-
<span>{member.viewers}</span>
30+
<span>{member.viewers || member.live_viewers}</span>
2931
</div>
3032
<div className="flex gap-1 items-center">
3133
<ClockIcon className="h-4 w-4" />
32-
<span>{startedStreaming(member.started)}</span>
34+
<span>
35+
{startedStreaming(member.started || member.start_actual)}
36+
</span>
3337
</div>
3438
</div>
3539
</div>

src/Components/SelectLiver.tsx

+13-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ import React, { useEffect, useState } from 'react';
33

44
import { CheckCircleIcon } from '@heroicons/react/24/solid';
55

6+
export const flattenedDatabase = Object.values(database).flatMap((group: any) =>
7+
group.flatMap((branch: any) => branch.members)
8+
);
9+
10+
export function databaseSearch(memberID: string) {
11+
for (const member of flattenedDatabase) {
12+
if (member.channelID === memberID) {
13+
return member;
14+
}
15+
}
16+
return {};
17+
}
18+
619
export default function SelectLiver({
720
settingsQuery,
821
showStreamTitle,
@@ -14,10 +27,6 @@ export default function SelectLiver({
1427
}) {
1528
const [selectedLivers, setSelectedLivers] = useState({});
1629

17-
const flattenedDatabase = Object.values(database).flatMap((group: any) =>
18-
group.flatMap((branch: any) => branch.members)
19-
);
20-
2130
function databaseSearch(memberID: string) {
2231
for (const member of flattenedDatabase) {
2332
if (member.channelID === memberID) {

src/layouts/Navigation.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Tooltip from '@/Components/Navigation/Tooltip';
22
import Custom from '@/pages/Custom';
33
import Home from '@/pages/Home';
44
import Settings from '@/pages/Settings';
5+
import ViewAll from '@/pages/ViewAll';
56
import React, { useEffect, useState } from 'react';
67
import { Link } from 'react-chrome-extension-router';
78

@@ -105,7 +106,9 @@ export default function Navigation({
105106
</Link>
106107
</Tooltip>
107108
<Tooltip title="All Live VTubers">
108-
<UserGroupIcon className="h-5 w-5 cursor-pointer hover:dark:text-blue-400 hover:text-slate-500" />
109+
<Link component={ViewAll} onClick={() => setShowStreamTitle('')}>
110+
<UserGroupIcon className="h-5 w-5 cursor-pointer hover:dark:text-blue-400 hover:text-slate-500" />
111+
</Link>
109112
</Tooltip>
110113
</div>
111114

src/pages/ViewAll.tsx

+58-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,61 @@
1-
import React from 'react';
1+
import { DisplayedLiver } from '@/Components/DisplayedLiver';
2+
import axios from 'axios';
3+
import { useEffect, useState } from 'react';
24

35
export default function ViewAll() {
4-
return <div>ViewAll</div>;
6+
const [APIResponse, setAPIResponse] = useState<any>([]);
7+
const [loading, setLoading] = useState(true);
8+
9+
async function getAPIData() {
10+
const url = 'https://holodex.net/api/v2/live';
11+
const config = {
12+
headers: {
13+
'X-APIKEY': process.env.NEXT_PUBLIC_HOLODEX,
14+
'Content-Type': 'application/json',
15+
},
16+
};
17+
try {
18+
const response = await axios.get(url, config);
19+
setAPIResponse(response.data);
20+
setLoading(false);
21+
} catch (error) {
22+
console.log(error);
23+
}
24+
}
25+
26+
useEffect(() => {
27+
getAPIData();
28+
}, []);
29+
30+
return (
31+
<div className="mt-3 mb-1">
32+
{loading && (
33+
<div className="flex flex-wrap justify-center gap-3">
34+
{[...Array(20)].map((x, index) => (
35+
<div
36+
key={index}
37+
className="h-20 w-20 bg-slate-300/60 dark:bg-slate-700/80 animate-pulse rounded-full fade-in"
38+
></div>
39+
))}
40+
</div>
41+
)}
42+
{!loading && (
43+
<div className="flex flex-wrap justify-center gap-3">
44+
{APIResponse.map((channelID: any, index: number) => {
45+
if (channelID.status === 'live') {
46+
return (
47+
<div key={channelID}>
48+
<DisplayedLiver
49+
member={channelID}
50+
loading={loading}
51+
path="viewAll"
52+
/>
53+
</div>
54+
);
55+
}
56+
})}
57+
</div>
58+
)}
59+
</div>
60+
);
561
}

0 commit comments

Comments
 (0)