mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Refactor item text lines
This commit is contained in:
parent
68c4a02500
commit
fe22de4f89
6 changed files with 167 additions and 103 deletions
102
src/apps/stable/features/playback/utils/itemText.test.ts
Normal file
102
src/apps/stable/features/playback/utils/itemText.test.ts
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import type { ItemDto } from 'types/base/models/item-dto';
|
||||||
|
|
||||||
|
import { getItemTextLines } from './itemText';
|
||||||
|
|
||||||
|
describe('getItemTextLines', () => {
|
||||||
|
it('Should return undefined if item is invalid', () => {
|
||||||
|
let lines = getItemTextLines({});
|
||||||
|
expect(lines).toBeUndefined();
|
||||||
|
lines = getItemTextLines(null);
|
||||||
|
expect(lines).toBeUndefined();
|
||||||
|
lines = getItemTextLines(undefined);
|
||||||
|
expect(lines).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return the name and index number', () => {
|
||||||
|
const item: ItemDto = {
|
||||||
|
Name: 'Item Name'
|
||||||
|
};
|
||||||
|
let lines = getItemTextLines(item);
|
||||||
|
expect(lines).toBeDefined();
|
||||||
|
expect(lines).toHaveLength(1);
|
||||||
|
expect(lines?.[0]).toBe(item.Name);
|
||||||
|
|
||||||
|
item.MediaType = MediaType.Video;
|
||||||
|
item.IndexNumber = 5;
|
||||||
|
lines = getItemTextLines(item);
|
||||||
|
expect(lines).toBeDefined();
|
||||||
|
expect(lines).toHaveLength(1);
|
||||||
|
expect(lines?.[0]).toBe(`${item.IndexNumber} - ${item.Name}`);
|
||||||
|
|
||||||
|
item.ParentIndexNumber = 2;
|
||||||
|
lines = getItemTextLines(item);
|
||||||
|
expect(lines).toBeDefined();
|
||||||
|
expect(lines).toHaveLength(1);
|
||||||
|
expect(lines?.[0]).toBe(`${item.ParentIndexNumber}.${item.IndexNumber} - ${item.Name}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should add artist names', () => {
|
||||||
|
let item: ItemDto = {
|
||||||
|
Name: 'Item Name',
|
||||||
|
ArtistItems: [
|
||||||
|
{ Name: 'Artist 1' },
|
||||||
|
{ Name: 'Artist 2' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let lines = getItemTextLines(item);
|
||||||
|
expect(lines).toBeDefined();
|
||||||
|
expect(lines).toHaveLength(2);
|
||||||
|
expect(lines?.[0]).toBe(item.Name);
|
||||||
|
expect(lines?.[1]).toBe('Artist 1, Artist 2');
|
||||||
|
|
||||||
|
item = {
|
||||||
|
Name: 'Item Name',
|
||||||
|
Artists: [
|
||||||
|
'Artist 1',
|
||||||
|
'Artist 2'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
lines = getItemTextLines(item);
|
||||||
|
expect(lines).toBeDefined();
|
||||||
|
expect(lines).toHaveLength(2);
|
||||||
|
expect(lines?.[0]).toBe(item.Name);
|
||||||
|
expect(lines?.[1]).toBe('Artist 1, Artist 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should add album or series name', () => {
|
||||||
|
let item: ItemDto = {
|
||||||
|
Name: 'Item Name',
|
||||||
|
SeriesName: 'Series'
|
||||||
|
};
|
||||||
|
let lines = getItemTextLines(item);
|
||||||
|
expect(lines).toBeDefined();
|
||||||
|
expect(lines).toHaveLength(2);
|
||||||
|
expect(lines?.[0]).toBe(item.SeriesName);
|
||||||
|
expect(lines?.[1]).toBe(item.Name);
|
||||||
|
|
||||||
|
item = {
|
||||||
|
Name: 'Item Name',
|
||||||
|
Album: 'Album'
|
||||||
|
};
|
||||||
|
lines = getItemTextLines(item);
|
||||||
|
expect(lines).toBeDefined();
|
||||||
|
expect(lines).toHaveLength(2);
|
||||||
|
expect(lines?.[0]).toBe(item.Album);
|
||||||
|
expect(lines?.[1]).toBe(item.Name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should add production year', () => {
|
||||||
|
const item = {
|
||||||
|
Name: 'Item Name',
|
||||||
|
ProductionYear: 2025
|
||||||
|
};
|
||||||
|
const lines = getItemTextLines(item);
|
||||||
|
expect(lines).toBeDefined();
|
||||||
|
expect(lines).toHaveLength(2);
|
||||||
|
expect(lines?.[0]).toBe(item.Name);
|
||||||
|
expect(lines?.[1]).toBe(String(item.ProductionYear));
|
||||||
|
});
|
||||||
|
});
|
44
src/apps/stable/features/playback/utils/itemText.ts
Normal file
44
src/apps/stable/features/playback/utils/itemText.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
||||||
|
|
||||||
|
import type { ItemDto } from 'types/base/models/item-dto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets lines of text used to describe an item for display.
|
||||||
|
* @param nowPlayingItem The item to describe
|
||||||
|
* @param isYearIncluded Should the production year be included
|
||||||
|
* @returns The list of strings describing the item for display
|
||||||
|
*/
|
||||||
|
export function getItemTextLines(
|
||||||
|
nowPlayingItem: ItemDto | null | undefined,
|
||||||
|
isYearIncluded = true
|
||||||
|
) {
|
||||||
|
let line1 = nowPlayingItem?.Name;
|
||||||
|
if (nowPlayingItem?.MediaType === MediaType.Video) {
|
||||||
|
if (nowPlayingItem.IndexNumber != null) {
|
||||||
|
line1 = nowPlayingItem.IndexNumber + ' - ' + line1;
|
||||||
|
}
|
||||||
|
if (nowPlayingItem.ParentIndexNumber != null) {
|
||||||
|
line1 = nowPlayingItem.ParentIndexNumber + '.' + line1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let line2: string | null | undefined;
|
||||||
|
if (nowPlayingItem?.ArtistItems?.length) {
|
||||||
|
line2 = nowPlayingItem.ArtistItems.map(a => a.Name).join(', ');
|
||||||
|
} else if (nowPlayingItem?.Artists?.length) {
|
||||||
|
line2 = nowPlayingItem.Artists.join(', ');
|
||||||
|
} else if (nowPlayingItem?.SeriesName || nowPlayingItem?.Album) {
|
||||||
|
line2 = line1;
|
||||||
|
line1 = nowPlayingItem.SeriesName || nowPlayingItem.Album;
|
||||||
|
} else if (nowPlayingItem?.ProductionYear && isYearIncluded) {
|
||||||
|
line2 = String(nowPlayingItem.ProductionYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line1) return;
|
||||||
|
|
||||||
|
const lines = [ line1 ];
|
||||||
|
|
||||||
|
if (line2) lines.push(line2);
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
|
||||||
|
|
||||||
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
||||||
|
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
|
||||||
import { PlaybackSubscriber } from 'apps/stable/features/playback/utils/playbackSubscriber';
|
import { PlaybackSubscriber } from 'apps/stable/features/playback/utils/playbackSubscriber';
|
||||||
import { getNowPlayingNames } from 'components/playback/nowplayinghelper';
|
|
||||||
import type { PlaybackManager } from 'components/playback/playbackmanager';
|
import type { PlaybackManager } from 'components/playback/playbackmanager';
|
||||||
import { MILLISECONDS_PER_SECOND, TICKS_PER_MILLISECOND } from 'constants/time';
|
import { MILLISECONDS_PER_SECOND, TICKS_PER_MILLISECOND } from 'constants/time';
|
||||||
import browser from 'scripts/browser';
|
import browser from 'scripts/browser';
|
||||||
|
@ -110,11 +110,11 @@ class MediaSessionSubscriber extends PlaybackSubscriber {
|
||||||
}
|
}
|
||||||
|
|
||||||
const album = item.Album || undefined;
|
const album = item.Album || undefined;
|
||||||
const [ line1, line2 ] = getNowPlayingNames(item, false) || [];
|
const [ line1, line2 ] = getItemTextLines(item, false) || [];
|
||||||
// The artist will be the second line if present or the first line otherwise
|
// The artist will be the second line if present or the first line otherwise
|
||||||
const artist = (line2 || line1)?.text;
|
const artist = line2 || line1;
|
||||||
// The title will be the first line if there are two lines
|
// The title will be the first line if there are two lines
|
||||||
const title = (line2 && line1)?.text;
|
const title = line2 && line1;
|
||||||
|
|
||||||
if (hasNavigatorSession) {
|
if (hasNavigatorSession) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
||||||
|
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
|
||||||
import { appRouter, isLyricsPage } from 'components/router/appRouter';
|
import { appRouter, isLyricsPage } from 'components/router/appRouter';
|
||||||
|
|
||||||
import datetime from '../../scripts/datetime';
|
import datetime from '../../scripts/datetime';
|
||||||
import Events from '../../utils/events.ts';
|
import Events from '../../utils/events.ts';
|
||||||
import browser from '../../scripts/browser';
|
import browser from '../../scripts/browser';
|
||||||
import imageLoader from '../images/imageLoader';
|
import imageLoader from '../images/imageLoader';
|
||||||
import layoutManager from '../layoutManager';
|
import layoutManager from '../layoutManager';
|
||||||
import { playbackManager } from '../playback/playbackmanager';
|
import { playbackManager } from '../playback/playbackmanager';
|
||||||
import nowPlayingHelper from '../playback/nowplayinghelper';
|
|
||||||
import { appHost } from '../apphost';
|
import { appHost } from '../apphost';
|
||||||
import dom from '../../scripts/dom';
|
import dom from '../../scripts/dom';
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
|
@ -17,7 +19,6 @@ import appFooter from '../appFooter/appFooter';
|
||||||
import itemShortcuts from '../shortcuts';
|
import itemShortcuts from '../shortcuts';
|
||||||
import './nowPlayingBar.scss';
|
import './nowPlayingBar.scss';
|
||||||
import '../../elements/emby-slider/emby-slider';
|
import '../../elements/emby-slider/emby-slider';
|
||||||
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
|
||||||
|
|
||||||
let currentPlayer;
|
let currentPlayer;
|
||||||
let currentPlayerSupportedCommands = [];
|
let currentPlayerSupportedCommands = [];
|
||||||
|
@ -474,24 +475,21 @@ function setLyricButtonActiveStatus() {
|
||||||
function updateNowPlayingInfo(state) {
|
function updateNowPlayingInfo(state) {
|
||||||
const nowPlayingItem = state.NowPlayingItem;
|
const nowPlayingItem = state.NowPlayingItem;
|
||||||
|
|
||||||
const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : [];
|
const textLines = nowPlayingItem ? getItemTextLines(nowPlayingItem) : undefined;
|
||||||
nowPlayingTextElement.innerHTML = '';
|
nowPlayingTextElement.innerHTML = '';
|
||||||
if (textLines) {
|
if (textLines) {
|
||||||
const itemText = document.createElement('div');
|
const itemText = document.createElement('div');
|
||||||
const secondaryText = document.createElement('div');
|
const secondaryText = document.createElement('div');
|
||||||
secondaryText.classList.add('nowPlayingBarSecondaryText');
|
secondaryText.classList.add('nowPlayingBarSecondaryText');
|
||||||
if (textLines.length > 1) {
|
if (textLines.length > 1 && textLines[1]) {
|
||||||
textLines[1].secondary = true;
|
const text = document.createElement('a');
|
||||||
if (textLines[1].text) {
|
text.innerText = textLines[1];
|
||||||
const text = document.createElement('a');
|
secondaryText.appendChild(text);
|
||||||
text.innerText = textLines[1].text;
|
|
||||||
secondaryText.appendChild(text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textLines[0].text) {
|
if (textLines[0]) {
|
||||||
const text = document.createElement('a');
|
const text = document.createElement('a');
|
||||||
text.innerText = textLines[0].text;
|
text.innerText = textLines[0];
|
||||||
itemText.appendChild(text);
|
itemText.appendChild(text);
|
||||||
}
|
}
|
||||||
nowPlayingTextElement.appendChild(itemText);
|
nowPlayingTextElement.appendChild(itemText);
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) {
|
|
||||||
let topItem = nowPlayingItem;
|
|
||||||
let bottomItem = null;
|
|
||||||
let topText = nowPlayingItem.Name;
|
|
||||||
|
|
||||||
if (nowPlayingItem.AlbumId && nowPlayingItem.MediaType === 'Audio') {
|
|
||||||
topItem = {
|
|
||||||
Id: nowPlayingItem.AlbumId,
|
|
||||||
Name: nowPlayingItem.Album,
|
|
||||||
Type: 'MusicAlbum',
|
|
||||||
IsFolder: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nowPlayingItem.MediaType === 'Video') {
|
|
||||||
if (nowPlayingItem.IndexNumber != null) {
|
|
||||||
topText = nowPlayingItem.IndexNumber + ' - ' + topText;
|
|
||||||
}
|
|
||||||
if (nowPlayingItem.ParentIndexNumber != null) {
|
|
||||||
topText = nowPlayingItem.ParentIndexNumber + '.' + topText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let bottomText = '';
|
|
||||||
|
|
||||||
if (nowPlayingItem.ArtistItems?.length) {
|
|
||||||
bottomItem = {
|
|
||||||
Id: nowPlayingItem.ArtistItems[0].Id,
|
|
||||||
Name: nowPlayingItem.ArtistItems[0].Name,
|
|
||||||
Type: 'MusicArtist',
|
|
||||||
IsFolder: true
|
|
||||||
};
|
|
||||||
|
|
||||||
bottomText = nowPlayingItem.ArtistItems.map(function (a) {
|
|
||||||
return a.Name;
|
|
||||||
}).join(', ');
|
|
||||||
} else if (nowPlayingItem.Artists?.length) {
|
|
||||||
bottomText = nowPlayingItem.Artists.join(', ');
|
|
||||||
} else if (nowPlayingItem.SeriesName || nowPlayingItem.Album) {
|
|
||||||
bottomText = topText;
|
|
||||||
topText = nowPlayingItem.SeriesName || nowPlayingItem.Album;
|
|
||||||
|
|
||||||
bottomItem = topItem;
|
|
||||||
|
|
||||||
if (nowPlayingItem.SeriesId) {
|
|
||||||
topItem = {
|
|
||||||
Id: nowPlayingItem.SeriesId,
|
|
||||||
Name: nowPlayingItem.SeriesName,
|
|
||||||
Type: 'Series',
|
|
||||||
IsFolder: true
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
topItem = null;
|
|
||||||
}
|
|
||||||
} else if (nowPlayingItem.ProductionYear && includeNonNameInfo !== false) {
|
|
||||||
bottomText = nowPlayingItem.ProductionYear;
|
|
||||||
}
|
|
||||||
|
|
||||||
const list = [];
|
|
||||||
|
|
||||||
list.push({
|
|
||||||
text: topText,
|
|
||||||
item: topItem
|
|
||||||
});
|
|
||||||
|
|
||||||
if (bottomText) {
|
|
||||||
list.push({
|
|
||||||
text: bottomText,
|
|
||||||
item: bottomItem
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getNowPlayingNames: getNowPlayingNames
|
|
||||||
};
|
|
|
@ -1,10 +1,13 @@
|
||||||
import escapeHtml from 'escape-html';
|
import escapeHtml from 'escape-html';
|
||||||
|
|
||||||
|
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
||||||
|
import { getItemTextLines } from 'apps/stable/features/playback/utils/itemText';
|
||||||
|
|
||||||
import datetime from '../../scripts/datetime';
|
import datetime from '../../scripts/datetime';
|
||||||
import { clearBackdrop, setBackdrops } from '../backdrop/backdrop';
|
import { clearBackdrop, setBackdrops } from '../backdrop/backdrop';
|
||||||
import listView from '../listview/listview';
|
import listView from '../listview/listview';
|
||||||
import imageLoader from '../images/imageLoader';
|
import imageLoader from '../images/imageLoader';
|
||||||
import { playbackManager } from '../playback/playbackmanager';
|
import { playbackManager } from '../playback/playbackmanager';
|
||||||
import nowPlayingHelper from '../playback/nowplayinghelper';
|
|
||||||
import Events from '../../utils/events.ts';
|
import Events from '../../utils/events.ts';
|
||||||
import { appHost } from '../apphost';
|
import { appHost } from '../apphost';
|
||||||
import globalize from '../../lib/globalize';
|
import globalize from '../../lib/globalize';
|
||||||
|
@ -22,7 +25,6 @@ import ServerConnections from '../ServerConnections';
|
||||||
import toast from '../toast/toast';
|
import toast from '../toast/toast';
|
||||||
import { appRouter } from '../router/appRouter';
|
import { appRouter } from '../router/appRouter';
|
||||||
import { getDefaultBackgroundClass } from '../cardbuilder/cardBuilderUtils';
|
import { getDefaultBackgroundClass } from '../cardbuilder/cardBuilderUtils';
|
||||||
import { getImageUrl } from 'apps/stable/features/playback/utils/image';
|
|
||||||
|
|
||||||
let showMuteButton = true;
|
let showMuteButton = true;
|
||||||
let showVolumeSlider = true;
|
let showVolumeSlider = true;
|
||||||
|
@ -86,15 +88,11 @@ function showSubtitleMenu(context, player, button) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNowPlayingNameHtml(nowPlayingItem, includeNonNameInfo) {
|
|
||||||
return nowPlayingHelper.getNowPlayingNames(nowPlayingItem, includeNonNameInfo).map(function (i) {
|
|
||||||
return escapeHtml(i.text);
|
|
||||||
}).join('<br/>');
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNowPlayingInfo(context, state, serverId) {
|
function updateNowPlayingInfo(context, state, serverId) {
|
||||||
const item = state.NowPlayingItem;
|
const item = state.NowPlayingItem;
|
||||||
const displayName = item ? getNowPlayingNameHtml(item).replace('<br/>', ' - ') : '';
|
const displayName = item ?
|
||||||
|
getItemTextLines(item).map(escapeHtml).join(' - ') :
|
||||||
|
'';
|
||||||
if (item) {
|
if (item) {
|
||||||
const nowPlayingServerId = (item.ServerId || serverId);
|
const nowPlayingServerId = (item.ServerId || serverId);
|
||||||
if (item.Type == 'AudioBook' || item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') {
|
if (item.Type == 'AudioBook' || item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue