From da1784d486c16277e677987b01db7550417baa2e Mon Sep 17 00:00:00 2001 From: Rubin Bhandari Date: Thu, 31 Oct 2024 14:51:53 +0545 Subject: [PATCH] feat: make music label more dynamically configurable (#389) * feat: make music lable more dynamically configurable * fix: move seekbar above controls * fix: add menu for media * fix: add menu for media * fix: add menu for media * Organize media player code * fix: revert back controls position * Delete pnpm-lock.yaml * Update options.ts * Update widget/settings/pages/config/bar/index.ts * fix: merge changes broken --------- Co-authored-by: Jas Singh --- lib/types/audio.d.ts | 9 +++ modules/bar/media/helpers.ts | 71 +++++++++++++++++++ modules/bar/media/index.ts | 38 ++-------- modules/menus/media/components/title/index.ts | 7 +- options.ts | 6 +- widget/settings/pages/config/bar/index.ts | 7 +- widget/settings/pages/config/index.ts | 4 ++ widget/settings/pages/config/menus/media.ts | 21 ++++++ 8 files changed, 124 insertions(+), 39 deletions(-) create mode 100644 modules/bar/media/helpers.ts create mode 100644 widget/settings/pages/config/menus/media.ts diff --git a/lib/types/audio.d.ts b/lib/types/audio.d.ts index 9332431..508e632 100644 --- a/lib/types/audio.d.ts +++ b/lib/types/audio.d.ts @@ -3,3 +3,12 @@ export type InputDevices = Button, Attribute>, Attribut type DummyDevices = Button, Attribute>, Attribute>, Attribute>[]; type RealPlaybackDevices = Button, Attribute>, Attribute>, Attribute>[]; export type PlaybackDevices = DummyDevices | RealPlaybackDevices; + +export type MediaTags = { + title: string; + artists: string; + artist: string; + album: string; + name: string; + identity: string; +}; diff --git a/modules/bar/media/helpers.ts b/modules/bar/media/helpers.ts new file mode 100644 index 0000000..b94c4c4 --- /dev/null +++ b/modules/bar/media/helpers.ts @@ -0,0 +1,71 @@ +import { MediaTags } from 'lib/types/audio.js'; +import { Opt } from 'lib/option'; +import { Variable } from 'types/variable'; +import { MprisPlayer } from 'types/service/mpris'; + +const getIconForPlayer = (playerName: string): string => { + const windowTitleMap = [ + ['Firefox', '󰈹'], + ['Microsoft Edge', '󰇩'], + ['Discord', ''], + ['Plex', '󰚺'], + ['Spotify', '󰓇'], + ['(.*)', '󰝚'], + ]; + + const foundMatch = windowTitleMap.find((wt) => RegExp(wt[0], 'i').test(playerName)); + + return foundMatch ? foundMatch[1] : '󰝚'; +}; + +const isValidMediaTag = (tag: unknown): tag is keyof MediaTags => { + if (typeof tag !== 'string') { + return false; + } + + const mediaTagKeys = ['title', 'artists', 'artist', 'album', 'name', 'identity'] as const; + return (mediaTagKeys as readonly string[]).includes(tag); +}; + +export const generateMediaLabel = ( + truncation_size: Opt, + show_label: Opt, + format: Opt, + songIcon: Variable, + activePlayer: Variable, +): string => { + if (activePlayer.value && show_label.value) { + const { track_title, identity, track_artists, track_album, name } = activePlayer.value; + songIcon.value = getIconForPlayer(identity); + + const mediaTags: MediaTags = { + title: track_title, + artists: track_artists.join(', '), + artist: track_artists[0] || '', + album: track_album, + name: name, + identity: identity, + }; + + const mediaFormat = format.getValue(); + + const truncatedLabel = mediaFormat.replace( + /{(title|artists|artist|album|name|identity)(:[^}]*)?}/g, + (_, p1: string | undefined, p2: string | undefined) => { + if (!isValidMediaTag(p1)) { + return ''; + } + const value = p1 !== undefined ? mediaTags[p1] : ''; + const suffix = p2?.length ? p2.slice(1) : ''; + return value ? value + suffix : ''; + }, + ); + + const mediaLabel = + truncation_size.value > 0 ? truncatedLabel.substring(0, truncation_size.value) : truncatedLabel; + return mediaLabel; + } else { + songIcon.value = getIconForPlayer(activePlayer.value?.identity || ''); + return `Media`; + } +}; diff --git a/modules/bar/media/index.ts b/modules/bar/media/index.ts index 5892d7b..868989d 100644 --- a/modules/bar/media/index.ts +++ b/modules/bar/media/index.ts @@ -7,8 +7,9 @@ import { BarBoxChild } from 'lib/types/bar.js'; import Button from 'types/widgets/button.js'; import { Attribute, Child } from 'lib/types/widget.js'; import { runAsyncCommand } from 'customModules/utils.js'; +import { generateMediaLabel } from './helpers.js'; -const { show_artist, truncation, truncation_size, show_label, show_active_only, rightClick, middleClick } = +const { truncation, truncation_size, show_label, show_active_only, rightClick, middleClick, format } = options.bar.media; const Media = (): BarBoxChild => { @@ -25,41 +26,10 @@ const Media = (): BarBoxChild => { isVis.value = !show_active_only.value || mpris.players.length > 0; }); - const getIconForPlayer = (playerName: string): string => { - const windowTitleMap = [ - ['Firefox', '󰈹'], - ['Microsoft Edge', '󰇩'], - ['Discord', ''], - ['Plex', '󰚺'], - ['Spotify', '󰓇'], - ['(.*)', '󰝚'], - ]; - - const foundMatch = windowTitleMap.find((wt) => RegExp(wt[0], 'i').test(playerName)); - - return foundMatch ? foundMatch[1] : '󰝚'; - }; - const songIcon = Variable(''); - const mediaLabel = Utils.watch('Media', [mpris, show_artist, truncation, truncation_size, show_label], () => { - if (activePlayer.value && show_label.value) { - const { track_title, identity, track_artists } = activePlayer.value; - songIcon.value = getIconForPlayer(identity); - const trackArtist = show_artist.value ? ` - ${track_artists.join(', ')}` : ``; - const truncatedLabel = truncation.value - ? `${track_title + trackArtist}`.substring(0, truncation_size.value) - : `${track_title + trackArtist}`; - - return track_title.length === 0 - ? `No media playing...` - : truncatedLabel.length < truncation_size.value || !truncation.value - ? `${truncatedLabel}` - : `${truncatedLabel.substring(0, truncatedLabel.length - 3)}...`; - } else { - songIcon.value = getIconForPlayer(activePlayer.value?.identity || ''); - return `Media`; - } + const mediaLabel = Utils.watch('Media', [mpris, truncation, truncation_size, show_label, format], () => { + return generateMediaLabel(truncation_size, show_label, format, songIcon, activePlayer); }); return { diff --git a/modules/menus/media/components/title/index.ts b/modules/menus/media/components/title/index.ts index c5019e6..5934e30 100644 --- a/modules/menus/media/components/title/index.ts +++ b/modules/menus/media/components/title/index.ts @@ -2,6 +2,9 @@ import { BoxWidget } from 'lib/types/widget'; import { songName } from './name/index'; import { songAuthor } from './author/index'; import { songAlbum } from './album/index'; +import options from 'options'; + +const { hideAlbum, hideAuthor } = options.menus.media; export const MediaInfo = (): BoxWidget => { return Widget.Box({ @@ -9,6 +12,8 @@ export const MediaInfo = (): BoxWidget => { hpack: 'center', hexpand: true, vertical: true, - children: [songName(), songAuthor(), songAlbum()], + children: Utils.merge([hideAlbum.bind('value'), hideAuthor.bind('value')], (hidAlbum, hidAuthor) => { + return [songName(), ...(hidAuthor ? [] : [songAuthor()]), ...(hidAlbum ? [] : [songAlbum()])]; + }), }); }; diff --git a/options.ts b/options.ts index 930da56..6dc9dcd 100644 --- a/options.ts +++ b/options.ts @@ -941,7 +941,7 @@ const options = mkOptions(OPTIONS, { scrollDown: opt(''), }, media: { - show_artist: opt(false), + format: opt('{artist: - }{title}'), truncation: opt(true), show_label: opt(true), truncation_size: opt(30), @@ -1062,6 +1062,10 @@ const options = mkOptions(OPTIONS, { menus: { transition: opt('crossfade'), transitionTime: opt(200), + media: { + hideAuthor: opt(false), + hideAlbum: opt(false), + }, bluetooth: { showBattery: opt(false), batteryState: opt('connected'), diff --git a/widget/settings/pages/config/bar/index.ts b/widget/settings/pages/config/bar/index.ts index c35c5ae..3ebef4e 100644 --- a/widget/settings/pages/config/bar/index.ts +++ b/widget/settings/pages/config/bar/index.ts @@ -737,9 +737,10 @@ export const BarSettings = (): Scrollable => { type: 'string', }), Option({ - opt: options.bar.media.show_artist, - title: 'Show Track Artist', - type: 'boolean', + opt: options.bar.media.format, + title: 'Label Format', + subtitle: 'Available placeholders: {title}, {artists}, {artist}, {album}, {name}, {identity}', + type: 'string', }), Option({ opt: options.bar.media.show_label, diff --git a/widget/settings/pages/config/index.ts b/widget/settings/pages/config/index.ts index c6dcf9b..b5686a2 100644 --- a/widget/settings/pages/config/index.ts +++ b/widget/settings/pages/config/index.ts @@ -7,6 +7,7 @@ import { OSDSettings } from './osd/index'; import { CustomModuleSettings } from 'customModules/config'; import { PowerMenuSettings } from './menus/power'; import { GBox } from 'lib/types/widget'; +import { MediaMenuSettings } from './menus/media'; import { BluetoothMenuSettings } from './menus/bluetooth'; import { VolumeMenuSettings } from './menus/volume'; @@ -15,6 +16,7 @@ type Page = | 'Bar' | 'Clock Menu' | 'Dashboard Menu' + | 'Media Menu' | 'Power Menu' | 'Bluetooth Menu' | 'Volume' @@ -29,6 +31,7 @@ const pagerMap: Page[] = [ 'Bar', 'Notifications', 'OSD', + 'Media Menu', 'Power Menu', 'Bluetooth Menu', 'Volume', @@ -61,6 +64,7 @@ export const SettingsMenu = (): GBox => { children: { General: BarGeneral(), Bar: BarSettings(), + 'Media Menu': MediaMenuSettings(), Notifications: NotificationSettings(), OSD: OSDSettings(), Volume: VolumeMenuSettings(), diff --git a/widget/settings/pages/config/menus/media.ts b/widget/settings/pages/config/menus/media.ts new file mode 100644 index 0000000..db7244c --- /dev/null +++ b/widget/settings/pages/config/menus/media.ts @@ -0,0 +1,21 @@ +import { Option } from 'widget/settings/shared/Option'; +import { Header } from 'widget/settings/shared/Header'; + +import options from 'options'; +import Scrollable from 'types/widgets/scrollable'; +import { Attribute, Child } from 'lib/types/widget'; + +export const MediaMenuSettings = (): Scrollable => { + return Widget.Scrollable({ + vscroll: 'automatic', + child: Widget.Box({ + class_name: 'bar-theme-page paged-container', + vertical: true, + children: [ + Header('Media'), + Option({ opt: options.menus.media.hideAuthor, title: 'Hide Author', type: 'boolean' }), + Option({ opt: options.menus.media.hideAlbum, title: 'Hide Album', type: 'boolean' }), + ], + }), + }); +};