Skip to content

Commit

Permalink
Add context menu
Browse files Browse the repository at this point in the history
  • Loading branch information
eirki committed Jul 30, 2022
1 parent eede6d4 commit 078d780
Show file tree
Hide file tree
Showing 12 changed files with 342 additions and 26 deletions.
9 changes: 9 additions & 0 deletions backend/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

scope = [
"user-library-read",
"user-library-modify",
"user-read-playback-state",
"user-modify-playback-state",
"user-read-currently-playing",
Expand Down Expand Up @@ -115,6 +116,14 @@ def albums(spotify: Spotify) -> dict[str, list[dict]]:
return data


def add_album(spotify: Spotify, album_id: str) -> None:
spotify.current_user_saved_albums_add([album_id])


def remove_album(spotify: Spotify, album_id: str) -> None:
spotify.current_user_saved_albums_delete([album_id])


class AlbumResponse(TypedDict):
added_at: str # "2021-05-13T07:00:09Z"
album: Album
Expand Down
59 changes: 58 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
from typing import Optional

from fastapi import Cookie, FastAPI, Request
from fastapi import Cookie, FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
Expand Down Expand Up @@ -49,7 +49,64 @@ async def root(request: Request, spotify_token: Optional[str] = Cookie(None)):
"index.html",
{"request": request, "data": albums},
)
if cache:
set_token_cookie(response, cache)
return response


@app.get("/albums")
async def albums(spotify_token: Optional[str] = Cookie(None)):
spotify_token_j = json.loads(spotify_token) if spotify_token else None
authed, auth_manager, cache = api.check_auth(spotify_token_j)
if not authed:
return RedirectResponse(auth_manager.get_authorize_url())
spotify = Spotify(auth_manager=auth_manager)
albums = api.albums(spotify)
response = JSONResponse(content=albums)
if cache:
set_token_cookie(response, cache)
cache.delete_cached_token()
return response


@app.get("/add_album/{album_id}")
async def add_album(album_id: str, spotify_token: Optional[str] = Cookie(None)):
spotify_token_j = json.loads(spotify_token) if spotify_token else None
authed, auth_manager, cache = api.check_auth(spotify_token_j)
if not authed:
raise HTTPException(status_code=403, detail="Authentication failed")
spotify = Spotify(auth_manager=auth_manager)
api.add_album(spotify, album_id)
response = JSONResponse(content={"success": True})
if cache:
set_token_cookie(response, cache)
cache.delete_cached_token()
return response


@app.get("/remove_album/{album_id}")
async def remove_album(album_id: str, spotify_token: Optional[str] = Cookie(None)):
spotify_token_j = json.loads(spotify_token) if spotify_token else None
authed, auth_manager, cache = api.check_auth(spotify_token_j)
if not authed:
raise HTTPException(status_code=403, detail="Authentication failed")
spotify = Spotify(auth_manager=auth_manager)
api.remove_album(spotify, album_id)
response = JSONResponse(content={"success": True})
if cache:
set_token_cookie(response, cache)
cache.delete_cached_token()
return response


@app.get("/authed")
async def authed(spotify_token: Optional[str] = Cookie(None)):
spotify_token_j = json.loads(spotify_token) if spotify_token else None
authed, auth_manager, cache = api.check_auth(spotify_token_j)
success = authed is not None
res = {"success": success}
response = JSONResponse(content=res)
if cache:
set_token_cookie(response, cache)
cache.delete_cached_token()
return res
2 changes: 1 addition & 1 deletion backend/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version = "22.07.29.0"
version = "22.07.30.0"
28 changes: 27 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.2.37"
"@imengyu/vue3-context-menu": "^1.0.9",
"vue": "^3.2.37",
"vue-toastification": "^2.0.0-rc.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.0.0",
Expand Down
96 changes: 86 additions & 10 deletions frontend/src/components/Album.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,18 @@
padding: `${padding}px`
}" @click="play">
<img class="album-art" :src="artUrl" :alt="album.name" :width=cellSize :height=cellSize @mouseover="hover"
@mouseleave="clearHover" />
@mouseleave="clearHover" @contextmenu="onContextMenu($event)" />

</div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import ContextMenu from '@imengyu/vue3-context-menu'
import { useToast } from "vue-toastification";
import Spinner from "./Spinner.vue";
import type { AlbumT, OverlayT } from '../types.js'
// import Toasted from "vue-toasted";
// import Vue from "vue";
// Vue.use(Toasted, {
// theme: "toasted-primary",
// position: "bottom-center",
// duration: 5000,
// });
const props = defineProps<{
Expand All @@ -29,14 +26,22 @@ const props = defineProps<{
nRows?: number
nCols?: number
nColsAll?: number
isInLibrary: boolean
}>()
const emit = defineEmits<{
(e: "play", arg: AlbumT): void
(e: "hover", arg: OverlayT): void
(e: "clearHover"): void
(e: "refreshAlbums", cb: (() => void)): void
}>()
let albumDesc = computed(
() =>
(props.album.artists.length === 1
? props.album.artists[0].name + " - "
: "")
+ `${props.album.name}`)
const colPosition = computed(() => (props.index === undefined || props.nCols === undefined ? 1 : props.index % props.nCols))
const rowPosition = computed(() => (props.index === undefined || props.nCols === undefined ? 1 : Math.floor(props.index / props.nCols)))
Expand All @@ -62,6 +67,9 @@ const artUrl = computed(() => {
}
})
const toast = useToast();
function hover() {
let arg: OverlayT = {
leftHalf: leftHalf.value,
Expand All @@ -80,10 +88,78 @@ function clearHover() {
function play() {
emit("play", props.album);
emit("clearHover");
// Vue.toasted.clear();
// Vue.toasted.show(this.playingMessage);
toast(albumDesc.value)
}
interface arg { remove: boolean }
function toggleLibrary({ remove }: arg) {
let spinnerToastId = toast(Spinner, { timeout: false })
let doneCallback = () => {
toast.dismiss(spinnerToastId)
toast(`${remove ? "Remov" : "Add"}ed ${albumDesc.value}`)
}
fetch(`/${remove ? "remove" : "add"}_album/${props.album.id}`)
.then(res => res.json())
.then(data => {
if (!data.success) {
throw new Error('failed')
}
})
.then(() => emit("refreshAlbums", doneCallback))
.catch(error => {
console.log(error)
toast.dismiss(spinnerToastId)
toast.error("Server error")
})
}
function onContextMenu(e: MouseEvent) {
e.preventDefault();
ContextMenu.showContextMenu({
customClass: "context-menu",
iconFontClass: "context-menu__icon",
x: e.x,
y: e.y,
items: [
{
label: albumDesc.value,
disabled: true,
},
{
label: "Go to album",
onClick: () => {
let url = `https://open.spotify.com/album/${props.album.id}`
window.open(url, "_blank")
}
},
props.album.artists.length === 1 ?
{
label: "Go to artist",
onClick: () => {
let url = `https://open.spotify.com/artist/${props.album.artists[0].id}`
window.open(url, "_blank")
}
}
:
{
label: "Go to artist",
children: props.album.artists.map(artist => ({
label: artist.name,
onClick: () => {
let url = `https://open.spotify.com/artist/${artist.id}`
window.open(url, "_blank")
}
})),
},
{
label: `${props.isInLibrary ? "Remove from" : "Add to"} library`,
onClick: () => toggleLibrary({ remove: props.isInLibrary })
}
]
})
}
</script>

<style scoped>
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/components/AlbumColumn.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<template>
<div class="albumColumn">
<Album v-for="(album, i) in albums" class="album" :key="i" :album="album" :cellSize="cellSize" :padding="padding"
:overlayMultiplier=overlayMultiplier v-on:play="propagatePlay($event)" v-on:hover="propagateHover($event)"
v-on:clearHover=propagateClearHover />
:overlayMultiplier=overlayMultiplier :isInLibrary=areInLibrary v-on:play="propagatePlay($event)"
v-on:hover="propagateHover($event)" v-on:clearHover=propagateClearHover
v-on:refreshAlbums="propagateRefreshAlbums" />
</div>
</template>

Expand All @@ -18,12 +19,14 @@ const props = defineProps<{
padding: number
albums: AlbumT[]
nRows: number
areInLibrary: boolean
}>()
const emit = defineEmits<{
(e: "play", arg: AlbumT): void
(e: "hover", arg: OverlayT): void
(e: "clearHover"): void
(e: "refreshAlbums", cb: (() => void)): void
}>()
const cellSizeStr = computed(() => `${props.cellSize}px`)
Expand All @@ -42,6 +45,10 @@ function propagateClearHover() {
emit("clearHover");
}
function propagateRefreshAlbums(cb: (() => void)) {
emit("refreshAlbums", cb);
}
</script>

Expand Down
13 changes: 10 additions & 3 deletions frontend/src/components/AlbumGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
:alt="overlayAlbum.data.name" :width="overlaySize" :height="overlaySize" />
</div>
<div class="album-grid">
<Album v-for="(album, i) in albums" :key="i" :album="album" :cellSize="cellSize" :padding="padding"
:overlayMultiplier=overlayMultiplier :nCols=nCols :nRows=nRows :index=i :nColsAll=nColsAll
v-on:play="propagatePlay($event)" v-on:hover="hover($event)" v-on:clearHover="clearHover" />
<Album v-for="(album, i) in albums" :key="`${i}-${album.id}`" :album="album" :cellSize="cellSize"
:padding="padding" :overlayMultiplier=overlayMultiplier :nCols=nCols :nRows=nRows :index=i :nColsAll=nColsAll
:isInLibrary=true v-on:play="propagatePlay($event)" v-on:hover="hover($event)" v-on:clearHover="clearHover"
v-on:refreshAlbums="propagateRefreshAlbums" />
</div>
</div>
</template>
Expand All @@ -32,6 +33,7 @@ const props = defineProps<{
const emit = defineEmits<{
(e: "play", arg: AlbumT): void
(e: "refreshAlbums", arg: (() => void)): void
}>()
const overlayAlbum: Ref<OverlayT | null> = ref(null);
Expand Down Expand Up @@ -69,6 +71,11 @@ function hover(arg: OverlayT) {
function clearHover() {
overlayAlbum.value = null
}
function propagateRefreshAlbums(cb: (() => void)) {
emit("refreshAlbums", cb);
}
</script>


Expand Down
Loading

0 comments on commit 078d780

Please sign in to comment.