Merge a3013a2750e5ac111d3e7e06dff0310e180b2d1d into 12d08985855353681c02d9eb9448cd69dc5f69bf

This commit is contained in:
zacaj 2025-11-22 19:09:49 -08:00 committed by GitHub
commit 9993984e4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 45 additions and 4 deletions

View File

@ -0,0 +1,7 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE annotation ADD COLUMN rated_at datetime;
-- +goose StatementEnd
-- +goose Down

View File

@ -6,6 +6,7 @@ type Annotations struct {
PlayCount int64 `structs:"play_count" json:"playCount,omitempty"` PlayCount int64 `structs:"play_count" json:"playCount,omitempty"`
PlayDate *time.Time `structs:"play_date" json:"playDate,omitempty" ` PlayDate *time.Time `structs:"play_date" json:"playDate,omitempty" `
Rating int `structs:"rating" json:"rating,omitempty" ` Rating int `structs:"rating" json:"rating,omitempty" `
RatedAt *time.Time `structs:"rated_at" json:"ratedAt,omitempty" `
Starred bool `structs:"starred" json:"starred,omitempty" ` Starred bool `structs:"starred" json:"starred,omitempty" `
StarredAt *time.Time `structs:"starred_at" json:"starredAt,omitempty"` StarredAt *time.Time `structs:"starred_at" json:"starredAt,omitempty"`
} }

View File

@ -44,6 +44,7 @@ var fieldMap = map[string]*mappedField{
"loved": {field: "COALESCE(annotation.starred, false)"}, "loved": {field: "COALESCE(annotation.starred, false)"},
"dateloved": {field: "annotation.starred_at"}, "dateloved": {field: "annotation.starred_at"},
"lastplayed": {field: "annotation.play_date"}, "lastplayed": {field: "annotation.play_date"},
"daterated": {field: "annotation.rated_at"},
"playcount": {field: "COALESCE(annotation.play_count, 0)"}, "playcount": {field: "COALESCE(annotation.play_count, 0)"},
"rating": {field: "COALESCE(annotation.rating, 0)"}, "rating": {field: "COALESCE(annotation.rating, 0)"},
"mbz_album_id": {field: "media_file.mbz_album_id"}, "mbz_album_id": {field: "media_file.mbz_album_id"},

View File

@ -388,6 +388,7 @@ func (r *playlistRepository) loadTracks(sel SelectBuilder, id string) (model.Pla
"coalesce(play_count, 0) as play_count", "coalesce(play_count, 0) as play_count",
"play_date", "play_date",
"coalesce(rating, 0) as rating", "coalesce(rating, 0) as rating",
"rated_at",
"f.*", "f.*",
"playlist_tracks.*", "playlist_tracks.*",
"library.path as library_path", "library.path as library_path",

View File

@ -97,6 +97,7 @@ func (r *playlistTrackRepository) Read(id string) (interface{}, error) {
"coalesce(rating, 0) as rating", "coalesce(rating, 0) as rating",
"starred_at", "starred_at",
"play_date", "play_date",
"rated_at",
"f.*", "f.*",
"playlist_tracks.*", "playlist_tracks.*",
). ).

View File

@ -28,6 +28,7 @@ func (r sqlRepository) withAnnotation(query SelectBuilder, idField string) Selec
"coalesce(rating, 0) as rating", "coalesce(rating, 0) as rating",
"starred_at", "starred_at",
"play_date", "play_date",
"rated_at",
) )
if conf.Server.AlbumPlayCountMode == consts.AlbumPlayCountModeNormalized && r.tableName == "album" { if conf.Server.AlbumPlayCountMode == consts.AlbumPlayCountModeNormalized && r.tableName == "album" {
query = query.Columns( query = query.Columns(
@ -77,7 +78,8 @@ func (r sqlRepository) SetStar(starred bool, ids ...string) error {
} }
func (r sqlRepository) SetRating(rating int, itemID string) error { func (r sqlRepository) SetRating(rating int, itemID string) error {
return r.annUpsert(map[string]interface{}{"rating": rating}, itemID) ratedAt := time.Now()
return r.annUpsert(map[string]interface{}{"rating": rating, "rated_at": ratedAt}, itemID)
} }
func (r sqlRepository) IncPlayCount(itemID string, ts time.Time) error { func (r sqlRepository) IncPlayCount(itemID string, ts time.Time) error {

View File

@ -1,10 +1,11 @@
import React from 'react' import React from 'react'
import { isDateSet } from '../utils/validations'
import { DateField as RADateField } from 'react-admin' import { DateField as RADateField } from 'react-admin'
export const DateField = (props) => { export const DateField = (props) => {
const { record, source } = props const { record, source } = props
const value = record?.[source] const value = record?.[source]
if (value === '0001-01-01T00:00:00Z' || value === null) return null if (!isDateSet(value)) return null
return <RADateField {...props} /> return <RADateField {...props} />
} }

View File

@ -7,6 +7,7 @@ import { makeStyles } from '@material-ui/core/styles'
import { useToggleLove } from './useToggleLove' import { useToggleLove } from './useToggleLove'
import { useRecordContext } from 'react-admin' import { useRecordContext } from 'react-admin'
import config from '../config' import config from '../config'
import { isDateSet } from '../utils/validations'
const useStyles = makeStyles({ const useStyles = makeStyles({
love: { love: {
@ -46,8 +47,13 @@ export const LoveButton = ({
<Button <Button
onClick={handleToggleLove} onClick={handleToggleLove}
size={'small'} size={'small'}
disabled={disabled || loading || record?.missing} disabled={disabled || loading || record.missing}
className={classes.love} className={classes.love}
title={
isDateSet(record.starredAt)
? new Date(record.starredAt).toLocaleString()
: undefined
}
{...rest} {...rest}
> >
{record.starred ? ( {record.starred ? (

View File

@ -2,6 +2,7 @@ import React, { useCallback } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Rating from '@material-ui/lab/Rating' import Rating from '@material-ui/lab/Rating'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { isDateSet } from '../utils/validations'
import StarBorderIcon from '@material-ui/icons/StarBorder' import StarBorderIcon from '@material-ui/icons/StarBorder'
import clsx from 'clsx' import clsx from 'clsx'
import { useRating } from './useRating' import { useRating } from './useRating'
@ -45,7 +46,14 @@ export const RatingField = ({
) )
return ( return (
<span onClick={(e) => stopPropagation(e)}> <span
onClick={(e) => stopPropagation(e)}
title={
isDateSet(record.ratedAt)
? new Date(record.ratedAt).toLocaleString()
: undefined
}
>
<Rating <Rating
name={record.mediaFileId || record.id} name={record.mediaFileId || record.id}
className={clsx( className={clsx(

View File

@ -10,3 +10,16 @@ export const urlValidate = (value) => {
return 'ra.validation.url' return 'ra.validation.url'
} }
} }
export function isDateSet(date) {
if (!date) {
return false
}
if (typeof date === 'string') {
return date !== '0001-01-01T00:00:00Z'
}
if (date instanceof Date) {
return date.toISOString() !== '0001-01-01T00:00:00Z'
}
return !!date
}