Skip to content

Instantly share code, notes, and snippets.

@rushkeldon
Last active April 17, 2024 04:54
Show Gist options
  • Save rushkeldon/ca5791003f87f46674218a4de3aa9fef to your computer and use it in GitHub Desktop.
Save rushkeldon/ca5791003f87f46674218a4de3aa9fef to your computer and use it in GitHub Desktop.
gamify Jira with sound effects and fireworks when moving tickets by dragging
canvas {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100vw;
height: 100vh;
z-index: 1000;
margin: auto;
transition: opacity 0.75s ease-out;
opacity: 1;
}
canvas.outro {
opacity: 0;
}
const gamify = () => {
let tickets = document.querySelectorAll('.js-parent-drag');
if (!tickets.length) return;
tickets.forEach((ticket) => {
if (!isGamified(ticket)) {
ticket.removeEventListener('mousedown', ticketPressed, true);
ticket.addEventListener('mousedown', ticketPressed, true);
ticket.removeEventListener('mouseup', ticketReleased, true);
ticket.addEventListener('mouseup', ticketReleased, true);
setGamified(ticket);
}
});
};
function ticketPressed(e) {
e.target.setAttribute('data-x-coord', getX(e.target));
}
function ticketReleased(e) {
const startX = parseInt(e?.target?.getAttribute('data-x-coord'));
if (isNaN(startX)) return;
const endX = getX(e.target);
let mp3 = 'https://appcloud9.com/snd/';
switch (true) {
case Math.abs(startX - endX) <= 20 :
mp3 += 'neutral.mp3';
break;
case startX < endX :
mp3 += 'powerup.mp3';
createFireworks();
break;
case startX > endX :
mp3 += 'powerdown.mp3';
break;
default :
console.log('unhandled case encountered by ticketReleased.');
}
playSound(mp3);
unsetGamfied(e.target);
window.setTimeout(gamify, 250);
}
function setGamified(el) {
el.setAttribute('data-gamified', true);
}
function unsetGamfied(el) {
el.removeAttribute('data-gamified');
}
function isGamified(el) {
return el.hasAttribute('data-gamified');
}
function getX(el) {
return el.getBoundingClientRect().left;
}
function playSound(url) {
const audio = new Audio(url);
audio.play();
}
function createFireworks() {
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d'),
cw = window.innerWidth,
ch = window.innerHeight,
fireworks = [],
particles = [],
hue = 120,
limiterTotal = 20,
limiterTick = 0,
timerTotal = 0,
randomTime = 0,
timerTick = 0,
mousedown = false,
mx,
my;
canvas.width = cw;
canvas.height = ch;
var snd = new Audio('https://appcloud9.com/snd/firework.mp3');
function random(min, max) {
return min + Math.random() * (max - min);
}
function calculateDistance(p1x, p1y, p2x, p2y) {
return Math.sqrt((p1x - p2x) * (p1x - p2x) + (p1y - p2y) * (p1y - p2y));
}
function Firework(sx, sy, tx, ty) {
this.x = sx;
this.y = sy;
this.sx = sx;
this.sy = sy;
this.tx = tx;
this.ty = ty;
this.distanceToTarget = calculateDistance(sx, sy, tx, ty);
this.distanceTraveled = 0;
this.coordinates = [];
this.coordinateCount = 2;
while (this.coordinateCount--) {
this.coordinates.push([this.x, this.y]);
}
this.angle = Math.atan2(ty - sy, tx - sx);
this.speed = 1;
this.acceleration = 1.2;
this.brightness = random(50, 70);
this.tragetRadius = 1;
}
Firework.prototype.update = function(index) {
if (this.targetRadius < 8) {
this.targetRadius += 0.3;
} else {
this.targetRadius = 1;
}
this.speed *= this.acceleration;
var vx = Math.cos(this.angle) * this.speed,
vy = Math.sin(this.angle) * this.speed;
this.distanceTraveled = calculateDistance(this.sx, this.sy, this.x + vx, this.y + vy);
if (this.distanceTraveled >= this.distanceToTarget) {
this.coordinates.pop();
this.coordinates.unshift([this.tx, this.ty]);
createParticles(this.x, this.y);
snd.play();
this.draw();
fireworks.splice(index, 1);
} else {
this.x += vx;
this.y += vy;
}
this.coordinates.pop();
this.coordinates.unshift([this.x, this.y]);
};
Firework.prototype.draw = function() {
ctx.beginPath();
ctx.moveTo(this.coordinates[this.coordinates.length - 1][0], this.coordinates[this.coordinates.length - 1][1]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = 'hsl(' + hue + ', 100%, ' + this.brightness + '%)';
ctx.stroke();
};
function Particle(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
this.coordinates = [];
this.coordinateCount = 6;
while (this.coordinateCount--) {
this.coordinates.push([this.x, this.y]);
}
var variation = random(1, 5);
this.friction = 0.95;
this.alpha = 1;
this.angle = random(0, Math.PI * 2);
switch (type) {
case 1:
switch (true) {
case variation < 2 :
this.speed = random(1, 15);
this.gravity = 4;
this.hue = random(hue - 50, hue + 50);
this.brightness = random(50, 80);
this.decay = random(0.01, 0.02);
break;
case variation < 3 :
this.speed = random(1, 5);
this.gravity = 3;
this.hue = random(hue - 50, hue);
this.brightness = random(50, 80);
this.decay = random(0.015, 0.03);
break;
case variation < 4 :
this.speed = random(1, 8);
this.gravity = 3;
this.hue = random(hue, hue + 50);
this.brightness = random(50, 80);
this.decay = random(0.015, 0.03);
break;
default :
this.speed = random(1, 15);
this.gravity = 3;
this.hue = random(hue - 50, hue + 50);
this.brightness = random(10, 20);
this.decay = random(0.015, 0.3);
}
break;
case 2:
this.hue = 100;
switch (true) {
case variation < 2 :
this.speed = random(1, 10);
this.gravity = 4;
this.brightness = random(50, 80);
this.decay = random(0.01, 0.02);
break;
case variation < 3 :
this.speed = random(1, 21);
this.gravity = 3;
this.brightness = random(50, 80);
this.decay = random(0.015, 0.03);
break;
case variation < 4 :
this.speed = random(1, 3);
this.gravity = 3;
this.brightness = random(50, 80);
this.decay = random(0.015, 0.03);
break;
default :
this.speed = random(1, 5);
this.gravity = 3;
this.brightness = random(10, 20);
this.decay = random(0.015, 0.3);
}
break;
case 3:
this.brightness = random(10, 20);
switch (true) {
case variation < 2 :
this.speed = random(10, 15);
this.gravity = 4;
this.hue = 60;
this.decay = random(0.01, 0.02);
break;
case variation < 3 :
this.speed = random(11, 15);
this.gravity = 3;
this.hue = 10;
this.decay = random(0.015, 0.03);
break;
case variation < 4 :
this.speed = random(11, 18);
this.gravity = 3;
this.hue = 90;
this.decay = random(0.015, 0.03);
break;
default :
this.speed = random(11, 15);
this.gravity = 3;
this.hue = 120;
this.decay = random(0.015, 0.3);
}
break;
case 4:
switch (true) {
case variation < 2:
this.speed = random(1, 10);
this.gravity = 4;
this.hue = 300;
this.brightness = random(50, 80);
this.decay = random(0.01, 0.02);
break;
case variation < 3:
this.speed = random(1, 21);
this.gravity = 3;
this.hue = 300;
this.brightness = random(50, 80);
this.decay = random(0.015, 0.03);
break;
case variation < 4:
this.speed = random(1, 3);
this.gravity = 3;
this.hue = 300;
this.brightness = random(50, 80);
this.decay = random(0.015, 0.03);
break;
default:
this.speed = random(1, 5);
this.gravity = 3;
this.hue = 100;
this.brightness = random(10, 20);
this.decay = random(0.015, 0.3);
}
break;
default:
}
}
Particle.prototype.update = function(index) {
this.speed *= this.friction;
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed + this.gravity;
this.alpha -= this.decay;
if (this.type == 4 && this.alpha <= 0.5) {
this.brightness += 50;
this.hue += 200;
if (this.brightness >= 200)
this.brightness = 0;
}
if (this.alpha <= this.decay) {
particles.splice(index, 1);
}
this.coordinates.pop();
this.coordinates.unshift([this.x, this.y]);
};
Particle.prototype.draw = function() {
ctx.beginPath();
ctx.moveTo(this.coordinates[this.coordinates.length - 1][0], this.coordinates[this.coordinates.length - 1][1]);
ctx.lineTo(this.x, this.y);
ctx.strokeStyle = 'hsla(' + this.hue + ', 100%, ' + this.brightness + '%, ' + this.alpha + ')';
ctx.stroke();
};
function createParticles(x, y) {
var particleCount = 300;
var type = Math.floor(random(1, 5));
while (particleCount--) {
particles.push(new Particle(x, y, type));
}
}
function loop() {
hue += 0.5;
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.fillRect(0, 0, cw, ch);
ctx.globalCompositeOperation = 'lighter';
var i = fireworks.length;
while (i--) {
fireworks[i].draw();
fireworks[i].update(i);
}
var i = particles.length;
while (i--) {
particles[i].draw();
particles[i].update(i);
}
if (timerTick >= timerTotal + randomTime) {
if (!mousedown) {
var xPos = Math.pow(Math.floor((random(-Math.pow(cw / 2, 1 / 3), Math.pow(cw / 2, 1 / 3)))), 3);
xPos += cw / 2;
fireworks.push(new Firework(cw / 2, ch, xPos, random(0, ch / 2)));
timerTick = 0;
randomTime = Math.pow(random(2, 4), 2);
}
} else {
timerTick++;
}
if (limiterTick >= limiterTotal) {
if (mousedown) {
fireworks.push(new Firework(cw / 2, ch, mx, my));
limiterTick = 0;
} else {
limiterTick = limiterTotal;
}
} else {
limiterTick++;
}
}
canvas.addEventListener('mousemove', function(e) {
mx = e.pageX - canvas.offsetLeft;
my = e.pageY - canvas.offsetTop;
});
canvas.addEventListener('mousedown', function(e) {
e.preventDefault();
mousedown = true;
});
canvas.addEventListener('mouseup', function(e) {
e.preventDefault();
mousedown = false;
});
setInterval(loop, 25);
function outroed() {
canvas.removeEventListener( 'transitionend', outroed );
snd.pause();
snd.src = '';
const trash = document.createElement( 'div' );
trash.appendChild( canvas );
trash.innerHTML = '';
}
function removeFireworks(){
canvas.addEventListener( 'transitionend', outroed );
canvas.classList.add( 'outro' );
}
window.setTimeout( removeFireworks, 4000 );
}
function init() {
window.setInterval(gamify, 500);
}
const whenDOMready = (func) => {
switch (document.readyState) {
case 'complete' :
case 'loaded' :
case 'interactive' :
func();
break;
default :
window.addEventListener('DOMContentLoaded', function(e) {
func();
});
}
};
whenDOMready(init);
@rushkeldon
Copy link
Author

rushkeldon commented Apr 17, 2024

This is meant to work in Chrome.

  1. install this Chrome extension
  2. visit your instance of Jira
  3. click on the icon for the above extension
  4. copy the JS above and paste it into the JavaScript panel
  5. copy the CSS above and paste it into the CSS panel
  6. save

Now when you drag and drop tickets in Jira you get sound effects!
I may add some visual FX later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment