feat: add minutes listened to ui and fix image drop

pull/52/head
Gabe Farrell 5 months ago
parent 0042e3f3bd
commit d84519fa21

@ -1,7 +1,12 @@
# v0.1.0
## Features
- You can now search and merge items by their ID! Just preface the id with `id:`. E.g. `id:123` (#26)
## Enhancements
- Track durations will now be updated using MusicBrainz data where possible, if the duration was not provided by the request. (#27)
- You can now search and merge items by their ID! Just preface the id with `id:`. E.g. `id:123` (#26)
- Hovering over any "hours listened" statistic will now also show the minutes listened.
## Fixes
- Navigating from one page directly to another and then changing the image via drag-and-drop now works as expected. (#25)
- Fixed a bug that caused updated usernames with uppercase letters to create login failures.

@ -12,6 +12,9 @@ postgres.schemadump:
postgres.run:
docker run --name koito-db -p 5432:5432 -e POSTGRES_PASSWORD=secret -d postgres
postgres.run-scratch:
docker run --name koito-scratch -p 5433:5432 -e POSTGRES_PASSWORD=secret -d postgres
postgres.start:
docker start koito-db
@ -21,9 +24,15 @@ postgres.stop:
postgres.remove:
docker stop koito-db && docker rm koito-db
postgres.remove-scratch:
docker stop koito-scratch && docker rm koito-scratch
api.debug:
KOITO_ALLOWED_HOSTS=* KOITO_LOG_LEVEL=debug KOITO_CONFIG_DIR=test_config_dir KOITO_DATABASE_URL=postgres://postgres:secret@localhost:5432?sslmode=disable go run cmd/api/main.go
api.scratch:
KOITO_ALLOWED_HOSTS=* KOITO_LOG_LEVEL=debug KOITO_CONFIG_DIR=test_config_dir/scratch KOITO_DATABASE_URL=postgres://postgres:secret@localhost:5433?sslmode=disable go run cmd/api/main.go
api.test:
go test ./... -timeout 60s

@ -288,7 +288,7 @@ type Stats = {
track_count: number
album_count: number
artist_count: number
hours_listened: number
minutes_listened: number
}
type SearchResponse = {
albums: Album[]

@ -26,7 +26,7 @@ export default function AllTimeStats() {
<div>
<h2>All Time Stats</h2>
<div>
<span className={numberClasses}>{data.hours_listened}</span> Hours Listened
<span className={numberClasses} title={data.minutes_listened + " minutes"}>{Math.floor(data.minutes_listened / 60)}</span> Hours Listened
</div>
<div>
<span className={numberClasses}>{data.listen_count}</span> Plays

@ -3,11 +3,10 @@ import { useEffect } from 'react';
interface Props {
itemType: string,
id: number,
onComplete: Function
}
export default function ImageDropHandler({ itemType, id, onComplete }: Props) {
export default function ImageDropHandler({ itemType, onComplete }: Props) {
useEffect(() => {
const handleDragOver = (e: DragEvent) => {
console.log('dragover!!')
@ -25,7 +24,11 @@ export default function ImageDropHandler({ itemType, id, onComplete }: Props) {
const formData = new FormData();
formData.append('image', imageFile);
formData.append(itemType.toLowerCase()+'_id', String(id))
const pathname = window.location.pathname;
const segments = pathname.split('/');
const filteredSegments = segments.filter(segment => segment !== '');
const lastSegment = filteredSegments[filteredSegments.length - 1];
formData.append(itemType.toLowerCase()+'_id', lastSegment)
replaceImage(formData).then((r) => {
if (r.status >= 200 && r.status < 300) {
onComplete()

@ -43,7 +43,7 @@ export default function Album() {
}}
subContent={<div className="flex flex-col gap-2 items-start">
{album.listen_count && <p>{album.listen_count} play{ album.listen_count > 1 ? 's' : ''}</p>}
{<p>{timeListenedString(album.time_listened)}</p>}
{<p title={Math.floor(album.time_listened / 60) + " minutes"}>{timeListenedString(album.time_listened)}</p>}
</div>}
>
<div className="mt-10">

@ -49,7 +49,7 @@ export default function Artist() {
}}
subContent={<div className="flex flex-col gap-2 items-start">
{artist.listen_count && <p>{artist.listen_count} play{ artist.listen_count > 1 ? 's' : ''}</p>}
{<p>{timeListenedString(artist.time_listened)}</p>}
{<p title={Math.floor(artist.time_listened / 60) + " minutes"}>{timeListenedString(artist.time_listened)}</p>}
</div>}
>
<div className="mt-10">

@ -61,7 +61,7 @@ export default function MediaLayout(props: Props) {
transition: '1000',
}}
>
<ImageDropHandler itemType={props.type.toLowerCase() === 'artist' ? 'artist' : 'album'} id={props.imgItemId} onComplete={replaceImageCallback} />
<ImageDropHandler itemType={props.type.toLowerCase() === 'artist' ? 'artist' : 'album'} onComplete={replaceImageCallback} />
<title>{title}</title>
<meta property="og:title" content={title} />
<meta

@ -46,7 +46,7 @@ export default function Track() {
subContent={<div className="flex flex-col gap-2 items-start">
<Link to={`/album/${track.album_id}`}>appears on {album.title}</Link>
{track.listen_count && <p>{track.listen_count} play{ track.listen_count > 1 ? 's' : ''}</p>}
{<p>{timeListenedString(track.time_listened)}</p>}
{<p title={Math.floor(track.time_listened / 60) + " minutes"}>{timeListenedString(track.time_listened)}</p>}
</div>}
>
<div className="mt-10">

@ -10,11 +10,11 @@ import (
)
type StatsResponse struct {
ListenCount int64 `json:"listen_count"`
TrackCount int64 `json:"track_count"`
AlbumCount int64 `json:"album_count"`
ArtistCount int64 `json:"artist_count"`
HoursListened int64 `json:"hours_listened"`
ListenCount int64 `json:"listen_count"`
TrackCount int64 `json:"track_count"`
AlbumCount int64 `json:"album_count"`
ArtistCount int64 `json:"artist_count"`
MinutesListened int64 `json:"minutes_listened"`
}
func StatsHandler(store db.DB) http.HandlerFunc {
@ -79,11 +79,11 @@ func StatsHandler(store db.DB) http.HandlerFunc {
l.Debug().Msg("StatsHandler: Successfully fetched statistics")
utils.WriteJSON(w, http.StatusOK, StatsResponse{
ListenCount: listens,
TrackCount: tracks,
AlbumCount: albums,
ArtistCount: artists,
HoursListened: timeListenedS / 60 / 60,
ListenCount: listens,
TrackCount: tracks,
AlbumCount: albums,
ArtistCount: artists,
MinutesListened: timeListenedS / 60,
})
}
}

@ -447,7 +447,7 @@ func TestStats(t *testing.T) {
assert.EqualValues(t, 3, actual.TrackCount)
assert.EqualValues(t, 3, actual.AlbumCount)
assert.EqualValues(t, 3, actual.ArtistCount)
assert.EqualValues(t, 0, actual.HoursListened)
assert.EqualValues(t, 11, actual.MinutesListened)
}
func TestListenActivity(t *testing.T) {

Loading…
Cancel
Save