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:
9
lib/types/audio.d.ts
vendored
9
lib/types/audio.d.ts
vendored
@@ -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 DummyDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
|
||||||
type RealPlaybackDevices = 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 PlaybackDevices = DummyDevices | RealPlaybackDevices;
|
||||||
|
|
||||||
|
export type MediaTags = {
|
||||||
|
title: string;
|
||||||
|
artists: string;
|
||||||
|
artist: string;
|
||||||
|
album: string;
|
||||||
|
name: string;
|
||||||
|
identity: string;
|
||||||
|
};
|
||||||
|
|||||||
71
modules/bar/media/helpers.ts
Normal file
71
modules/bar/media/helpers.ts
Normal 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`;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -7,8 +7,9 @@ import { BarBoxChild } from 'lib/types/bar.js';
|
|||||||
import Button from 'types/widgets/button.js';
|
import Button from 'types/widgets/button.js';
|
||||||
import { Attribute, Child } from 'lib/types/widget.js';
|
import { Attribute, Child } from 'lib/types/widget.js';
|
||||||
import { runAsyncCommand } from 'customModules/utils.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;
|
options.bar.media;
|
||||||
|
|
||||||
const Media = (): BarBoxChild => {
|
const Media = (): BarBoxChild => {
|
||||||
@@ -25,41 +26,10 @@ const Media = (): BarBoxChild => {
|
|||||||
isVis.value = !show_active_only.value || mpris.players.length > 0;
|
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 songIcon = Variable('');
|
||||||
|
|
||||||
const mediaLabel = Utils.watch('Media', [mpris, show_artist, truncation, truncation_size, show_label], () => {
|
const mediaLabel = Utils.watch('Media', [mpris, truncation, truncation_size, show_label, format], () => {
|
||||||
if (activePlayer.value && show_label.value) {
|
return generateMediaLabel(truncation_size, show_label, format, songIcon, activePlayer);
|
||||||
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`;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import { BoxWidget } from 'lib/types/widget';
|
|||||||
import { songName } from './name/index';
|
import { songName } from './name/index';
|
||||||
import { songAuthor } from './author/index';
|
import { songAuthor } from './author/index';
|
||||||
import { songAlbum } from './album/index';
|
import { songAlbum } from './album/index';
|
||||||
|
import options from 'options';
|
||||||
|
|
||||||
|
const { hideAlbum, hideAuthor } = options.menus.media;
|
||||||
|
|
||||||
export const MediaInfo = (): BoxWidget => {
|
export const MediaInfo = (): BoxWidget => {
|
||||||
return Widget.Box({
|
return Widget.Box({
|
||||||
@@ -9,6 +12,8 @@ export const MediaInfo = (): BoxWidget => {
|
|||||||
hpack: 'center',
|
hpack: 'center',
|
||||||
hexpand: true,
|
hexpand: true,
|
||||||
vertical: true,
|
vertical: true,
|
||||||
children: [songName(), songAuthor(), songAlbum()],
|
children: Utils.merge([hideAlbum.bind('value'), hideAuthor.bind('value')], (hidAlbum, hidAuthor) => {
|
||||||
|
return [songName(), ...(hidAuthor ? [] : [songAuthor()]), ...(hidAlbum ? [] : [songAlbum()])];
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -941,7 +941,7 @@ const options = mkOptions(OPTIONS, {
|
|||||||
scrollDown: opt(''),
|
scrollDown: opt(''),
|
||||||
},
|
},
|
||||||
media: {
|
media: {
|
||||||
show_artist: opt(false),
|
format: opt('{artist: - }{title}'),
|
||||||
truncation: opt(true),
|
truncation: opt(true),
|
||||||
show_label: opt(true),
|
show_label: opt(true),
|
||||||
truncation_size: opt(30),
|
truncation_size: opt(30),
|
||||||
@@ -1062,6 +1062,10 @@ const options = mkOptions(OPTIONS, {
|
|||||||
menus: {
|
menus: {
|
||||||
transition: opt<Transition>('crossfade'),
|
transition: opt<Transition>('crossfade'),
|
||||||
transitionTime: opt(200),
|
transitionTime: opt(200),
|
||||||
|
media: {
|
||||||
|
hideAuthor: opt(false),
|
||||||
|
hideAlbum: opt(false),
|
||||||
|
},
|
||||||
bluetooth: {
|
bluetooth: {
|
||||||
showBattery: opt(false),
|
showBattery: opt(false),
|
||||||
batteryState: opt<BluetoothBatteryState>('connected'),
|
batteryState: opt<BluetoothBatteryState>('connected'),
|
||||||
|
|||||||
@@ -737,9 +737,10 @@ export const BarSettings = (): Scrollable<Gtk.Widget, Gtk.Widget> => {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
}),
|
}),
|
||||||
Option({
|
Option({
|
||||||
opt: options.bar.media.show_artist,
|
opt: options.bar.media.format,
|
||||||
title: 'Show Track Artist',
|
title: 'Label Format',
|
||||||
type: 'boolean',
|
subtitle: 'Available placeholders: {title}, {artists}, {artist}, {album}, {name}, {identity}',
|
||||||
|
type: 'string',
|
||||||
}),
|
}),
|
||||||
Option({
|
Option({
|
||||||
opt: options.bar.media.show_label,
|
opt: options.bar.media.show_label,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { OSDSettings } from './osd/index';
|
|||||||
import { CustomModuleSettings } from 'customModules/config';
|
import { CustomModuleSettings } from 'customModules/config';
|
||||||
import { PowerMenuSettings } from './menus/power';
|
import { PowerMenuSettings } from './menus/power';
|
||||||
import { GBox } from 'lib/types/widget';
|
import { GBox } from 'lib/types/widget';
|
||||||
|
import { MediaMenuSettings } from './menus/media';
|
||||||
import { BluetoothMenuSettings } from './menus/bluetooth';
|
import { BluetoothMenuSettings } from './menus/bluetooth';
|
||||||
import { VolumeMenuSettings } from './menus/volume';
|
import { VolumeMenuSettings } from './menus/volume';
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ type Page =
|
|||||||
| 'Bar'
|
| 'Bar'
|
||||||
| 'Clock Menu'
|
| 'Clock Menu'
|
||||||
| 'Dashboard Menu'
|
| 'Dashboard Menu'
|
||||||
|
| 'Media Menu'
|
||||||
| 'Power Menu'
|
| 'Power Menu'
|
||||||
| 'Bluetooth Menu'
|
| 'Bluetooth Menu'
|
||||||
| 'Volume'
|
| 'Volume'
|
||||||
@@ -29,6 +31,7 @@ const pagerMap: Page[] = [
|
|||||||
'Bar',
|
'Bar',
|
||||||
'Notifications',
|
'Notifications',
|
||||||
'OSD',
|
'OSD',
|
||||||
|
'Media Menu',
|
||||||
'Power Menu',
|
'Power Menu',
|
||||||
'Bluetooth Menu',
|
'Bluetooth Menu',
|
||||||
'Volume',
|
'Volume',
|
||||||
@@ -61,6 +64,7 @@ export const SettingsMenu = (): GBox => {
|
|||||||
children: {
|
children: {
|
||||||
General: BarGeneral(),
|
General: BarGeneral(),
|
||||||
Bar: BarSettings(),
|
Bar: BarSettings(),
|
||||||
|
'Media Menu': MediaMenuSettings(),
|
||||||
Notifications: NotificationSettings(),
|
Notifications: NotificationSettings(),
|
||||||
OSD: OSDSettings(),
|
OSD: OSDSettings(),
|
||||||
Volume: VolumeMenuSettings(),
|
Volume: VolumeMenuSettings(),
|
||||||
|
|||||||
21
widget/settings/pages/config/menus/media.ts
Normal file
21
widget/settings/pages/config/menus/media.ts
Normal 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' }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user