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