wip search
This commit is contained in:
parent
e44850ff2d
commit
43d37a3d06
12 changed files with 376 additions and 100 deletions
17
deno.lock
generated
17
deno.lock
generated
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"version": "5",
|
||||
"specifiers": {
|
||||
"npm:@tidal-music/api@0.7": "0.7.0",
|
||||
"npm:@tidal-music/auth@^1.4.0": "1.4.0",
|
||||
"npm:@tidal-music/event-producer@^2.4.0": "2.4.0",
|
||||
"npm:@tidal-music/player@~0.11.2": "0.11.2",
|
||||
|
|
@ -550,6 +551,12 @@
|
|||
"tslib"
|
||||
]
|
||||
},
|
||||
"@tidal-music/api@0.7.0": {
|
||||
"integrity": "sha512-WaogpHAlGhwPR/ScapgvyiXMwCIakUQPdR78LVus4vEcXr1m0oKyeuNu54Y+jMSFM26caIk2kPx75yKpiegSmA==",
|
||||
"dependencies": [
|
||||
"openapi-fetch"
|
||||
]
|
||||
},
|
||||
"@tidal-music/auth@1.4.0": {
|
||||
"integrity": "sha512-b/8n6aHYoMuzGbNTT4QOwm9xjTosHCn9F+Vi26Nq1x1OuK+XK9zbuAkSwma/C5eYHy4fPBTcSw82us5K5mUHmA==",
|
||||
"dependencies": [
|
||||
|
|
@ -1695,6 +1702,15 @@
|
|||
"regex-recursion"
|
||||
]
|
||||
},
|
||||
"openapi-fetch@0.15.0": {
|
||||
"integrity": "sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==",
|
||||
"dependencies": [
|
||||
"openapi-typescript-helpers"
|
||||
]
|
||||
},
|
||||
"openapi-typescript-helpers@0.0.15": {
|
||||
"integrity": "sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw=="
|
||||
},
|
||||
"p-limit@6.2.0": {
|
||||
"integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==",
|
||||
"dependencies": [
|
||||
|
|
@ -2309,6 +2325,7 @@
|
|||
"workspace": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@tidal-music/api@0.7",
|
||||
"npm:@tidal-music/auth@^1.4.0",
|
||||
"npm:@tidal-music/event-producer@^2.4.0",
|
||||
"npm:@tidal-music/player@~0.11.2",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tidal-music/api": "^0.7.0",
|
||||
"@tidal-music/auth": "^1.4.0",
|
||||
"@tidal-music/event-producer": "^2.4.0",
|
||||
"@tidal-music/player": "^0.11.2",
|
||||
|
|
|
|||
|
|
@ -5,10 +5,82 @@ import "../styles/index.css"
|
|||
|
||||
<Layout>
|
||||
<section id="guesses">
|
||||
<span class="incorrect">A bad answer - Someone</span>
|
||||
<span class="partial">Killer Queen - Queen</span>
|
||||
<span class="correct">Bohemian Rhapsody - Queen</span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span class="correct">The Correct Answer - Queen</span>
|
||||
<div class="search-container">
|
||||
<input type="text" id="search" placeholder="Make a guess here..." />
|
||||
<section id="autocomplete"
|
||||
><button data-title="Ruler of Everything" data-artists="3524912"
|
||||
>Ruler of Everything - Tally Hall</button
|
||||
><button data-title="King of Everything" data-artists="7774122"
|
||||
>King of Everything - Dominic Fike</button
|
||||
><button
|
||||
data-title="The Queen Of All Everything"
|
||||
data-artists="35020"
|
||||
>The Queen Of All Everything - OTT</button
|
||||
><button
|
||||
data-title="Ruler of Everything"
|
||||
data-artists="37670866"
|
||||
>Ruler of Everything - Chonny Jash</button
|
||||
><button
|
||||
data-title="Ruler of Everything"
|
||||
data-artists="15539806"
|
||||
>Ruler of Everything - Sheet Music Boss</button
|
||||
><button data-title="Ruler of Everything" data-artists="3616070"
|
||||
>Ruler of Everything - Samuel Ljungblahd</button
|
||||
><button
|
||||
data-title="Ruler of Everything"
|
||||
data-artists="16653822"
|
||||
>Ruler of Everything - MixAndMash</button
|
||||
><button
|
||||
data-title="Ruler Of Everything"
|
||||
data-artists="16653822"
|
||||
>Ruler Of Everything - MixAndMash</button
|
||||
><button
|
||||
data-title="Ruler of Everything"
|
||||
data-artists="30871574"
|
||||
>Ruler of Everything - TheRealSullyG</button
|
||||
><button
|
||||
data-title="Ruler of Everything"
|
||||
data-artists="31171068"
|
||||
>Ruler of Everything - Intelevande</button
|
||||
><button data-title="King of Everything" data-artists="3520382"
|
||||
>King of Everything - Wiz Khalifa</button
|
||||
><button
|
||||
data-title="The Theory of Everything"
|
||||
data-artists="4133018"
|
||||
>The Theory of Everything - Jóhann Jóhannsson</button
|
||||
><button
|
||||
data-title="Everything Reminds Me Of Her"
|
||||
data-artists="3501428"
|
||||
>Everything Reminds Me Of Her - Elliott Smith</button
|
||||
><button
|
||||
data-title="At the Bottom of Everything"
|
||||
data-artists="16906"
|
||||
>At the Bottom of Everything - Bright Eyes</button
|
||||
><button
|
||||
data-title="Everything You Do Is A Balloon"
|
||||
data-artists="3568992"
|
||||
>Everything You Do Is A Balloon - Boards of Canada</button
|
||||
><button data-title="Ruler of My Heart" data-artists="10532"
|
||||
>Ruler of My Heart - Irma Thomas</button
|
||||
><button data-title="Ruler Of My Heart" data-artists="10532"
|
||||
>Ruler Of My Heart - Irma Thomas</button
|
||||
><button
|
||||
data-title="Everything Reminds Me Of You"
|
||||
data-artists="48201581"
|
||||
>Everything Reminds Me Of You - Take Care</button
|
||||
><button
|
||||
data-title="Everything Is Free"
|
||||
data-artists="4752502,4972344"
|
||||
>Everything Is Free - Flock Of Dimes, Sylvan Esso</button
|
||||
><button data-title="Everything" data-artists="8718491"
|
||||
>Everything - J.I the Prince of N.Y</button
|
||||
></section
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
<div>
|
||||
<button id="play">
|
||||
|
|
@ -36,86 +108,5 @@ import "../styles/index.css"
|
|||
</div>
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
import * as player from "@tidal-music/player"
|
||||
import * as auth from "@tidal-music/auth"
|
||||
import * as eventProducer from "@tidal-music/event-producer"
|
||||
|
||||
await auth.init({
|
||||
clientId: "NkcXqihOmrfZdBla",
|
||||
clientSecret: "e6ldlWH48BEBOUZV1uIyIWJOf1KUGUEeH6qHkFvNjeU=",
|
||||
credentialsStorageKey: "ZvZbtTWzx5@1zfiWUluxWD9@di17e1da",
|
||||
})
|
||||
|
||||
player.setCredentialsProvider(auth.credentialsProvider)
|
||||
player.setEventSender(eventProducer)
|
||||
|
||||
class ExtendableTimeout {
|
||||
private timerId: ReturnType<typeof setTimeout>
|
||||
private timeStart: number
|
||||
private fn: () => void
|
||||
|
||||
constructor(fn: () => void, time: number) {
|
||||
this.fn = fn
|
||||
this.timeStart = Date.now()
|
||||
this.timerId = setTimeout(fn, time)
|
||||
}
|
||||
|
||||
extend(time: number): void {
|
||||
clearTimeout(this.timerId)
|
||||
|
||||
const elapsed = Date.now() - this.timeStart
|
||||
const newTime = Math.max(0, time - elapsed)
|
||||
|
||||
this.timerId = setTimeout(this.fn, newTime)
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
clearTimeout(this.timerId)
|
||||
}
|
||||
}
|
||||
|
||||
const play = document.querySelector("#play")
|
||||
const played = document.querySelector("#played")
|
||||
const skip = document.querySelector("#skip")
|
||||
const available = document.querySelector("#available")
|
||||
|
||||
let phase = 1
|
||||
let timer: ExtendableTimeout | null
|
||||
|
||||
const getTimeout = (phase: number) => (2 ** phase - 1) * 1000
|
||||
|
||||
const playVideo = async () => {
|
||||
await player.load({
|
||||
productId: "36737274",
|
||||
productType: "track",
|
||||
sourceId: "",
|
||||
sourceType: "",
|
||||
})
|
||||
await player.play()
|
||||
played?.classList.add("playing")
|
||||
|
||||
timer?.cancel()
|
||||
timer = new ExtendableTimeout(() => {
|
||||
player.pause()
|
||||
timer = null
|
||||
played?.classList.remove("playing")
|
||||
}, getTimeout(phase))
|
||||
}
|
||||
|
||||
play?.addEventListener("click", playVideo)
|
||||
|
||||
skip?.addEventListener("click", () => {
|
||||
if (phase >= 4) return alert("FAIL!!!!")
|
||||
phase += 1
|
||||
|
||||
// @ts-expect-error
|
||||
available.style["grid-column-end"] = phase + 1
|
||||
|
||||
if (timer == null) {
|
||||
playVideo()
|
||||
} else {
|
||||
timer.extend(getTimeout(phase))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<script src="../scripts/playSong.ts"></script>
|
||||
<script src="../scripts/search.ts"></script>
|
||||
|
|
|
|||
9
src/scripts/auth.ts
Normal file
9
src/scripts/auth.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import * as auth from "@tidal-music/auth"
|
||||
|
||||
await auth.init({
|
||||
clientId: "NkcXqihOmrfZdBla",
|
||||
clientSecret: "e6ldlWH48BEBOUZV1uIyIWJOf1KUGUEeH6qHkFvNjeU=",
|
||||
credentialsStorageKey: "ZvZbtTWzx5@1zfiWUluxWD9@di17e1da",
|
||||
})
|
||||
|
||||
export default auth
|
||||
3
src/scripts/client.ts
Normal file
3
src/scripts/client.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { createAPIClient } from "@tidal-music/api"
|
||||
import auth from "./auth"
|
||||
export const client = createAPIClient(auth.credentialsProvider)
|
||||
10
src/scripts/debounce.ts
Normal file
10
src/scripts/debounce.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export const debounce = <T extends (...args: any[]) => void>(
|
||||
fn: T
|
||||
): ((...args: Parameters<T>) => void) => {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => fn(...args), 500)
|
||||
}
|
||||
}
|
||||
47
src/scripts/playSong.ts
Normal file
47
src/scripts/playSong.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import player from "./player"
|
||||
import { ExtendableTimeout } from "./timeout"
|
||||
|
||||
const play = document.querySelector("#play")
|
||||
const played = document.querySelector("#played")
|
||||
const skip = document.querySelector("#skip")
|
||||
const available = document.querySelector("#available")
|
||||
|
||||
let phase = 1
|
||||
let timer: ExtendableTimeout | null
|
||||
|
||||
const getTimeout = (phase: number) => (2 ** phase - 1) * 1000
|
||||
|
||||
const playSong = async () => {
|
||||
played?.classList.remove("playing")
|
||||
await player.load({
|
||||
productId: "36737274",
|
||||
productType: "track",
|
||||
sourceId: "",
|
||||
sourceType: "",
|
||||
})
|
||||
await player.play()
|
||||
played?.classList.add("playing")
|
||||
|
||||
timer?.cancel()
|
||||
timer = new ExtendableTimeout(() => {
|
||||
player.pause()
|
||||
timer = null
|
||||
played?.classList.remove("playing")
|
||||
}, getTimeout(phase))
|
||||
}
|
||||
|
||||
play?.addEventListener("click", playSong)
|
||||
|
||||
skip?.addEventListener("click", () => {
|
||||
if (phase >= 4) return alert("FAIL!!!!") // TODO
|
||||
phase += 1
|
||||
|
||||
// @ts-expect-error
|
||||
available.style["grid-column-end"] = phase + 1
|
||||
|
||||
if (timer == null) {
|
||||
playSong()
|
||||
} else {
|
||||
timer.extend(getTimeout(phase))
|
||||
}
|
||||
})
|
||||
8
src/scripts/player.ts
Normal file
8
src/scripts/player.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import * as eventProducer from "@tidal-music/event-producer"
|
||||
import * as player from "@tidal-music/player"
|
||||
import auth from "./auth"
|
||||
|
||||
player.setCredentialsProvider(auth.credentialsProvider)
|
||||
player.setEventSender(eventProducer)
|
||||
|
||||
export default player
|
||||
89
src/scripts/search.ts
Normal file
89
src/scripts/search.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { client } from "./client"
|
||||
import { debounce } from "./debounce"
|
||||
|
||||
const search = document.querySelector("#search") as HTMLInputElement
|
||||
|
||||
search?.addEventListener(
|
||||
"input",
|
||||
debounce(async (event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
const value = target.value
|
||||
const tracks = await client.GET(
|
||||
"/searchResults/{id}/relationships/tracks",
|
||||
{
|
||||
params: {
|
||||
path: { id: value },
|
||||
query: { countryCode: "US" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!tracks.response.ok)
|
||||
alert(
|
||||
"An error occurred while fetching tracks, please try again later."
|
||||
)
|
||||
|
||||
if (tracks.data?.data == null) return
|
||||
|
||||
const withArtists = await client.GET("/tracks", {
|
||||
params: {
|
||||
query: {
|
||||
"filter[id]": tracks.data.data?.map((track) => track.id),
|
||||
include: ["artists"],
|
||||
countryCode: "US",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (!withArtists.response.ok)
|
||||
alert(
|
||||
"An error occurred while fetching track info, please try again later."
|
||||
)
|
||||
|
||||
const autocomplete = document.querySelector("#autocomplete")
|
||||
|
||||
autocomplete?.replaceChildren(
|
||||
...(tracks.data?.data
|
||||
?.map((orderedTrack) => {
|
||||
const track = withArtists.data?.data.find(
|
||||
(track) => track.id == orderedTrack.id
|
||||
)
|
||||
if (track == null) return
|
||||
|
||||
if (
|
||||
track.attributes == null ||
|
||||
!(
|
||||
"title" in track.attributes &&
|
||||
"relationships" in track
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
const span = document.createElement("button")
|
||||
|
||||
span.dataset.title = track.attributes.title
|
||||
span.dataset.artists = track.relationships?.artists.data
|
||||
?.map((artist) => artist.id)
|
||||
.join()
|
||||
span.textContent = `${
|
||||
track.attributes.title
|
||||
} - ${track.relationships?.artists.data
|
||||
?.map((songArtist) => {
|
||||
const artist = withArtists.data?.included?.find(
|
||||
(artist) => artist.id == songArtist.id
|
||||
)
|
||||
if (
|
||||
artist?.attributes == null ||
|
||||
!("name" in artist.attributes)
|
||||
)
|
||||
return
|
||||
return artist?.attributes?.name
|
||||
})
|
||||
.join(", ")}`
|
||||
|
||||
return span
|
||||
})
|
||||
.filter((element) => element != null) as HTMLSpanElement[])
|
||||
)
|
||||
})
|
||||
)
|
||||
24
src/scripts/timeout.ts
Normal file
24
src/scripts/timeout.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
export class ExtendableTimeout {
|
||||
private timerId: ReturnType<typeof setTimeout>
|
||||
private timeStart: number
|
||||
private fn: () => void
|
||||
|
||||
constructor(fn: () => void, time: number) {
|
||||
this.fn = fn
|
||||
this.timeStart = Date.now()
|
||||
this.timerId = setTimeout(fn, time)
|
||||
}
|
||||
|
||||
extend(time: number): void {
|
||||
clearTimeout(this.timerId)
|
||||
|
||||
const elapsed = Date.now() - this.timeStart
|
||||
const newTime = Math.max(0, time - elapsed)
|
||||
|
||||
this.timerId = setTimeout(this.fn, newTime)
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
clearTimeout(this.timerId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
main > * {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
|
@ -10,26 +11,87 @@ main > * {
|
|||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
|
||||
& span {
|
||||
& > span {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-weight: bold;
|
||||
|
||||
background-color: #000;
|
||||
background: rgb(26, 26, 26);
|
||||
height: 2.4rem;
|
||||
width: 100%;
|
||||
|
||||
border-radius: 2rem;
|
||||
|
||||
&.correct {
|
||||
background-image: linear-gradient(
|
||||
background: linear-gradient(
|
||||
to bottom right,
|
||||
var(--brand-pink) 0%,
|
||||
var(--brand-purple) 100%
|
||||
);
|
||||
}
|
||||
|
||||
&.partial {
|
||||
background: linear-gradient(
|
||||
to bottom right,
|
||||
rgb(255, 135, 90) 0%,
|
||||
rgb(255, 196, 69) 100%
|
||||
);
|
||||
}
|
||||
|
||||
&.incorrect {
|
||||
background: linear-gradient(
|
||||
to bottom right,
|
||||
rgb(185, 74, 72) 0%,
|
||||
rgb(157, 38, 29) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
& .search-container {
|
||||
position: relative;
|
||||
&,
|
||||
& > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
& > input {
|
||||
border: none;
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
background: rgb(220, 218, 218);
|
||||
|
||||
&:focus {
|
||||
outline: 0.3rem solid var(--brand-pink);
|
||||
}
|
||||
}
|
||||
|
||||
& > section {
|
||||
border-radius: 1rem;
|
||||
background-color: rgb(46, 46, 46);
|
||||
|
||||
max-height: 30vh;
|
||||
overflow: scroll;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
position: absolute;
|
||||
top: 3.5rem;
|
||||
z-index: 3;
|
||||
|
||||
border: 0.2rem solid var(--brand-gray);
|
||||
|
||||
& button {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: 0.2rem solid var(--brand-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
display: flex;
|
||||
|
|
@ -10,6 +16,7 @@ body {
|
|||
:root {
|
||||
--brand-purple: #6e06ff;
|
||||
--brand-pink: #cc56d0;
|
||||
--brand-gray: rgb(98, 98, 98);
|
||||
|
||||
background: linear-gradient(black, rgb(18, 2, 38));
|
||||
font-family: sans-serif;
|
||||
|
|
@ -27,7 +34,7 @@ nav {
|
|||
align-items: center;
|
||||
padding: 2rem 3rem;
|
||||
|
||||
height: 5rem;
|
||||
height: 8rem;
|
||||
& span {
|
||||
font-size: 6rem;
|
||||
text-shadow: 2px 0 var(--brand-purple), -2px 0 var(--brand-purple),
|
||||
|
|
@ -44,26 +51,33 @@ main {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
gap: 1rem;
|
||||
gap: 1.5rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(80%);
|
||||
}
|
||||
}
|
||||
|
||||
:not(#autocomplete) > button {
|
||||
--border-radius: 2rem;
|
||||
position: relative;
|
||||
background: white;
|
||||
color: black;
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
padding: 0.2rem 1rem;
|
||||
margin: 1rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: rgb(211, 211, 211);
|
||||
}
|
||||
|
|
@ -73,14 +87,15 @@ button {
|
|||
}
|
||||
}
|
||||
|
||||
button::before {
|
||||
:not(#autocomplete) > button::before {
|
||||
--border-size: 5px;
|
||||
box-sizing: content-box;
|
||||
content: "";
|
||||
z-index: -1;
|
||||
border-radius: var(--border-radius);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(
|
||||
background: linear-gradient(
|
||||
to bottom right,
|
||||
var(--brand-pink) 0%,
|
||||
var(--brand-purple) 100%
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue