activity.template.mjs
1 import { html } from "uhtml/reactive" 2 import * as constants from "./constants.mjs" 3 import * as access from "./access.mjs" 4 5 function copyToClipboard(val) { 6 navigator.clipboard.writeText(val) 7 } 8 9 const SessionTable = (rows) => (html` 10 <div class="table support-sessions"> 11 <div class="table-header"> 12 <p class="table-value session-channel">Channel</p> 13 <p class="table-value session-start">Start</p> 14 <p class="table-value session-duration">Duration</p> 15 <p class="table-value session-bits">Bits</p> 16 <p class="table-value session-users">Users</p> 17 <p class="table-value session-viewers">Viewers</p> 18 <p class="table-value session-questions">Questions</p> 19 </div> 20 ${rows.map((r) => SessionTableRow(r))} 21 </div>`) 22 23 const SessionTableRow = (row) => { 24 const channelLink = `https://twitch.tv/${row.twitch_userName}` 25 , vodLink = row.vod ? html`<a class="table-value-button" href="${row.vod}">VOD</a>` : html`<span ?hidden=${true}></span>` 26 return html` 27 <div class="table-row"> 28 <div class="table-value session-channel table-value-double"> 29 <p class="session-channel-name"> 30 <a href="${channelLink}">${row.twitch_userName}</a> 31 </p> 32 <p class="session-channel-id table-value-button" onclick="${() => { copyToClipboard(row.channel_id) }}">${row.channel_id}</p> 33 </div> 34 <p class="table-value session-start table-value-double"> 35 <span>${new Date(row.open).toDateString()}</span> 36 ${vodLink} 37 </p> 38 <p class="table-value session-duration">${Math.floor((row.close - row.open) / 60000)}m</p> 39 <p class="table-value session-bits">${row.bits}</p> 40 <p class="table-value session-users">${row.subscribers + row.viewers}</p> 41 <p class="table-value session-viewers">${row.avg_viewers > 0 ? row.avg_viewers : ""}</p> 42 <p class="table-value session-questions">${row.questions}</p> 43 </div>` 44 } 45 46 const ConfigTable = (rows) => (html` 47 <div class="table support-configs"> 48 <div class="table-header"> 49 <p class="table-value config-channel">Channel</p> 50 <p class="table-value config-mode">Mode</p> 51 <p class="table-value config-points">Points</p> 52 <p class="table-value config-submit">Submit</p> 53 <p class="table-value config-upvote">Upvote</p> 54 <p class="table-value config-suspensions">Suspended</p> 55 </div> 56 ${rows.map((r) => ConfigTableRow(r))} 57 </div>`) 58 59 function MultiBadges(active) { 60 const badges = [] 61 for (const multi of constants.ALL_MULTI) { 62 let badgeClass = `config-multi config-multi-x${multi} ${active.has(multi) ? " config-multi-active" : " "}` 63 badges.push(html`<span class="${badgeClass}">${multi}</span>`) 64 } 65 66 return badges 67 } 68 69 const ConfigTableRow = (row) => { 70 const channelLink = `https://twitch.tv/${row.twitch_userName}` 71 , activeUpvoteMultis = new Set(row.enabled_upvote_multipliers.split(" ")) 72 , activeSubmissionMultis = new Set(row.enabled_submission_multipliers.split(" ")) 73 , sessionLength = row.session_mode != "ENDLESS" ? html`<span>${row.session_length + "m"}</span>` : html`<span ?hidden=${true}></span>` 74 , isLiveClass = row.isLive ? "config-live" : "" 75 , rowClass = row.hasNoConfig ? "table-row config-empty" : "table-row" 76 77 const viewerAllowedClass = row.exclude_viewers ? "config-points-notAllowed" : "" 78 79 return html` 80 <div class="${rowClass}"> 81 <div class="table-value config-channel table-value-double"> 82 <p class="config-channel-name"> 83 <a href="${channelLink}" class="${isLiveClass}">${row.twitch_userName}</a> 84 </p> 85 <p class="session-channel-id table-value-button" onclick="${() => { copyToClipboard(row.channel_id) }}">${row.channel_id}</p> 86 </div> 87 <p class="table-value table-value-double config-mode"> 88 <span>${row.session_mode == "ENDLESS" ? "Endless" : "Countdown"}</span> 89 ${sessionLength} 90 </p> 91 <p class="table-value config-points"> 92 <span class="${viewerAllowedClass}">${row.viewer_points}</span> 93 <span>${row.subscriber_points}</span> 94 </p> 95 <p class="table-value config-submit">${MultiBadges(activeSubmissionMultis)}</p> 96 <p class="table-value config-upvote">${MultiBadges(activeUpvoteMultis)}</p> 97 <p class="table-value config-suspensions">${row.suspended.split(" ").length - 1}</p> 98 </div>` 99 } 100 101 export const ActivityView = (state) => { 102 const { isLoading, lastUpdated, sessions, configs } = state 103 104 const isLoadingClass = isLoading.value ? "activity loading" : "activity" 105 , updateHandler = () => { access.GetActivityData(state, "latest") } 106 107 return html` 108 <div class=${isLoadingClass}> 109 <div class="activity-tools"> 110 <p class="activity-tools-lastUpdated"> 111 <span class="activity-tools-lastUpdated-label">Last Updated</span> 112 <span class="activity-tools-lastUpdated-time">${lastUpdated.value.toLocaleTimeString()}</span> 113 </p> 114 <button class="activity-tools-update" onclick=${updateHandler}></button> 115 </div> 116 117 <h3 class="activity-headline">Sessions</h3> 118 ${SessionTable(Array.from(sessions.value.values()))} 119 <h3 class="activity-headline">Configs</h3> 120 ${ConfigTable(Array.from(configs.value.values()))} 121 </div>` 122 }