From 7b83b321fee5001e465964075c7790f73083e791 Mon Sep 17 00:00:00 2001 From: tyler Date: Mon, 18 Dec 2023 16:15:55 -0500 Subject: [PATCH] Fixed UI issues; added footer and controls --- app.go | 23 +- frontend/src/assets/icons/gear.png | Bin 0 -> 6770 bytes .../src/assets/icons/hand-thumbs-down.png | Bin 0 -> 4880 bytes frontend/src/assets/icons/hand-thumbs-up.png | Bin 0 -> 4886 bytes frontend/src/assets/icons/heart-fill.png | Bin 0 -> 4885 bytes frontend/src/assets/icons/house.png | Bin 0 -> 2425 bytes frontend/src/assets/icons/index.jsx | 16 ++ frontend/src/assets/icons/pause-circle.png | Bin 0 -> 5425 bytes frontend/src/assets/icons/pause-fill.png | Bin 0 -> 1867 bytes frontend/src/assets/icons/play-circle.png | Bin 0 -> 5411 bytes frontend/src/assets/icons/play-fill.png | Bin 0 -> 2343 bytes frontend/src/assets/icons/star-fill.png | Bin 0 -> 4961 bytes frontend/src/components/Highlight.css | 24 ++ frontend/src/components/Highlight.jsx | 74 ++++++ frontend/src/components/StreamActivity.css | 24 ++ frontend/src/components/StreamActivity.jsx | 20 ++ frontend/src/components/StreamChat.css | 19 ++ frontend/src/components/StreamChat.jsx | 13 + frontend/src/components/StreamEvent.css | 42 ++++ frontend/src/components/StreamEvent.jsx | 108 ++++++++ frontend/src/components/StreamInfo.css | 120 +++++++++ frontend/src/components/StreamInfo.jsx | 77 ++++++ frontend/src/main.jsx | 20 +- frontend/src/screens/Dashboard.css | 94 +++---- frontend/src/screens/Dashboard.jsx | 236 ++++++++---------- frontend/src/style.css | 2 +- go.mod | 4 +- go.sum | 8 +- internal/api/api.go | 111 ++++++++ main.go | 9 +- 30 files changed, 821 insertions(+), 223 deletions(-) create mode 100644 frontend/src/assets/icons/gear.png create mode 100644 frontend/src/assets/icons/hand-thumbs-down.png create mode 100644 frontend/src/assets/icons/hand-thumbs-up.png create mode 100644 frontend/src/assets/icons/heart-fill.png create mode 100644 frontend/src/assets/icons/house.png create mode 100644 frontend/src/assets/icons/pause-circle.png create mode 100644 frontend/src/assets/icons/pause-fill.png create mode 100644 frontend/src/assets/icons/play-circle.png create mode 100644 frontend/src/assets/icons/play-fill.png create mode 100644 frontend/src/assets/icons/star-fill.png create mode 100644 frontend/src/components/Highlight.css create mode 100644 frontend/src/components/Highlight.jsx create mode 100644 frontend/src/components/StreamActivity.css create mode 100644 frontend/src/components/StreamActivity.jsx create mode 100644 frontend/src/components/StreamChat.css create mode 100644 frontend/src/components/StreamChat.jsx create mode 100644 frontend/src/components/StreamEvent.css create mode 100644 frontend/src/components/StreamEvent.jsx create mode 100644 frontend/src/components/StreamInfo.css create mode 100644 frontend/src/components/StreamInfo.jsx create mode 100644 internal/api/api.go diff --git a/app.go b/app.go index d5d9339..cba1dda 100644 --- a/app.go +++ b/app.go @@ -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 +// } diff --git a/frontend/src/assets/icons/gear.png b/frontend/src/assets/icons/gear.png new file mode 100644 index 0000000000000000000000000000000000000000..c19249563bcb21ef09c20737f493e32a6e5b3143 GIT binary patch literal 6770 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®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 U+5&p@eE005u}1^@s6i_d2*0004SX+uL$X=7sm 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 Hu0mjf&-xx( literal 0 HcmV?d00001 diff --git a/frontend/src/assets/icons/house.png b/frontend/src/assets/icons/house.png new file mode 100644 index 0000000000000000000000000000000000000000..982d5ea472fbfac345e85ad00d72d690c60538e1 GIT binary patch literal 2425 zcmd6oYd8}O8^^b`9vfksilUmsiVzQyvY5jP6LUV4L&>4YA;)G;J(JUGY)*|FRz%L_ zFw__!!ZSI9QcjbHSQ^<@;pus=_w)OHeLwu}`+r^c^}oOTKm1ehRu{!Z4~qf-0C96O zQ`=v=fUfzNFN?cPpm|e zU6W2>EowZ+QLV>oGE3*E3Ox?NAbCRIm+07c`lr5R zoZ!EhJ})A$JEy%`tLJ%Nf~_Pqax?IfXuBc_m~VGKn|4dRbW|lw&M;#=g3EKL>yW47=(cLE1%1bHM$&VgLm=RUOdKS)f?#<``Zv^4`Z9eXxNrYuLqo%)!G;@H5aSpI&Q?!(AWMQ;ZN$Gd#Zcc$i+Y&1sc}t@fx73=-mw zOpLlctKSAgd0}HaFy>HJNvRzfvw^qnvPa+gJZ5s~;Nk6+wqh?#@UaR9H$HzX+}{^7 zySGKRUaRZrto<>5uw!wX-LTK*p=X&lCrK zhbn|Z6dJ6a5o_JqU5CGXu#a>KAF}Moxe)o*!O5Y6P&8HO_T!Y>JP-)M06?&K02m4Q zFA}F%@A~c@njabw5#hp%Q+d@cSNuA}yMlkE-5_g0`fTjw8pk^H4`gH#*tGrp+1Na< zRm!!kbEe(7Oh_`G(Odog>&9jG?M#LuNa5|(1Y!Xy=?MK=)mw1)!$+dR%wYoZ{Gv8h z5*khIy;;}Glc$e+68aXDjv=;?)@w%#7egs{DtF+k8Cm)e+sQ7#HH+)ZyAMjZz@u3x z_e*G%>T7IzivE1gr5m@0r9Ix%y)>?#KR4(*J9BQYZ&yVJ@HFohft(rKLUM%)lvCp- z!o3Nv5~YYstK+U+gG)rO_ry0%5O@J2TVvi*&&Qt2R`eOZbxBg_io35m=}cs3HGXU< zC#s|5q)zgeYL3Z=4*jlr9ZD%yOxMeWStu}(`4DM&n6i{WZf zZX1%;YuPpO@wxkn%^+zj)=vYEHRMB7^;*aCZ#Zg@Xx;)BTsgIlSWL^U_c`iT08Pk{!rYfk)HgbMSqp6(prmw2;q+6rDkEK+gVdLor@5z-&&HXTY-RiES09Gn z(x*k~ayDe~iKMI_*)3uyPH5+MM_VOy((gRs0OLtKY1b>AxW>`WT(5{>kPlGUaIV=( zs`i|gl4-l1u+2a!HA}iYbQ@ zx-xppyw}*IKZu6*I_dE~E0aoB`lh=MW*2!)o3t zdw?;h#%xJoj-$>#ZG>i7tN+L5CL+Yo;Nc4C9_XFLU-HL5jz6vyv7A{=r2fLn3rvm) ztMX}!S_G`#%Gg>68}6Z3mRf;wamuQ=IOxA_YB)4H2UuZFH}XneSidnV{G8Y%(e!x` zO07?gj|fCWX2YK!A-H`gsV3OsuWx5KH+~))f=!Yze@-iHIm>n&;5#$nE@AmDvoje1 z{u56MOi@p+ z#^{T!1&kkwh6wCkVmz$2XH>kPp$eLVo*p!J{mb6xk~F7=Pp<{d9G#ENtt<9LONRW_ zR+pXfjfH;7zH_DFxcAQG0q-+ka9Qj{!DO2YFA*WYReDf7!s2dDB??hde(OcvS)6=u zLiAf+nH#MbXlHv?ELPcLXI4!Q8lskO=G#%FFl%_Qzd7`NF>u1MjV#XHhJFk?nK#ep{3zxKUYA8v2~q+F(PiRZVZX<+cy3mJ*f|>1BNkkt~eJ2i2v$yfH~I6wAR@3 F?%#_qSY7}C literal 0 HcmV?d00001 diff --git a/frontend/src/assets/icons/index.jsx b/frontend/src/assets/icons/index.jsx index c4c8028..e506d73 100644 --- a/frontend/src/assets/icons/index.jsx +++ b/frontend/src/assets/icons/index.jsx @@ -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; diff --git a/frontend/src/assets/icons/pause-circle.png b/frontend/src/assets/icons/pause-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..686a73d04bee6345f674d758ad38451ac30cab97 GIT binary patch literal 5425 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 bKL783aDxnScX!*i00000NkvXXu0mjf?-Ohw literal 0 HcmV?d00001 diff --git a/frontend/src/assets/icons/pause-fill.png b/frontend/src/assets/icons/pause-fill.png new file mode 100644 index 0000000000000000000000000000000000000000..a54d6007894007c269624c5656ccb80242532ed3 GIT binary patch literal 1867 zcmeH{`#;kQ7{|Y3#42V=URoguFU(0aVy3eel52E9TG2FX%o&j#wn`y~Mde;nsAM=N zw`MbiZYnXxnaf;WaWG6~t6`2h{R{nee)v4k^V{=!J@3!+N%i)0*$B~t006Mj&Gmrq zTEF*AYN~5Bts~4506=5{2IEZ#2>}4p%$Q7$dyBK~%c8nNSaX#aXw++OZ1L4{gN%S= z1ojXU*JJ!_7xPBRNY;)ve{z(mT?!&`qhcKkQVF?yPi;rUY@(&_DAl7FD{w%}F-&Ca z0o1xLNA~RzRodZlH9B@`cZElIK$?t_K>0^@W@0v=Rn?uV(ndV9az6>XQ|V{7(Q+7vffp9H(8n_%hJ z{Km&QlkMZ%-tAvno>|$|?W6T$qSVbYuWc^3}CuOqk7-E?7uu8+; z0tM`ojm#sy_Axf&l30U$k=K1oc{qHFZem?1qPG;VZ-@blL0TdpQ+)t5FJY zX#HgBEWO1DS13(JDu8{(=ZqU_gz;+#)XA-g%** zsHk7w$0r5qYb3WY+zYAM_K404uWf6$N=umaqy*CRz_F?s^g`%sEb88*1;r63Sy8w) z5_;Q)SM)w^Z=GhU;PXO=uX?1BNKrYnfWm0pGZkO;q({|`WYZ2OHj-Gv$ubVw$gd}B zh*AsvVEwA>bl;f;@f#V@HIkoC$hPNyW~C%}a?Sm{>cUsVLiMmbqs`Y(I$pC+II|*Q zxR^iwN}J*7|II$fAT%-haM|`*duclhHi{M}AS2T`!lG?b&Or`7&uE-7Z-R5Mm1D-O z%vGveq({UjTAhL zd~fa2eO41B63$ev=>0j7a@QRZD0&v{5j!Nr082EB<4d+@I8aXjKsQFT z5kR2BX;a5w&}LZ9Xbl}G!!DYutKm}JqXL`(-hKIPFu1z4)Xvg?PViYdq@;9w)VELv z+FbJ}@&7eHj@G)hz)~8-DjQT0 zQwaAD&!?bVBtiKX{=)}aKAvxS#PPce$_CGLloACefk=1lv)9p~f`LivcDPSIrly~c ze>FBAMO-RnK2#P6d;eM;l8DH5%dWG?cDUQkq3Ka{QsHGW0`-L^q2_3u5)!lRyJRDy zUd>Kq2h`mfv=fsIk@DuS>)T~ElEajpB!mL&cxO8b8ku!v)x8S&;02NgHy=oDAGm)f z2}w$iAFQxl^LfRmnB7Y?TVs3pOCWU-s*0}{{r$NfC3qsOF2bb2Pp;0Hj9XBG{OtJb z+zZN8{c<_lvqkZ=ml(W|Kz(ohil0#+5~8=&FV^X{Zf7g@tlV70vhjIihPjP#8=l(q zO&ZQUIVxi@b4aMj>4_`TigTqj69G4SN=P2ai2G0mpg5d9jwzfE(8HK;^Hv%l`rrt1of@ literal 0 HcmV?d00001 diff --git a/frontend/src/assets/icons/play-circle.png b/frontend/src/assets/icons/play-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..def567b70321495e34494adc90925204729d70b0 GIT binary patch literal 5411 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 literal 0 HcmV?d00001 diff --git a/frontend/src/components/Highlight.css b/frontend/src/components/Highlight.css new file mode 100644 index 0000000..8a67f50 --- /dev/null +++ b/frontend/src/components/Highlight.css @@ -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; +} \ No newline at end of file diff --git a/frontend/src/components/Highlight.jsx b/frontend/src/components/Highlight.jsx new file mode 100644 index 0000000..5d355ca --- /dev/null +++ b/frontend/src/components/Highlight.jsx @@ -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 ( +
+ {valueString()} + {props.description} +
+ ); +} + +export default Highlight; diff --git a/frontend/src/components/StreamActivity.css b/frontend/src/components/StreamActivity.css new file mode 100644 index 0000000..7010da7 --- /dev/null +++ b/frontend/src/components/StreamActivity.css @@ -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); +} \ No newline at end of file diff --git a/frontend/src/components/StreamActivity.jsx b/frontend/src/components/StreamActivity.jsx new file mode 100644 index 0000000..30f48f0 --- /dev/null +++ b/frontend/src/components/StreamActivity.jsx @@ -0,0 +1,20 @@ +import StreamEvent from './StreamEvent'; + +import './StreamActivity.css'; + +function StreamActivity(props) { + return ( +
+
+ {props.title} +
+
+ {props.events.map((event, index) => ( + + ))} +
+
+ ); +} + +export default StreamActivity; diff --git a/frontend/src/components/StreamChat.css b/frontend/src/components/StreamChat.css new file mode 100644 index 0000000..b70cb73 --- /dev/null +++ b/frontend/src/components/StreamChat.css @@ -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; +} \ No newline at end of file diff --git a/frontend/src/components/StreamChat.jsx b/frontend/src/components/StreamChat.jsx new file mode 100644 index 0000000..bb9e3c1 --- /dev/null +++ b/frontend/src/components/StreamChat.jsx @@ -0,0 +1,13 @@ +import './StreamChat.css'; + +function StreamChat(props) { + return ( +
+
+ {props.title} +
+
+ ); +} + +export default StreamChat; diff --git a/frontend/src/components/StreamEvent.css b/frontend/src/components/StreamEvent.css new file mode 100644 index 0000000..0347ea4 --- /dev/null +++ b/frontend/src/components/StreamEvent.css @@ -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; +} diff --git a/frontend/src/components/StreamEvent.jsx b/frontend/src/components/StreamEvent.jsx new file mode 100644 index 0000000..436f982 --- /dev/null +++ b/frontend/src/components/StreamEvent.jsx @@ -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 ( +
+
+ {props.event.followed_on && } + {props.event.subscribed_on && } +
+ {props.event.username} + + {props.event.followed_on && 'Followed you'} + {props.event.subscribed_on && 'Subscribed'} + +
+
+ + {props.event.followed_on && dateString(props.event.followed_on)} + {props.event.subscribed_on && dateString(props.event.subscribed_on)} + +
+ ); +} + +export default StreamEvent; diff --git a/frontend/src/components/StreamInfo.css b/frontend/src/components/StreamInfo.css new file mode 100644 index 0000000..3cceb22 --- /dev/null +++ b/frontend/src/components/StreamInfo.css @@ -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; +} \ No newline at end of file diff --git a/frontend/src/components/StreamInfo.jsx b/frontend/src/components/StreamInfo.jsx new file mode 100644 index 0000000..ba0b68b --- /dev/null +++ b/frontend/src/components/StreamInfo.jsx @@ -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 ( +
+
+
+ {props.live ? props.title : '-'} +
+
+
+ + {props.live ? props.categories.primary.title : 'none'} + + + {props.live ? props.categories.secondary.title : 'none'} + +
+
+
+ + + {props.live ? likesString(props.likes) : '-'} + +
+
+ + + {props.live ? likesString(props.dislikes) : '-'} + +
+
+
+
+
+ Channel: {props.channel} +
+
+
+
+ + + +
+
+
+
+ ); +} + +export default StreamInfo; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index e50e105..5a6262c 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -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( - - - -) + // + + // +); diff --git a/frontend/src/screens/Dashboard.css b/frontend/src/screens/Dashboard.css index fba8777..0f135af 100644 --- a/frontend/src/screens/Dashboard.css +++ b/frontend/src/screens/Dashboard.css @@ -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; -} \ No newline at end of file diff --git a/frontend/src/screens/Dashboard.jsx b/frontend/src/screens/Dashboard.jsx index 599e0e6..04180ef 100644 --- a/frontend/src/screens/Dashboard.jsx +++ b/frontend/src/screens/Dashboard.jsx @@ -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 (
- Dashboard: -
-
- Followers -
-
- - {channelFollowers} - - Channel -
-
- - {totalFollowers} - - Total -
-
-
-
- {recentFollowers.map((follower, index) => ( -
- - {follower.username} - - - {dateString(follower.followed_on)} - -
- ))} +
+
+
+ {/* */} + + +
+
+
+
+ +
+
+ +
+
+
+
); } diff --git a/frontend/src/style.css b/frontend/src/style.css index 38bdf56..16aff13 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -6,5 +6,5 @@ body { } #app { - height: 100vh; + /* height: 100vh; */ } diff --git a/go.mod b/go.mod index f4e2f55..5a1e8b7 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 8457545..adf327d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/api/api.go b/internal/api/api.go new file mode 100644 index 0000000..c6966fb --- /dev/null +++ b/internal/api/api.go @@ -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 diff --git a/main.go b/main.go index e9600bb..a848514 100644 --- a/main.go +++ b/main.go @@ -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, }, })