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 <jaskiratpal.singh@outlook.com>
This commit is contained in:
Rubin Bhandari
2024-10-31 14:51:53 +05:45
committed by GitHub
parent 8893122006
commit da1784d486
8 changed files with 124 additions and 39 deletions

View File

@@ -3,3 +3,12 @@ export type InputDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribut
type DummyDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
type RealPlaybackDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
export type PlaybackDevices = DummyDevices | RealPlaybackDevices;
export type MediaTags = {
title: string;
artists: string;
artist: string;
album: string;
name: string;
identity: string;
};

View File

@@ -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<number>,
show_label: Opt<boolean>,
format: Opt<string>,
songIcon: Variable<string>,
activePlayer: Variable<MprisPlayer>,
): 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`;
}
};

View File

@@ -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 {

View File

@@ -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()])];
}),
});
};

View File

@@ -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<Transition>('crossfade'),
transitionTime: opt(200),
media: {
hideAuthor: opt(false),
hideAlbum: opt(false),
},
bluetooth: {
showBattery: opt(false),
batteryState: opt<BluetoothBatteryState>('connected'),

View File

@@ -737,9 +737,10 @@ export const BarSettings = (): Scrollable<Gtk.Widget, Gtk.Widget> => {
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,

View File

@@ -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(),

View File

@@ -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<Child, Attribute> => {
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' }),
],
}),
});
};