From 4a0e0fdb8942d68e003425be2f027c2487cc3390 Mon Sep 17 00:00:00 2001 From: Ryan Fernandez Date: Sun, 22 Jan 2023 14:44:27 -0600 Subject: [PATCH] add confetti animation on level up --- package-lock.json | 46 ++++++++++++++++++++++ package.json | 1 + src/Components/Session.js | 83 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 885ab91..e384467 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@testing-library/user-event": "^13.5.0", "dayjs": "^1.11.7", "react": "^18.2.0", + "react-canvas-confetti": "^1.3.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" @@ -4312,6 +4313,11 @@ "@types/node": "*" } }, + "node_modules/@types/canvas-confetti": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.4.2.tgz", + "integrity": "sha512-t45KUDHlwrD9PJVRHc5z1SlXhO82BQEgMKUXGEV1KnWLFMPA6Y5LfUsLTHHzH9KcKDHZLEiYYH5nIDcjRKWNTg==" + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -6131,6 +6137,15 @@ } ] }, + "node_modules/canvas-confetti": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.5.1.tgz", + "integrity": "sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==", + "funding": { + "type": "donate", + "url": "https://www.paypal.me/kirilvatev" + } + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -14683,6 +14698,18 @@ "node": ">=14" } }, + "node_modules/react-canvas-confetti": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-canvas-confetti/-/react-canvas-confetti-1.3.0.tgz", + "integrity": "sha512-5qBYTux3GKIu77SZ72oZYk4yvy1r4Z8q/BgIuO7mZrkqv/wH9vM7Yr/BzcqApGy9qZtIctF1Q41EYyw3aSDBmg==", + "dependencies": { + "@types/canvas-confetti": "1.4.2", + "canvas-confetti": "1.5.1" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -20569,6 +20596,11 @@ "@types/node": "*" } }, + "@types/canvas-confetti": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.4.2.tgz", + "integrity": "sha512-t45KUDHlwrD9PJVRHc5z1SlXhO82BQEgMKUXGEV1KnWLFMPA6Y5LfUsLTHHzH9KcKDHZLEiYYH5nIDcjRKWNTg==" + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -21975,6 +22007,11 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001388.tgz", "integrity": "sha512-znVbq4OUjqgLxMxoNX2ZeeLR0d7lcDiE5uJ4eUiWdml1J1EkxbnQq6opT9jb9SMfJxB0XA16/ziHwni4u1I3GQ==" }, + "canvas-confetti": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.5.1.tgz", + "integrity": "sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==" + }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -27978,6 +28015,15 @@ "whatwg-fetch": "^3.6.2" } }, + "react-canvas-confetti": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-canvas-confetti/-/react-canvas-confetti-1.3.0.tgz", + "integrity": "sha512-5qBYTux3GKIu77SZ72oZYk4yvy1r4Z8q/BgIuO7mZrkqv/wH9vM7Yr/BzcqApGy9qZtIctF1Q41EYyw3aSDBmg==", + "requires": { + "@types/canvas-confetti": "1.4.2", + "canvas-confetti": "1.5.1" + } + }, "react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", diff --git a/package.json b/package.json index 70c9762..75dbe80 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@testing-library/user-event": "^13.5.0", "dayjs": "^1.11.7", "react": "^18.2.0", + "react-canvas-confetti": "^1.3.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" diff --git a/src/Components/Session.js b/src/Components/Session.js index 6a393d3..4f69a24 100644 --- a/src/Components/Session.js +++ b/src/Components/Session.js @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react" +import { useState, useEffect, useCallback, useRef } from "react" import { getCorrespondingBreak, POMODORO_TIMER } from "../Utils/sessionUtils" import alarm from "../audio/alarm.mp3" import { DEFAULT_MSG_LENGTH, NO_SESSION_ERROR } from "../Utils/errorUtils" @@ -7,6 +7,16 @@ import { getUpdatedLevelProgress } from "../Utils/levelUtils" import { changeStatus, STATUS } from "../Utils/status" import { Alert, Button, Divider, MenuItem, Select } from "@mui/material" import { ArrowBack } from "@mui/icons-material" +import ReactCanvasConfetti from 'react-canvas-confetti' + +const confettiCanvasStyles = { + position: "fixed", + pointerEvents: "none", + width: "100%", + height: "100%", + top: 0, + left: 0, +} const DEFAULT_DOCUMENT_TITLE = "routyne" const WORKING_DEFAULT = 25*60 @@ -26,6 +36,7 @@ const Session = ({ session, setInSessionView, activeListName }) => { const [isBreak, setIsBreak] = useState(false) const [isWorking, setIsWorking] = useState(false) const [totalWorkingSeconds, setTotalWorkingSeconds] = useState(0) + const refAnimationInstance = useRef(null) const sessionEndAlarm = new Audio(alarm) sessionEndAlarm.volume = 0.2 @@ -49,6 +60,70 @@ const Session = ({ session, setInSessionView, activeListName }) => { return () => clearTimeout(timer) }, [time]) // eslint-disable-line + const getInstance = useCallback((instance) => { + refAnimationInstance.current = instance + }, []) + + const makeShot = useCallback((particleRatio, opts) => { + refAnimationInstance.current && + refAnimationInstance.current({ + ...opts, + particleCount: Math.floor(200 * particleRatio) + }) + }, []) + + const fireConfetti = useCallback(() => { + makeShot(0.25, { + spread: 50, + startVelocity: 60, + angle: 30, + origin: { y: 0.3, x:0, }, + }) + makeShot(0.50, { + spread: 60, + startVelocity: 80, + angle: 20, + origin: { y: 0.3, x:0, }, + }) + makeShot(0.50, { + spread: 60, + startVelocity: 80, + angle: 20, + origin: { y: 0.5, x:0, }, + }) + makeShot(0.50, { + spread: 60, + startVelocity: 80, + angle: 20, + origin: { y: 0.8, x:0, }, + }) + + makeShot(0.25, { + spread: 50, + startVelocity: 60, + angle: 170, + origin: { y: 0.3, x:1, }, + }) + makeShot(0.50, { + spread: 60, + startVelocity: 80, + angle: 150, + origin: { y: 0.3, x:1, }, + }) + makeShot(0.50, { + spread: 60, + startVelocity: 80, + angle: 150, + origin: { y: 0.5, x:1, }, + }) + makeShot(0.50, { + spread: 60, + startVelocity: 80, + angle: 150, + origin: { y: 0.8, x:1, }, + }) + }, [makeShot]) + const resetState = () => { setIsWorking(false) setIsBreak(false) @@ -164,7 +239,10 @@ const Session = ({ session, setInSessionView, activeListName }) => { let currLevel = Number(data.level) let currXP = Number(data.xp) + Math.ceil(totalWorkingSeconds / 60) - let { updatedLevel, updatedXP} = getUpdatedLevelProgress(currLevel, currXP) + let { updatedLevel, updatedXP } = getUpdatedLevelProgress(currLevel, currXP) + if (currLevel !== updatedLevel) { + fireConfetti() + } let updatedSessionData = await supabase .from("users") .update({level: updatedLevel, xp: updatedXP}) @@ -194,6 +272,7 @@ const Session = ({ session, setInSessionView, activeListName }) => { return (
+