Skip to content

Commit

Permalink
CheerLights Live: Add new project
Browse files Browse the repository at this point in the history
This is also done and no changes have been done for a while, except for some finishing, so it's a good time to release it
  • Loading branch information
Hans5958 committed Jan 8, 2025
1 parent 08bb17d commit 2537fe8
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 1 deletion.
6 changes: 5 additions & 1 deletion site/_data/projects.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,8 @@

- id: aslap-warp
name: AS Long As Possible Warp
desc: View the rendition of the "AS Long As Possible" GIF, with time warping abilities (see past and future renditions).
desc: View the rendition of the "AS Long As Possible" GIF, with time warping abilities (see past and future renditions).

- id: cheerlights-live
name: CheerLights Live
desc: A simple viewer/client for CheerLights with extensive code documentation for educational purposes.
102 changes: 102 additions & 0 deletions site/cheerlights-live/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
layout: default
title: CheerLights Live
description: A simple viewer/client for CheerLights with extensive code documentation for educational purposes.
---

<!-- Welcome! This is the source code of the page of "CheerLights Live". -->

<!-- Some Day.js libraries to handle dates -->
<script defer src="https://cdn.jsdelivr.net/combine/npm/dayjs@1,npm/dayjs@1/plugin/relativeTime.min.js,npm/dayjs@1/plugin/customParseFormat.min.js,npm/dayjs@1/plugin/utc.min.js,npm/dayjs@1/plugin/timezone.min.js"></script>

<!-- Includes the script that makes this page possible.
This file can be found beside this index.html page. -->
<script defer src="script.js"></script>

<!-- Some styles regarding the page -->
<style>
/* The light element. */
#light {
/* We will use the variable --cl-main so we don't need to
duplicate the colors for multiple parts of the stylesheet. */
background: var(--cl-main);
border-radius: 50%;
height: 10rem;
width: 10rem;
border: 1px solid var(--cl-secondary);

box-shadow: 0 0 0 0 var(--cl-main);
/* transform: scale(1); */
/* animation: pulse 2s; */
}

/* An animation defining the pulse. */
@keyframes pulse {
0% {
/* transform: scale(0.95); */
box-shadow: 0 0 0 0 var(--cl-secondary);
}

70% {
/* transform: scale(1); */
box-shadow: 0 0 0 10px rgba(255, 255, 255, 1);
}

100% {
/* transform: scale(0.95); */
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
}
}
</style>

<div class="container">
<section class="row">
<div class="col d-flex flex-column justify-content-center">
<p class="h2">The current color is <span class="light-text font-weight-bold"></span>.</p>
<div>
<div class="d-flex justify-content-between">
<div class="mr-1">Event: </div>
<div class="text-right d-flex justify-content-right">
<div class="info-event-2 mb-0 mr-2"></div>
<div class="info-event mb-0 text-monospace"></div>
</div>
</div>
</div>
<div>
<div class="d-flex justify-content-between">
<div class="mr-1">Received: </div>
<div class="text-right d-flex justify-content-right">
<div class="info-received-2 mb-0 mr-2"></div>
<div class="info-received mb-0 text-monospace"></div>
</div>
</div>
</div>
<div>
<div class="d-flex justify-content-between">
<div class="mr-1">Fetch: </div>
<div class="text-right d-flex justify-content-right">
<div class="info-fetch-2 mb-0 mr-2"></div>
<div class="info-fetch mb-0 text-monospace"></div>
</div>
</div>
</div>

</div>
<div class="col-auto">
<div id="light"></div>
</div>
</section>
<section id="status">
<p style="text-align: center; width: 100%; margin-bottom: 0"> </p>
<div class="progress" style="width: 100%">
<div class="progress-bar" role="progressbar" style="width: 0"></div>
</div>
</section>
<p></p>
<section class="jumbotron py-5">
<h2 class="mb-4">About <small class="text-muted">CheerLights Live</small></h2>
<p>This page displays the current light on <a href="https://cheerlights.com/">CheerLights</a>, a public light service/"global network of synchronized lights" that everyone can control. You are a part of "the network" by opening this page, and you can control it.</p>
<p>To control the light, simply interact with the bot present on <a href="https://cheerlights.com/discord">the CheerLights Discord server</a>, or mentioning the account on Mastodon, <a href="https://botsin.space/@CheerLights">@[email protected]</a>. The list of colors are present on <a href="https://cheerlights.com/learn/">their website</a>.</p>
<p class="mb-0">This page also serves an educational purpose due to how simple it is (get the color from the API, show the color on the page), has a substantial amount of documentation, and the usage of a public, active API. The source code can be viewed on <a href="https://github.com/Hans5958/mini-htmls/tree/master/site/cheerlights-live">the GitHub repository</a>.</p>
</section>
</div>
127 changes: 127 additions & 0 deletions site/cheerlights-live/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// This file serves as the script that makes the page interactive.
// (basically making the whole page possible)
// Note that the usage of "date" has the same meaning as the Date object in
// JavaScript, which also includes the time. If you care only the light,
// the parts regarding dates can be safetly ignored.

/**
* Formats the date into a nice string.
* @param day A Day.js object, which includes a date.
* @returns A nice string displaying the date.
*/
const dayToString1 = day => {
return day.format('DD/MM/YYYY HH:mm:ss Z')
}

/**
* The element for the light.
*/
const lightEl = document.querySelector('#light')

/**
* The current event ID.
*/
let currentId = null

/**
* The date the current event is receieved.
*/
let receivedDate = null

/**
* Fetches the current event of the light and updates the page with new information.
*/
const execute = async () => {
// Resets the animation
lightEl.style.setProperty('animation', 'none')

// Fetches the API related to Cheerlights. The API can be found on their site.
// The fetch() function returns a Promise (an type of asynchronous function), which we need to wait with await.
const request = await fetch('http://api.thingspeak.com/channels/1417/field/1/last.json')

// Tells that the result is a JSON, in which should be parsed
// This, too, returns a Promise, which we need to wait with the same way.
const data = await request.json()

// Set the color variable for ease of access
const color = data.field1

// Put a pulsing animation
lightEl.style.setProperty('animation', 'pulse 2s')

// Set few relevant dates

/**
* The date included on the data.
*/
const eventDate = dayjs(data.created_at, 'YYYY-MM-DDTHH:mm:ssWIB')

/**
* The date on which the data is fetched.
*/
const fetchDate = dayjs()

// Updates the dates on the document
updateTextContent('.info-event', dayToString1(eventDate))
if (receivedDate) updateTextContent('.info-received', dayToString1(receivedDate))
updateTextContent('.info-fetch', dayToString1(fetchDate))

// Run these next functions only when it is a new event, inferred from the ID.
// Ideally we don't want to redo something that has been done because it waste
// resources (in this case, negligble), but this also handles the part related
// the date on receivedDate. If you care only the light, this can also be
// safetly ignored.
if (currentId === data.entry_id) return
currentId = data.entry_id

// Set receivedDate and update the date on the document
receivedDate = dayjs()
updateTextContent('.info-received', dayToString1(receivedDate))

// Update the displayed colors
lightEl.style.setProperty('--cl-main', color)
updateTextContent('.light-text', color)
// Sets the secondary color, this mainly to handle an edge case when it is
// white, otherwise you can see anything. If it is any other color, the
// secondary color would be the same as the main one.
if (color === 'white') {
lightEl.style.setProperty('--cl-secondary', 'black')
} else {
lightEl.style.setProperty('--cl-secondary', color)
}
}

execute()

let statusProgressBar = 'fetch'

/**
* Handles the timer when it is updating
*/
const executeTimer = async () => {
statusProgressBar = "fetch"
document.querySelector("#status .progress-bar").style.width = "0"
document.querySelector("#status .progress-bar").style.transition = "width 0.25s ease"
updateTextContent("#status p", `Fetching data...`)
await execute()
document.querySelector("#status .progress-bar").style.width = "100%"
updateTextContent("#status p", "Data fetched!")
setTimeout(() => {
document.querySelector("#status .progress-bar").style.transition = "unset"
statusProgressBar = "timer"
}, 1000)
}

/**
* Handles the countdown of the timer
* @param {*} timer
*/
const executeTick = timer => {
if (statusProgressBar === "timer") {
updateTextContent("#status p", `Waiting... (${timer.toFixed(2)}s)`)
document.querySelector("#status .progress-bar").style.width = `${100 - (timer / 10) * 100}%`
}
}

// This function can be found on site/assets/js/base.js.
window.setIntervalFancy(executeTick, executeTimer, 5000, true)

0 comments on commit 2537fe8

Please sign in to comment.