Fixed UI issues; added footer and controls

This commit is contained in:
tyler 2023-12-18 16:15:55 -05:00
parent f1c4759e0d
commit 7b83b321fe
30 changed files with 821 additions and 223 deletions

23
app.go
View file

@ -5,7 +5,6 @@ import (
"fmt"
"github.com/tylertravisty/go-utils/random"
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
)
// App struct
@ -35,15 +34,15 @@ func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s, It's show time!", random)
}
func (a *App) QueryAPI(url string) (*rumblelivestreamlib.Followers, error) {
fmt.Println("QueryAPI")
client := rumblelivestreamlib.Client{StreamKey: url}
resp, err := client.Request()
if err != nil {
// TODO: log error
fmt.Println("client.Request err:", err)
return nil, fmt.Errorf("API request failed")
}
// func (a *App) QueryAPI(url string) (*rumblelivestreamlib.Followers, error) {
// fmt.Println("QueryAPI")
// client := rumblelivestreamlib.Client{StreamKey: url}
// resp, err := client.Request()
// if err != nil {
// // TODO: log error
// fmt.Println("client.Request err:", err)
// return nil, fmt.Errorf("API request failed")
// }
return &resp.Followers, nil
}
// return &resp.Followers, nil
// }

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,5 +1,21 @@
import eye from './eye.png';
import eye_slash from './eye-slash.png';
import gear from './gear.png';
import heart from './heart-fill.png';
import house from './house.png';
import pause from './pause-fill.png';
import play from './play-fill.png';
import star from './star-fill.png';
import thumbs_down from './hand-thumbs-down.png';
import thumbs_up from './hand-thumbs-up.png';
export const Eye = eye;
export const EyeSlash = eye_slash;
export const Gear = gear;
export const Heart = heart;
export const House = house;
export const Pause = pause;
export const Play = play;
export const Star = star;
export const ThumbsDown = thumbs_down;
export const ThumbsUp = thumbs_up;

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,24 @@
.highlight {
align-items: start;
color: white;
display: flex;
background-color: #75a54b;
border-radius: 0.5rem;
flex-direction: column;
font-family: sans-serif;
font-weight: bold;
height: 40px;
justify-content: center;
min-width: 90px;
padding: 5px 10px;
width: 75px;
}
.highlight-value {
font-family: monospace;
font-size: 20px;
}
.highlight-description {
font-size: 12px;
}

View file

@ -0,0 +1,74 @@
import './Highlight.css';
function Highlight(props) {
const countString = () => {
switch (true) {
case props.value <= 0:
return '-';
case props.value < 1000:
return props.value;
case props.value < 1000000:
return (props.value / 1000).toFixed(3).slice(0, -2) + 'K';
case props.value < 1000000000:
return (props.value / 1000000).toFixed(6).slice(0, -5) + 'M';
default:
return 'Inf';
}
};
const stopwatchString = () => {
console.log(props.value);
if (isNaN(Date.parse(props.value))) {
return '--:--';
}
let now = new Date();
let date = new Date(props.value);
console.log(date);
let diff = now - date;
let msMinute = 1000 * 60;
let msHour = msMinute * 60;
let msDay = msHour * 24;
let days = Math.floor(diff / msDay);
let hours = Math.floor((diff - days * msDay) / msHour);
let minutes = Math.floor((diff - days * msDay - hours * msHour) / msMinute);
if (diff >= 100 * msDay) {
return days + 'd';
}
if (diff >= msDay) {
return days + 'd ' + hours + 'h';
}
if (hours < 10) {
hours = '0' + hours;
}
if (minutes < 10) {
minutes = '0' + minutes;
}
return hours + ':' + minutes;
};
const valueString = () => {
switch (props.type) {
case 'count':
return countString();
case 'stopwatch':
return stopwatchString();
default:
return props.value;
}
};
return (
<div className='highlight'>
<span className='highlight-value'>{valueString()}</span>
<span className='highlight-description'>{props.description}</span>
</div>
);
}
export default Highlight;

View file

@ -0,0 +1,24 @@
.stream-activity {
width: 100%;
height: 100%;
}
.stream-activity-header {
text-align: left;
background-color: rgba(6,23,38,1);
border-bottom: 1px solid #495a6a;
height: 19px;
padding: 10px 20px;
}
.stream-activity-title {
color: white;
font-family: sans-serif;
font-size: 12px;
font-weight: bold;
}
.stream-activity-list {
overflow-y: auto;
height: calc(100vh - 84px - 40px - 179px);
}

View file

@ -0,0 +1,20 @@
import StreamEvent from './StreamEvent';
import './StreamActivity.css';
function StreamActivity(props) {
return (
<div className='stream-activity'>
<div className='stream-activity-header'>
<span className='stream-activity-title'>{props.title}</span>
</div>
<div className='stream-activity-list'>
{props.events.map((event, index) => (
<StreamEvent event={event} />
))}
</div>
</div>
);
}
export default StreamActivity;

View file

@ -0,0 +1,19 @@
.stream-chat {
width: 100%;
height: 100%;
}
.stream-chat-header {
text-align: left;
background-color: rgba(6,23,38,1);
border-bottom: 1px solid #495a6a;
height: 19px;
padding: 10px 20px;
}
.stream-chat-title {
color: white;
font-family: sans-serif;
font-size: 12px;
font-weight: bold;
}

View file

@ -0,0 +1,13 @@
import './StreamChat.css';
function StreamChat(props) {
return (
<div className='stream-chat'>
<div className='stream-chat-header'>
<span className='stream-chat-title'>{props.title}</span>
</div>
</div>
);
}
export default StreamChat;

View file

@ -0,0 +1,42 @@
.stream-event {
border-bottom: 1px solid #82b1ff;
color: white;
display: flex;
flex-direction: row;
font-family: sans-serif;
justify-content: space-between;
padding: 10px 20px;
}
.stream-event-left {
align-items: center;
display: flex;
flex-direction: row;
}
.stream-event-icon {
width: 20px;
height: 20px;
padding-right: 10px;
}
.stream-event-left-text {
display: flex;
flex-direction: column;
}
.stream-event-username {
font-size: 14px;
font-weight: bold;
}
.stream-event-description {
color: #88a0b8;
font-size: 14px;
}
.stream-event-date {
align-items: center;
display: flex;
font-family: monospace;
}

View file

@ -0,0 +1,108 @@
import { Heart, Star } from '../assets/icons';
import './StreamEvent.css';
function StreamEvent(props) {
const dateDate = (date) => {
const options = { month: 'short' };
let month = new Intl.DateTimeFormat('en-US', options).format(date);
let day = date.getDay();
return month + ' ' + day;
};
const dateDay = (date) => {
let now = new Date();
let today = now.getDay();
switch (date.getDay()) {
case 0:
return 'Sunday';
case 1:
return 'Monday';
case 2:
return 'Tuesday';
case 3:
return 'Wednesday';
case 4:
return 'Thursday';
case 5:
return 'Friday';
case 6:
return 'Saturday';
}
};
const dateTime = (date) => {
let now = new Date();
let today = now.getDay();
let day = date.getDay();
if (today !== day) {
return dateDay(date);
}
let hours24 = date.getHours();
let hours = hours24 % 12 || 12;
let minutes = date.getMinutes();
let mer = 'pm';
if (hours24 < 12) {
mer = 'am';
}
return hours + ':' + minutes + ' ' + mer;
};
const dateString = (d) => {
if (isNaN(Date.parse(d))) {
return 'Who knows?';
}
let now = new Date();
let date = new Date(d);
// Fix Rumble's timezone problem
date.setHours(date.getHours() - 4);
let diff = now - date;
switch (true) {
case diff < 0:
return 'In the future!?';
case diff < 60000:
return 'Now';
case diff < 3600000:
let minutes = Math.floor(diff / 1000 / 60);
let postfix = ' minutes ago';
if (minutes == 1) {
postfix = ' minute ago';
}
return minutes + postfix;
case diff < 86400000:
return dateTime(date);
case diff < 604800000:
return dateDay(date);
default:
return dateDate(date);
}
};
return (
<div className='stream-event'>
<div className='stream-event-left'>
{props.event.followed_on && <img className='stream-event-icon' src={Heart}></img>}
{props.event.subscribed_on && <img className='stream-event-icon' src={Star}></img>}
<div className='stream-event-left-text'>
<span className='stream-event-username'>{props.event.username}</span>
<span className='stream-event-description'>
{props.event.followed_on && 'Followed you'}
{props.event.subscribed_on && 'Subscribed'}
</span>
</div>
</div>
<span className='stream-event-date'>
{props.event.followed_on && dateString(props.event.followed_on)}
{props.event.subscribed_on && dateString(props.event.subscribed_on)}
</span>
</div>
);
}
export default StreamEvent;

View file

@ -0,0 +1,120 @@
.stream-info {
display: flex;
flex-direction: column;
/* padding: 20px 0px; */
width: 100%;
}
.stream-info-title {
color: white;
font-family: sans-serif;
font-size: 20px;
font-weight: bold;
}
.stream-info-categories {
padding: 5px 0px;
margin-right: 50px;
}
.stream-info-category {
background-color: rgba(6,23,38,1);
border: 1px solid white;
border-radius: 30px;
color: white;
font-family: sans-serif;
margin-right: 5px;
padding: 1px 7px;
}
.stream-info-channel {
color: white;
font-family: sans-serif;
font-size: 16px;
font-weight: bold;
padding: 5px 20px;
}
.stream-info-controls {
align-items: center;
border: 1px solid white;
border-radius: 5px;
display: flex;
justify-content: center;
margin: 5px 0px 20px 0px;
}
.stream-info-control {
height: 32px;
padding: 5px;
width: 32px;
}
.stream-info-control-button {
background-color: #000312;
border-radius: 5px;
cursor: pointer;
border: none;
padding: 0px;
}
.stream-info-control-button:hover {
background-color: rgba(6,23,38,1);
}
.stream-info-footer {
align-items: center;
display: flex;
justify-content: center;
}
.stream-info-likes {
align-items: center;
display: flex;
flex-direction: row;
padding: 5px 0px;
}
.stream-info-likes-count {
color: white;
font-family: monospace;
font-size: 16px;
padding: 0px 5px;
}
.stream-info-likes-left {
align-items: center;
display: flex;
flex-direction: row;
background-color: rgba(6,23,38,1);
border: 1px solid white;
border-radius: 10rem 0rem 0rem 10rem;
margin-right: 1px;
}
.stream-info-likes-right {
align-items: center;
display: flex;
flex-direction: row;
background-color: rgba(6,23,38,1);
border: 1px solid white;
border-radius: 0rem 10rem 10rem 0rem;
}
.stream-info-likes-icon {
align-items: center;
display: flex;
flex-direction: row;
height: 16px;
padding: 5px;
width: 16px;
}
.stream-info-live {
padding: 10px 20px 5px 20px;
}
.stream-info-subtitle {
align-items: center;
display: flex;
flex-direction: row;
}

View file

@ -0,0 +1,77 @@
import { Gear, House, Pause, Play, ThumbsDown, ThumbsUp } from '../assets/icons';
import './StreamInfo.css';
function StreamInfo(props) {
const likesString = (likes) => {
switch (true) {
case likes <= 0:
return '0';
case likes < 1000:
return likes;
case likes < 1000000:
return (likes / 1000).toFixed(3).slice(0, -2) + 'K';
case likes < 1000000000:
return (likes / 1000000).toFixed(6).slice(0, -5) + 'M';
default:
return 'Inf';
}
};
return (
<div className='stream-info'>
<div className='stream-info-live'>
<div className='stream-info-title'>
<span>{props.live ? props.title : '-'}</span>
</div>
<div className='stream-info-subtitle'>
<div className='stream-info-categories'>
<span className='stream-info-category'>
{props.live ? props.categories.primary.title : 'none'}
</span>
<span className='stream-info-category'>
{props.live ? props.categories.secondary.title : 'none'}
</span>
</div>
<div className='stream-info-likes'>
<div className='stream-info-likes-left'>
<img className='stream-info-likes-icon' src={ThumbsUp} />
<span className='stream-info-likes-count'>
{props.live ? likesString(props.likes) : '-'}
</span>
</div>
<div className='stream-info-likes-right'>
<img className='stream-info-likes-icon' src={ThumbsDown} />
<span className='stream-info-likes-count'>
{props.live ? likesString(props.dislikes) : '-'}
</span>
</div>
</div>
</div>
</div>
<div className='stream-info-channel'>
<span>Channel: {props.channel}</span>
</div>
<div className='stream-info-footer'>
<div></div>
<div className='stream-info-controls'>
<button className='stream-info-control-button'>
<img className='stream-info-control' src={House} />
</button>
<button className='stream-info-control-button'>
<img
onClick={props.active ? props.pause : props.play}
className='stream-info-control'
src={props.active ? Pause : Play}
/>
</button>
<button className='stream-info-control-button'>
<img className='stream-info-control' src={Gear} />
</button>
</div>
<div></div>
</div>
</div>
);
}
export default StreamInfo;

View file

@ -1,14 +1,14 @@
import React from 'react'
import {createRoot} from 'react-dom/client'
import './style.css'
import App from './App'
import React from 'react';
import { createRoot } from 'react-dom/client';
import './style.css';
import App from './App';
const container = document.getElementById('root')
const container = document.getElementById('root');
const root = createRoot(container)
const root = createRoot(container);
root.render(
<React.StrictMode>
<App/>
</React.StrictMode>
)
// <React.StrictMode>
<App />
// </React.StrictMode>
);

View file

@ -1,82 +1,56 @@
#Dashboard {
align-items: center;
background-color: #f3f5f8;
background-color: #000312;
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
}
.followers {
background-color: #061726;
height: 100%;
.header {
align-items: center;
display: flex;
flex-direction: row;
height: 62px;
justify-content: center;
padding: 10px 0px;
width: 100%;
}
.followers-list {
overflow-y: auto;
.header-left {
width: 20%;
}
.followers-list-follower {
border-bottom: 1px solid #82b1ff;
color: white;
.header-right {
width: 20%;
}
.main {
border-bottom: 1px solid #495a6a;
border-top: 1px solid #495a6a;
display: flex;
flex-direction: row;
font-family: sans-serif;
height: calc(100vh - 83px - 179px);
justify-content: space-between;
padding: 10px 20px;
}
.followers-list-follower-username {
font-weight: bold;
}
.followers-list-follower-date {
font-family: monospace;
}
.followers-header {
align-items: center;
border-bottom: 1px solid #82b1ff;
display: flex;
flex-direction: column;
justify-content: space-evenly;
padding: 10px 20px;
}
.followers-header-title {
color: white;
font-family: sans-serif;
font-size: 24px;
font-weight: bold;
padding-bottom: 10px;
text-transform: uppercase;
}
.followers-header-highlights {
display: flex;
flex-direction: row;
justify-content: space-evenly;
width: 100%;
}
.followers-header-highlight {
.main-left {
border-right: 1px solid #495a6a;
width: 30%;
height: 100%;
}
.main-right {
width: 70%;
height: 100%;
}
.highlights {
align-items: center;
color: white;
display: flex;
background-color: #75a54b;
border-radius: 0.5rem;
flex-direction: column;
font-family: sans-serif;
font-weight: bold;
min-width: 50px;
padding: 10px;
flex-direction: row;
justify-content: space-evenly;
height: 50px;
width: 60%;
}
.followers-header-highlight-count {
font-size: 16px;
}
.followers-header-highlight-description {
font-size: 12px;
}

View file

@ -1,162 +1,132 @@
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { QueryAPI } from '../../wailsjs/go/main/App';
import { Start, Stop } from '../../wailsjs/go/api/Api';
import './Dashboard.css';
import { EventsEmit, EventsOn } from '../../wailsjs/runtime/runtime';
import { Heart, Star } from '../assets/icons';
import Highlight from '../components/Highlight';
import StreamEvent from '../components/StreamEvent';
import StreamActivity from '../components/StreamActivity';
import StreamChat from '../components/StreamChat';
import StreamInfo from '../components/StreamInfo';
function Dashboard() {
const location = useLocation();
const [refresh, setRefresh] = useState(false);
const [active, setActive] = useState(false);
const [streamKey, setStreamKey] = useState(location.state.streamKey);
const [channelName, setChannelName] = useState('');
const [followers, setFollowers] = useState({});
const [totalFollowers, setTotalFollowers] = useState('-');
const [channelFollowers, setChannelFollowers] = useState('-');
const [latestFollower, setLatestFollower] = useState('-');
const [totalFollowers, setTotalFollowers] = useState(0);
const [channelFollowers, setChannelFollowers] = useState(0);
const [recentFollowers, setRecentFollowers] = useState([]);
// useEffect(() => {
// QueryAPI(streamKey)
// .then((response) => {
// console.log(response);
// setFollowers(response);
// setChannelFollowers(response.num_followers);
// setTotalFollowers(response.num_followers_total);
// setLatestFollower(response.latest_follower.username);
// setRecentFollowers(response.recent_followers);
// })
// .catch((e) => console.log('Error:', e));
// }, []);
const [subscribers, setSubscribers] = useState({});
const [subscriberCount, setSubscriberCount] = useState(0);
const [recentSubscribers, setRecentSubscribers] = useState([]);
const [streamCategories, setStreamCategories] = useState({
primary: { title: '' },
secondary: { title: '' },
});
const [streamLikes, setStreamLikes] = useState(0);
const [streamLive, setStreamLive] = useState(false);
const [streamDislikes, setStreamDislikes] = useState(0);
const [streamTitle, setStreamTitle] = useState('');
const [watchingNow, setWatchingNow] = useState(0);
const [createdOn, setCreatedOn] = useState('');
useEffect(() => {
let interval = setInterval(() => {
console.log('Query API');
QueryAPI(streamKey)
.then((response) => {
console.log(response);
setFollowers(response);
setChannelFollowers(response.num_followers);
setTotalFollowers(response.num_followers_total);
setLatestFollower(response.latest_follower.username);
setRecentFollowers(response.recent_followers);
})
.catch((e) => console.log('Error:', e));
}, 10000);
console.log('use effect start');
Start(streamKey);
setActive(true);
return () => {
clearInterval(interval);
};
EventsOn('QueryResponse', (response) => {
console.log('query response received');
setRefresh(!refresh);
setActive(true);
setChannelName(response.channel_name);
setFollowers(response.followers);
setChannelFollowers(response.followers.num_followers);
setTotalFollowers(response.followers.num_followers_total);
setRecentFollowers(response.followers.recent_followers);
setSubscribers(response.subscribers);
setSubscriberCount(response.subscribers.num_subscribers);
setRecentSubscribers(response.subscribers.recent_subscribers);
if (response.livestreams.length > 0) {
setStreamLive(true);
setStreamCategories(response.livestreams[0].categories);
setStreamLikes(response.livestreams[0].likes);
setStreamDislikes(response.livestreams[0].dislikes);
setStreamTitle(response.livestreams[0].title);
setCreatedOn(response.livestreams[0].created_on);
setWatchingNow(response.livestreams[0].watching_now);
} else {
setStreamLive(false);
}
});
}, []);
const dateDate = (date) => {
const options = { month: 'short' };
let month = new Intl.DateTimeFormat('en-US', options).format(date);
let day = date.getDay();
return month + ' ' + day;
const startQuery = () => {
console.log('start');
Start(streamKey);
setActive(true);
};
const dateDay = (date) => {
let now = new Date();
let today = now.getDay();
switch (date.getDay()) {
case 0:
return 'Sunday';
case 1:
return 'Monday';
case 2:
return 'Tuesday';
case 3:
return 'Wednesday';
case 4:
return 'Thursday';
case 5:
return 'Friday';
case 6:
return 'Saturday';
const stopQuery = () => {
console.log('stop');
Stop();
// EventsEmit('StopQuery');
setActive(false);
};
const activityDate = (activity) => {
if (activity.followed_on) {
return activity.followed_on;
}
if (activity.subscribed_on) {
return activity.subscribed_on;
}
};
const dateTime = (date) => {
let now = new Date();
let today = now.getDay();
let day = date.getDay();
if (today !== day) {
return dateDay(date);
}
let hours24 = date.getHours();
let hours = hours24 % 12 || 12;
let minutes = date.getMinutes();
let mer = 'pm';
if (hours24 < 12) {
mer = 'am';
}
return hours + ':' + minutes + ' ' + mer;
};
const dateString = (d) => {
let now = new Date();
let date = new Date(d);
// Fix Rumble's timezone problem
date.setHours(date.getHours() - 4);
let diff = now - date;
switch (true) {
case diff < 60000:
return 'Now';
case diff < 3600000:
let minutes = Math.floor(diff / 1000 / 60);
let postfix = ' minutes ago';
if (minutes == 1) {
postfix = ' minute ago';
}
return minutes + postfix;
case diff < 86400000:
return dateTime(date);
case diff < 604800000:
return dateDay(date);
default:
return dateDate(date);
}
console.log('Diff:', diff);
return d;
const activityEvents = () => {
let sorted = [...recentFollowers, ...recentSubscribers].sort((a, b) =>
activityDate(a) < activityDate(b) ? 1 : -1
);
return sorted;
};
return (
<div id='Dashboard'>
<span>Dashboard:</span>
<div className='followers'>
<div className='followers-header'>
<span className='followers-header-title'>Followers</span>
<div className='followers-header-highlights'>
<div className='followers-header-highlight'>
<span className='followers-header-highlight-count'>
{channelFollowers}
</span>
<span className='followers-header-highlight-description'>Channel</span>
</div>
<div className='followers-header-highlight'>
<span className='followers-header-highlight-count'>
{totalFollowers}
</span>
<span className='followers-header-highlight-description'>Total</span>
</div>
</div>
</div>
<div className='followers-list'>
{recentFollowers.map((follower, index) => (
<div className='followers-list-follower'>
<span className='followers-list-follower-username'>
{follower.username}
</span>
<span className='followers-list-follower-date'>
{dateString(follower.followed_on)}
</span>
</div>
))}
<div className='header'>
<div className='header-left'></div>
<div className='highlights'>
{/* <Highlight description={'Session'} type={'stopwatch'} value={createdOn} /> */}
<Highlight description={'Viewers'} type={'count'} value={watchingNow} />
<Highlight description={'Followers'} type={'count'} value={channelFollowers} />
<Highlight description={'Subscribers'} type={'count'} value={subscriberCount} />
</div>
<div className='header-right'></div>
</div>
<div className='main'>
<div className='main-left'>
<StreamActivity title={'Stream Activity'} events={activityEvents()} />
</div>
<div className='main-right'>
<StreamChat title={'Stream Chat'} />
</div>
<div></div>
</div>
<StreamInfo
active={active}
channel={channelName}
title={streamTitle}
categories={streamCategories}
likes={streamLikes}
live={streamLive}
dislikes={streamDislikes}
play={startQuery}
pause={stopQuery}
/>
</div>
);
}

View file

@ -6,5 +6,5 @@ body {
}
#app {
height: 100vh;
/* height: 100vh; */
}

4
go.mod
View file

@ -4,7 +4,7 @@ go 1.19
require (
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20231213162428-b33f413975bb
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20231218182551-5ac1d6c01910
github.com/wailsapp/wails/v2 v2.7.1
)
@ -36,7 +36,7 @@ require (
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
)

8
go.sum
View file

@ -55,8 +55,8 @@ github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQ
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 h1:xrjIFqzGQXlCrCdMPpW6+SodGFSlrQ3ZNUCr3f5tF1g=
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909/go.mod h1:2W31Jhs9YSy7y500wsCOW0bcamGi9foQV1CKrfvfTxk=
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20231213162428-b33f413975bb h1:nqq0eTr8ocCaENdoryAt9T3g5xJgwcXMW7pYAztb1lc=
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20231213162428-b33f413975bb/go.mod h1:YrfW5N6xVozOzubzfNNsy+v0MIL2GPi9Kx3mTZ/Q9zI=
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20231218182551-5ac1d6c01910 h1:pu5jBae9XZDF/G8YkCN7D5TMxOCsCO5+Jn1/lNPsOUY=
github.com/tylertravisty/rumble-livestream-lib-go v0.0.0-20231218182551-5ac1d6c01910/go.mod h1:YrfW5N6xVozOzubzfNNsy+v0MIL2GPi9Kx3mTZ/Q9zI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
@ -89,8 +89,8 @@ golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=

111
internal/api/api.go Normal file
View file

@ -0,0 +1,111 @@
package api
import (
"context"
"fmt"
"sync"
"time"
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
type Api struct {
ctx context.Context
cancel context.CancelFunc
cancelMu sync.Mutex
querying bool
queryingMu sync.Mutex
queryInterval time.Duration
queryIntervalMu sync.Mutex
}
func NewApi() *Api {
return &Api{queryInterval: 10 * time.Second}
}
func (a *Api) Startup(ctx context.Context) {
a.ctx = ctx
runtime.EventsOn(ctx, "StopQuery", func(optionalData ...interface{}) {
a.Stop()
})
}
func (a *Api) Start(url string) error {
fmt.Println("Api.Start")
if url == "" {
return fmt.Errorf("empty stream key")
}
a.queryingMu.Lock()
start := !a.querying
a.querying = true
a.queryingMu.Unlock()
if start {
fmt.Println("Starting querying")
ctx, cancel := context.WithCancel(context.Background())
a.cancelMu.Lock()
a.cancel = cancel
a.cancelMu.Unlock()
a.start(ctx, url)
} else {
fmt.Println("Querying already started")
}
return nil
}
func (a *Api) Stop() {
fmt.Println("stop querying")
a.cancelMu.Lock()
if a.cancel != nil {
a.cancel()
}
a.cancelMu.Unlock()
}
func (a *Api) start(ctx context.Context, url string) {
for {
a.query(url)
a.queryIntervalMu.Lock()
interval := a.queryInterval
a.queryIntervalMu.Unlock()
timer := time.NewTimer(interval)
select {
case <-ctx.Done():
a.queryingMu.Lock()
a.querying = false
a.queryingMu.Unlock()
timer.Stop()
return
case <-timer.C:
}
}
}
func (a *Api) query(url string) {
fmt.Println("QueryAPI")
client := rumblelivestreamlib.Client{StreamKey: url}
resp, err := client.Request()
if err != nil {
// TODO: log error
fmt.Println("client.Request err:", err)
// a.Stop()
}
// resp := &rumblelivestreamlib.LivestreamResponse{}
// resp.Followers.RecentFollowers = append(resp.Followers.RecentFollowers, rumblelivestreamlib.Follower{"tyler-follow", "2023-12-12T21:53:34-04:00"})
// resp.Subscribers.RecentSubscribers = append(resp.Subscribers.RecentSubscribers, rumblelivestreamlib.Subscriber{"tyler-sub", "tyler-sub", 500, 5, "2023-12-14T21:53:34-04:00"})
// resp.Subscribers.RecentSubscribers = append(resp.Subscribers.RecentSubscribers, rumblelivestreamlib.Subscriber{"tyler-sub", "tyler-sub", 500, 5, "2023-12-13T21:53:34-04:00"})
// resp.Subscribers.RecentSubscribers = append(resp.Subscribers.RecentSubscribers, rumblelivestreamlib.Subscriber{"tyler-sub", "tyler-sub", 500, 5, "2023-11-13T21:53:34-04:00"})
// resp.Livestreams = []rumblelivestreamlib.Livestream{
// {
// CreatedOn: "2023-12-16T16:13:30+00:00",
// WatchingNow: 4},
// }
runtime.EventsEmit(a.ctx, "QueryResponse", &resp)
}
// TODO: if start errors, send event

View file

@ -1,8 +1,10 @@
package main
import (
"context"
"embed"
"github.com/tylertravisty/rum-goggles/internal/api"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
@ -13,6 +15,7 @@ var assets embed.FS
func main() {
// Create an instance of the app structure
api := api.NewApi()
app := NewApp()
// Create application with options
@ -24,9 +27,13 @@ func main() {
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
OnStartup: app.startup,
OnStartup: func(ctx context.Context) {
app.startup(ctx)
api.Startup(ctx)
},
Bind: []interface{}{
app,
api,
},
})