Skip to content

Commit

Permalink
🔪 frontend: Refactor confetti and Yace modules (#236)
Browse files Browse the repository at this point in the history
Rename YaceEditor to Editor as it is increasingly becoming customized and
tailored to evy. Pull out confetti Easter egg into its own module and remove
dependency on separate CSS file. In last commit re-work confetti Easter egg
for readability.

This merges the following commits:
* frontend: Rename YaceEditor to Editor
* fontend: Move confetti easter egg to its own module
* frontend: Make confetti.js self-contained
* frontend: Refactor confetti module

     frontend/css/index.css                        |  17 ---
     frontend/index.js                             |  61 +----------
     frontend/module/confetti.js                   | 103 ++++++++++++++++++
     frontend/module/{yace-editor.js => editor.js} |   2 +-
     4 files changed, 108 insertions(+), 75 deletions(-)

Pull-Request: #236
  • Loading branch information
juliaogris committed Jan 9, 2024
2 parents c0dbaf0 + 9573744 commit 2997529
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 75 deletions.
17 changes: 0 additions & 17 deletions frontend/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -789,23 +789,6 @@ embed {
margin-right: auto;
}

/* --- Easter egg ---------------------------------------------------- */
.confetti {
height: 7vw;
width: 7vw;
line-height: 7vw;
border-radius: 50%;
position: absolute;
font-size: 4vw;
user-select: none;
text-align: center;
color: hsl(0deg 0% 100%);
}
.confetti.fadeout {
opacity: 0;
transition: opacity 1.5s ease-in-out;
}

/* --- Utilities ----------------------------------------------------- */
.desktop {
display: var(--display-desktop-only);
Expand Down
61 changes: 4 additions & 57 deletions frontend/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use strict"
import Yace from "./module/yace-editor.js"
import Editor from "./module/editor.js"
import showConfetti from "./module/confetti.js"

// --- Globals ---------------------------------------------------------

Expand Down Expand Up @@ -119,6 +120,7 @@ function jsPrint(ptr, len) {
const output = document.querySelector("#console")
output.textContent += s
output.scrollTo({ behavior: "smooth", left: 0, top: output.scrollHeight })
// 🐣 Show confetti Easter egg if print argument contains literal string "confetti"
if (s.toLowerCase().includes("confetti")) {
showConfetti()
}
Expand Down Expand Up @@ -772,7 +774,7 @@ function clamp(val, min, max) {
}

function initEditor() {
editor = new Yace(".editor", { sessionKey: "evy-editor" })
editor = new Editor(".editor", { sessionKey: "evy-editor" })
document.querySelector(".editor-wrap").classList.remove("noscrollbar")
}

Expand Down Expand Up @@ -954,61 +956,6 @@ function showAbout() {
about.showModal()
}

// --- UI: Confetti Easter Egg -----------------------------------------
//
// When code input string contains the sub string "confetti" show
// confetti on Run button click.

function showConfetti() {
const names = ["🦊", "🐐"]
const colors = ["red", "purple", "blue", "orange", "gold", "green"]
let confetti = new Array(100)
.fill()
.map((_, i) => {
return {
name: names[i % names.length],
x: Math.random() * 100,
y: -20 - Math.random() * 100,
r: 0.1 + Math.random() * 1,
color: colors[i % colors.length],
}
})
.sort((a, b) => a.r - b.r)

const cssText = (c) =>
`background: ${c.color}; left: ${c.x}%; top: ${c.y}%; transform: scale(${c.r})`
const confettiDivs = confetti.map((c) => {
const div = document.createElement("div")
div.style.cssText = cssText(c)
div.classList.add("confetti")
div.textContent = c.name
document.body.appendChild(div)
return div
})

let frame

function loop() {
frame = requestAnimationFrame(loop)
confetti = confetti.map((c, i) => {
c.y += 0.7 * c.r
if (c.y > 120) c.y = -20
const div = confettiDivs[i]
div.style.cssText = cssText(c)
return c
})
}

loop()
setTimeout(() => {
cancelAnimationFrame(frame)
confettiDivs.forEach((div) => div.remove())
}, 10000)
setTimeout(() => {
confettiDivs.forEach((div) => div.classList.add("fadeout"))
}, 8500)
}

// --- Share / load snippets -------------------------------------------

async function share() {
Expand Down
103 changes: 103 additions & 0 deletions frontend/module/confetti.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const defaultOptions = {
duration: 10, // animation seconds
fadeoutAfter: 8, // fadeout start in seconds
count: 100, // confetti count
texts: ["🦊", "🐐"],
colors: ["red", "purple", "blue", "orange", "gold", "green"],
}

export default function showConfetti(options = {}) {
options = { ...defaultOptions, ...options }
const confettis = Array(options.count)
for (let i = 0; i < options.count; i++) {
confettis[i] = newConfetti(options)
}
const divs = confettis.map(newDiv)
divs.forEach((div) => document.body.appendChild(div))
animate(confettis, divs, options)
}

function newConfetti(options) {
const { texts, colors } = options
return {
text: texts[Math.floor(Math.random() * texts.length)],
x: Math.random() * 100,
y: Math.random() * 100,
r: Math.random() + 0.1, // scale factor, see top()
color: colors[Math.floor(Math.random() * colors.length)],
}
}

function newDiv(confetti) {
const baseStyle = {
height: "14vh",
width: "14vh",
lineHeight: "14vh",
borderRadius: "50%",
position: "absolute",
fontSize: "8vh",
userSelect: "none",
textAlign: "center",
}
const confettiStyle = {
background: confetti.color,
top: `${top(confetti.y, 0)}%`,
// left property offsets the center of the confetti with max radius 7vh.
left: `calc(${confetti.x}vw - 7vh)`,
transform: `scale(${confetti.r})`,
}

const div = document.createElement("div")
div.textContent = confetti.text
Object.assign(div.style, baseStyle, confettiStyle)
return div
}

function animate(confettis, divs, options) {
const fadeoutStyle = newFadeoutStyle(options)
let fading = false
const start = document.timeline.currentTime

requestAnimationFrame(onFrame)

function onFrame(ts) {
const elapsed = (ts - start) / 1000 // elapsed seconds
if (elapsed > options.duration) {
// animation done
divs.forEach((div) => div.remove())
return
}
// update offset from top
for (let i = 0; i < divs.length; i++) {
const style = { top: `${top(confettis[i], elapsed)}%` }
Object.assign(divs[i].style, style)
}
if (elapsed > options.fadeoutAfter && !fading) {
// add fadeout style
fading = true
divs.forEach((div) => Object.assign(div.style, fadeoutStyle))
}
requestAnimationFrame(onFrame)
}
}

function newFadeoutStyle(options) {
const transitionDur = options.duration - options.fadeoutAfter
return {
opacity: 0,
transition: `opacity ${transitionDur}s ease-in-out`,
}
}

// top returns offset from top of viewport.
function top(confetti, elapsed) {
// r is the scale factor [0.1,1.1). It scales down confetti size and delta y.
const r = confetti.r
// y is the initial position. It is [-120, 20) above viewport.
const maxDiameter = 14 // 14vh
const yInitial = -confetti.y - maxDiameter
// yElapsed is the position after elapsed seconds.
const yElapsed = yInitial + elapsed * 50 * r
// When yElapsed is below the viewport start over from the top.
return (yElapsed % (100 + maxDiameter)) - maxDiameter
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// https://github.com/petersolopov/yace - MIT licensed
// source: https://github.com/petersolopov/yace/blob/8ed1f99977c4db9bdd60db4e2f5ba4edfcfc1940/src/index.js
export default class Yace {
export default class Editor {
constructor(selector, options = {}) {
this.root = document.querySelector(selector)

Expand Down

0 comments on commit 2997529

Please sign in to comment.