mirror of
https://github.com/gabehf/Koito.git
synced 2026-04-22 12:01:52 -07:00
feat: add minutes listened to ui and fix image drop
This commit is contained in:
parent
0042e3f3bd
commit
d84519fa21
11 changed files with 39 additions and 22 deletions
|
|
@ -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)
|
||||
- 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.
|
||||
9
Makefile
9
Makefile
|
|
@ -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…
Add table
Add a link
Reference in a new issue