Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,466 changes: 918 additions & 5,548 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@
"types": "tsc"
},
"dependencies": {
"@radix-ui/react-slider": "^1.1.1",
"clsx": "^1.2.1",
"debug": "^4.3.4",
"framer-motion": "^9.0.2",
"framer-motion": "^10.12.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.3.5",
"zustand": "^4.3.3"
},
"devDependencies": {
"@types/node": "^18.13.0",
"@types/debug": "^4.1.7",
"@types/node": "^18.13.0",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.10",
"@vitejs/plugin-react": "^3.1.0",
Expand All @@ -32,7 +33,7 @@
"postcss": "^8.4.21",
"prettier": "^2.8.4",
"tailwindcss": "^3.2.6",
"typescript": "^4.9.3",
"typescript": "^5.0.4",
"vite": "^4.1.0"
}
}
12 changes: 12 additions & 0 deletions src/components/Config/Slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Slider from "@radix-ui/react-slider";

const SliderComponent = () => {
return <Slider.Root>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumb />
</Slider.Root>
}

export default SliderComponent;
16 changes: 10 additions & 6 deletions src/components/Config/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ import { PropsWithChildren, useCallback, useState } from "react";
import c from "clsx";

import initGame from "@/modules/state/init";

import useSpotify from "@/modules/spotify/state";
import { getSpotifyToken, setSpotifyToken } from "@/modules/spotify/token";
import { setSpotifyToken } from "@/modules/spotify/token";
import { connectSpotifyPlayer } from "@/modules/spotify";
import useGame, { progressGameFlow } from "@/modules/state";
import { progressGameFlow } from "@/modules/state";
import ImageBackdrop from "../ImageBackdrop";

const ConfigWrapper: React.FC<PropsWithChildren> = ({ children }) => {
return (
<div className="w-full">
<ImageBackdrop>
<div className="w-full">
<div className="mx-12 my-10">
<h1 className="font-bold">Remix Icebreaker Game</h1>
<h1 className="font-bold text-3xl">Remix</h1>
<hr className="my-2 border-gray-600 w-full" />
{children}
</div>
</div>
</div>
</ImageBackdrop>
);
};

Expand Down Expand Up @@ -124,6 +126,8 @@ const Config: React.FC = () => {
onChange={onTokenValueChange}
/>



{/* Game Config */}
<div className="flex items-center gap-2">
<label htmlFor={CONFIG_TEXTAREA_NAME} className="text-sm">
Expand Down
9 changes: 1 addition & 8 deletions src/components/Game/RoundIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,11 @@ export const RoundIndicatorInner: React.FC<InnerProps> = ({
}) => {
const isPresent = useIsPresent();

// This will execute onComplete 500ms after the component is unmounted.
useEffect(() => {
!isPresent && setTimeout(onComplete, 500);
}, [isPresent, onComplete]);

/* useEffect(() => {
if (isPresent) return;
// The following code will run when the component is removed

onComplete(); // onComplete will progress the round and start the actual game/playing part
setTimeout(safeToRemove, 500);
}, [isPresent, onComplete, safeToRemove]); */

return (
<motion.div
className="flex flex-col items-center justify-center p-20"
Expand Down
14 changes: 4 additions & 10 deletions src/components/Game/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useEffect } from "react";

import ImageBackdrop from "../ImageBackdrop";

import { progressRoundSection } from "@/modules/state";
import { RoundIndicator } from "./RoundIndicator";
import { RoundSection } from "@/modules/state/types";
Expand All @@ -11,20 +10,15 @@ import config from "@/config";
import { setVolume } from "@/modules/spotify/state";
import { convertTimeArrayToMs } from "@/modules/time";
import { useCurrentRound } from "@/hooks/useCurrentRound";
import { getLastElementOfArray } from "@/util";

// drop-shadow(0px 2px 6px rgba(0,0,0,0.15))

const Game = () => {
const { index, section, roundData: currentRound } = useCurrentRound();

const bgImage =
currentRound.trackData.album.images[
currentRound.trackData.album.images.length - 1
].url;

/* const onRoundIndicatorComplete = useCallback(() => {
progressRoundSection();
}, [currentRound.songData.uri]); */
// The last image in the array is the smallest, so we use that to save on bandwidth.
const backgroundImage = getLastElementOfArray(currentRound.trackData.album.images)

useEffect(() => {
playTrack(
Expand All @@ -37,7 +31,7 @@ const Game = () => {
}, [currentRound.songData.uri, currentRound.songData.offset, index]);

return (
<ImageBackdrop imageSrc={bgImage}>
<ImageBackdrop imageSrc={backgroundImage.url}>
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center p-10 drop-shadow-md">
{section === RoundSection.START ? (
<RoundIndicator onComplete={progressRoundSection} />
Expand Down
25 changes: 0 additions & 25 deletions src/components/ImageBackdrop.tsx

This file was deleted.

13 changes: 13 additions & 0 deletions src/components/ImageBackdrop/backdrop.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* Generated with https://csshero.org/mesher/ */

.defaultMeshBackground {
background-color:#ff99fc;
background-image:
radial-gradient(at 69% 30%, hsla(215,77%,70%,1) 0px, transparent 50%),
radial-gradient(at 25% 59%, hsla(273,69%,68%,1) 0px, transparent 50%),
radial-gradient(at 9% 55%, hsla(253,60%,70%,1) 0px, transparent 50%),
radial-gradient(at 26% 18%, hsla(285,75%,65%,1) 0px, transparent 50%),
radial-gradient(at 30% 59%, hsla(13,71%,65%,1) 0px, transparent 50%),
radial-gradient(at 20% 90%, hsla(42,70%,69%,1) 0px, transparent 50%),
radial-gradient(at 50% 10%, hsla(211,92%,72%,1) 0px, transparent 50%);
}
39 changes: 39 additions & 0 deletions src/components/ImageBackdrop/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { PropsWithChildren } from "react";
import c from "clsx";

import backdropCSSClasses from './backdrop.module.css'


interface ImageBackdropProps extends PropsWithChildren {
imageSrc?: string;
}

const ImageBackdrop: React.FC<ImageBackdropProps> = ({
imageSrc,
children
}) => {
const hasImage = typeof imageSrc === "string" && imageSrc.length > 0;

const ImageTag = hasImage ? "img" : "div";
const imageClassName = c("w-full h-full object-cover opacity-80", {
[backdropCSSClasses.defaultMeshBackground]: !hasImage,
"blur-[128px]": hasImage,
});

return (
<div className="font-bold relative w-full h-screen bg-black text-white">
<ImageTag
src={hasImage ? imageSrc : undefined}
className={imageClassName}
alt="Backdrop"
/>
<div className="absolute top-0 left-0 w-full h-full">
{children}
</div>
</div>
);
};

export default ImageBackdrop;

export type { ImageBackdropProps };
3 changes: 1 addition & 2 deletions src/components/Title/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState } from "react";

import { motion } from "framer-motion";

import useGame, { progressGameFlow } from "@/modules/state";
Expand Down Expand Up @@ -27,7 +26,7 @@ const Title: React.FC = () => {
});

return (
<ImageBackdrop imageSrc={background}>
<ImageBackdrop>
{/* Title */}
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-start pl-20">
<motion.div
Expand Down
4 changes: 3 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
interface Configuration {
SPOTIFY_APP_CLIENT_ID: string;
SPOTIFY_WEB_PLAYBACK_DEVICE_NAME: string;
SPOTIFY_WEB_PLAYBACK_VOLUME: number;
SPOTIFY_WEB_PLAYBACK_VOLUME_DECREASED: number;
Expand All @@ -8,7 +9,8 @@ interface Configuration {
}

const config: Configuration = {
SPOTIFY_WEB_PLAYBACK_DEVICE_NAME: "Remix Internal Player",
SPOTIFY_APP_CLIENT_ID: "449cdfc9cf4f43a1a5c709315fde8bd3",
SPOTIFY_WEB_PLAYBACK_DEVICE_NAME: "Remix Internal Player", // This will show in all Spotify clients when the player is active.
SPOTIFY_WEB_PLAYBACK_VOLUME: 0.75, // Must be between 0 and 1
SPOTIFY_WEB_PLAYBACK_VOLUME_DECREASED: 0.1, // Plays between rounds
ROUND_INDICATOR_DISPLAY_TIME_IN_SECONDS: 2,
Expand Down
53 changes: 53 additions & 0 deletions src/modules/spotify/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import config from "@/config";

const clientId = config.SPOTIFY_APP_CLIENT_ID;
const redirectUri = "http://localhost:8080"; // @TODO Change me

function generateRandomString(length: number) {
let text = "";
let possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

let codeVerifier = generateRandomString(128);

async function generateCodeChallenge(codeVerifier: string) {
function base64encode(string: any) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(string)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}

const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await window.crypto.subtle.digest("SHA-256", data);

return base64encode(digest);
}

export const authenticate = () => {
generateCodeChallenge(codeVerifier).then(codeChallenge => {
let state = generateRandomString(16);
let scope = "user-read-private user-read-email"; // @TODO Change me

localStorage.setItem("code_verifier", codeVerifier);

let args = new URLSearchParams({
response_type: "code",
client_id: clientId,
scope: scope,
redirect_uri: redirectUri,
state: state,
code_challenge_method: "S256",
code_challenge: codeChallenge
});

window.location = "https://accounts.spotify.com/authorize?" + args;
});
}
4 changes: 2 additions & 2 deletions src/modules/spotify/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const prepareSpotifyWebPlaybackSDKReadyCallback = (): void => {
// Create a global callback called onSpotifyWebPlaybackSDKReady
// This will be called when the SDK is ready and Spotify.Player is available
// <any> skips type checking on window
(<any>window).onSpotifyWebPlaybackSDKReady = () => {
(window as any).onSpotifyWebPlaybackSDKReady = () => {
// You can now initialize Spotify.Player and use the SDK

useSpotify.setState({ sdkReady: true });
Expand All @@ -23,7 +23,7 @@ const prepareSpotifyWebPlaybackSDKReadyCallback = (): void => {
}
};

const player: SpotifyPlayer = new (<any>window).Spotify.Player(
const player: SpotifyPlayer = new (window as any).Spotify.Player(
spotifyPlayerConstructorOptions
);

Expand Down
6 changes: 3 additions & 3 deletions src/modules/spotify/web-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ export const requestTracksData = async (ids: string[]): Promise<Track[]> => {

export const playTrack = async (
uri: string,
offset?: number
offsetMs?: number
): Promise<void> => {
const body: Record<string, unknown> = {
uris: [uri]
};

if (offset) {
body["position_ms"] = offset;
if (offsetMs) {
body["position_ms"] = offsetMs;
}

const response = await fetch(
Expand Down
4 changes: 4 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ export const splitArrayIntoChunks = <T>(
}
return chunks;
};

export const getLastElementOfArray = <T>(array: T[]): T => {
return array[array.length - 1];
}