// Package tracklist manages track-list display configuration. package tracklist import ( "errors" "fmt" "slices" ) var ( errUnknownColumnID = errors.New("unknown track-list column ID") errDuplicateColumn = errors.New("duplicate column ID") ) // ColumnID identifies a displayable column in the track list. type ColumnID string // Valid column identifiers. const ( ColTrackName ColumnID = "trackName" ColArtistName ColumnID = "artistName" ColTrackLength ColumnID = "trackLength" ColAlbum ColumnID = "album" ColGenre ColumnID = "genre" ColYear ColumnID = "year" ColComposer ColumnID = "composer" ColTrackNumber ColumnID = "trackNumber" ColDiscNumber ColumnID = "discNumber" ColFilePath ColumnID = "filePath" ColFileType ColumnID = "fileType" ColSampleRate ColumnID = "sampleRate" ColBitDepth ColumnID = "bitDepth" ColChannels ColumnID = "channels" ColBitrate ColumnID = "bitrate" ColFileSize ColumnID = "fileSize" ColPlayCount ColumnID = "playCount" ColAlbumArt ColumnID = "albumArt" ) // AllColumnIDs lists every recognised column in default display // order. var AllColumnIDs = []ColumnID{ ColAlbumArt, ColTrackName, ColArtistName, ColTrackLength, ColAlbum, ColGenre, ColYear, ColComposer, ColTrackNumber, ColDiscNumber, ColFilePath, ColFileType, ColSampleRate, ColBitDepth, ColChannels, ColBitrate, ColFileSize, ColPlayCount, } // DefaultColumns is the initial column configuration. Album is in it // (H-15): without it the three identical `Tideline / Aurora Fields / // 00:06` rows in this app's own fixture library cannot be told apart, // in an app that has a duplicate-detection feature. Must match // DEFAULT_COLUMN_IDS in frontend/src/components/track-list/columns.ts, // which is what the UI falls back to before the config arrives. var DefaultColumns = []Column{ {ID: ColTrackName}, {ID: ColArtistName}, {ID: ColAlbum}, {ID: ColTrackLength}, } // Column represents a visible column in the track list. type Column struct { ID ColumnID `json:"id" toml:"ID"` } // Config holds track-list display preferences. type Config struct { Columns []Column `json:"columns" toml:"Columns"` } // ApplyDefaults fills zero-value fields with sensible defaults. func (c *Config) ApplyDefaults() { if len(c.Columns) == 0 { c.Columns = make([]Column, len(DefaultColumns)) copy(c.Columns, DefaultColumns) } } // Validate checks that every column ID is recognised and that // there are no duplicates. func (c *Config) Validate() error { c.ApplyDefaults() seen := make(map[ColumnID]bool, len(c.Columns)) for _, col := range c.Columns { if !isValidColumnID(col.ID) { return fmt.Errorf( "%w: %q", errUnknownColumnID, col.ID, ) } if seen[col.ID] { return fmt.Errorf( "%w: %q", errDuplicateColumn, col.ID, ) } seen[col.ID] = true } return nil } // isValidColumnID returns true when id matches a known column. func isValidColumnID(id ColumnID) bool { return slices.Contains(AllColumnIDs, id) }