mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-15 01:23:53 -07:00
New: Show downloading status for artist progress bar
(cherry picked from commit ac806a2933bf2dc0c96d471ec143fca8e1f5282f)
This commit is contained in:
parent
f1dede240d
commit
af12fad185
14 changed files with 132 additions and 29 deletions
|
@ -1,4 +1,10 @@
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { SelectProvider } from 'App/SelectContext';
|
import { SelectProvider } from 'App/SelectContext';
|
||||||
import NoArtist from 'Artist/NoArtist';
|
import NoArtist from 'Artist/NoArtist';
|
||||||
|
@ -22,6 +28,7 @@ import {
|
||||||
setArtistView,
|
setArtistView,
|
||||||
} from 'Store/Actions/artistIndexActions';
|
} from 'Store/Actions/artistIndexActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
import { fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||||
import scrollPositions from 'Store/scrollPositions';
|
import scrollPositions from 'Store/scrollPositions';
|
||||||
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
|
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
|
@ -48,7 +55,7 @@ import ArtistIndexTable from './Table/ArtistIndexTable';
|
||||||
import ArtistIndexTableOptions from './Table/ArtistIndexTableOptions';
|
import ArtistIndexTableOptions from './Table/ArtistIndexTableOptions';
|
||||||
import styles from './ArtistIndex.css';
|
import styles from './ArtistIndex.css';
|
||||||
|
|
||||||
function getViewComponent(view) {
|
function getViewComponent(view: string) {
|
||||||
if (view === 'posters') {
|
if (view === 'posters') {
|
||||||
return ArtistIndexPosters;
|
return ArtistIndexPosters;
|
||||||
}
|
}
|
||||||
|
@ -94,6 +101,10 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
|
||||||
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
|
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
|
||||||
const [isSelectMode, setIsSelectMode] = useState(false);
|
const [isSelectMode, setIsSelectMode] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchQueueDetails({ all: true }));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
const onRssSyncPress = useCallback(() => {
|
const onRssSyncPress = useCallback(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
executeCommand({
|
executeCommand({
|
||||||
|
|
|
@ -50,6 +50,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.downloading {
|
||||||
|
composes: legendItemColor;
|
||||||
|
|
||||||
|
background-color: var(--purple);
|
||||||
|
}
|
||||||
|
|
||||||
.statistics {
|
.statistics {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'continuing': string;
|
'continuing': string;
|
||||||
|
'downloading': string;
|
||||||
'ended': string;
|
'ended': string;
|
||||||
'footer': string;
|
'footer': string;
|
||||||
'legendItem': string;
|
'legendItem': string;
|
||||||
|
|
|
@ -113,6 +113,16 @@ export default function ArtistIndexFooter() {
|
||||||
/>
|
/>
|
||||||
<div>{translate('MissingTracksArtistNotMonitored')}</div>
|
<div>{translate('MissingTracksArtistNotMonitored')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.legendItem}>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
styles.downloading,
|
||||||
|
enableColorImpairedMode && 'colorImpaired'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div>{translate('ArtistIndexFooterDownloading')}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.statistics}>
|
<div className={styles.statistics}>
|
||||||
|
|
|
@ -183,13 +183,15 @@ function ArtistIndexBanner(props: ArtistIndexBannerProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ArtistIndexProgressBar
|
<ArtistIndexProgressBar
|
||||||
|
artistId={artistId}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
status={status}
|
status={status}
|
||||||
trackCount={trackCount}
|
trackCount={trackCount}
|
||||||
trackFileCount={trackFileCount}
|
trackFileCount={trackFileCount}
|
||||||
totalTrackCount={totalTrackCount}
|
totalTrackCount={totalTrackCount}
|
||||||
posterWidth={bannerWidth}
|
width={bannerWidth}
|
||||||
detailedProgressBar={detailedProgressBar}
|
detailedProgressBar={detailedProgressBar}
|
||||||
|
isStandalone={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showTitle ? (
|
{showTitle ? (
|
||||||
|
|
|
@ -160,13 +160,15 @@ function ArtistIndexOverview(props: ArtistIndexOverviewProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ArtistIndexProgressBar
|
<ArtistIndexProgressBar
|
||||||
|
artistId={artistId}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
status={status}
|
status={status}
|
||||||
trackCount={trackCount}
|
trackCount={trackCount}
|
||||||
trackFileCount={trackFileCount}
|
trackFileCount={trackFileCount}
|
||||||
totalTrackCount={totalTrackCount}
|
totalTrackCount={totalTrackCount}
|
||||||
posterWidth={posterWidth}
|
width={posterWidth}
|
||||||
detailedProgressBar={overviewOptions.detailedProgressBar}
|
detailedProgressBar={overviewOptions.detailedProgressBar}
|
||||||
|
isStandalone={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -183,13 +183,15 @@ function ArtistIndexPoster(props: ArtistIndexPosterProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ArtistIndexProgressBar
|
<ArtistIndexProgressBar
|
||||||
|
artistId={artistId}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
status={status}
|
status={status}
|
||||||
trackCount={trackCount}
|
trackCount={trackCount}
|
||||||
trackFileCount={trackFileCount}
|
trackFileCount={trackFileCount}
|
||||||
totalTrackCount={totalTrackCount}
|
totalTrackCount={totalTrackCount}
|
||||||
posterWidth={posterWidth}
|
width={posterWidth}
|
||||||
detailedProgressBar={detailedProgressBar}
|
detailedProgressBar={detailedProgressBar}
|
||||||
|
isStandalone={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showTitle ? (
|
{showTitle ? (
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import createArtistQueueItemsDetailsSelector, {
|
||||||
|
ArtistQueueDetails,
|
||||||
|
} from 'Artist/Index/createArtistQueueDetailsSelector';
|
||||||
import ProgressBar from 'Components/ProgressBar';
|
import ProgressBar from 'Components/ProgressBar';
|
||||||
import { sizes } from 'Helpers/Props';
|
import { sizes } from 'Helpers/Props';
|
||||||
import getProgressBarKind from 'Utilities/Artist/getProgressBarKind';
|
import getProgressBarKind from 'Utilities/Artist/getProgressBarKind';
|
||||||
|
@ -6,35 +10,51 @@ import translate from 'Utilities/String/translate';
|
||||||
import styles from './ArtistIndexProgressBar.css';
|
import styles from './ArtistIndexProgressBar.css';
|
||||||
|
|
||||||
interface ArtistIndexProgressBarProps {
|
interface ArtistIndexProgressBarProps {
|
||||||
|
artistId: number;
|
||||||
monitored: boolean;
|
monitored: boolean;
|
||||||
status: string;
|
status: string;
|
||||||
trackCount: number;
|
trackCount: number;
|
||||||
trackFileCount: number;
|
trackFileCount: number;
|
||||||
totalTrackCount: number;
|
totalTrackCount: number;
|
||||||
posterWidth: number;
|
width: number;
|
||||||
detailedProgressBar: boolean;
|
detailedProgressBar: boolean;
|
||||||
|
isStandalone: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ArtistIndexProgressBar(props: ArtistIndexProgressBarProps) {
|
function ArtistIndexProgressBar(props: ArtistIndexProgressBarProps) {
|
||||||
const {
|
const {
|
||||||
|
artistId,
|
||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
trackCount,
|
trackCount,
|
||||||
trackFileCount,
|
trackFileCount,
|
||||||
totalTrackCount,
|
totalTrackCount,
|
||||||
posterWidth,
|
width,
|
||||||
detailedProgressBar,
|
detailedProgressBar,
|
||||||
|
isStandalone,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const queueDetails: ArtistQueueDetails = useSelector(
|
||||||
|
createArtistQueueItemsDetailsSelector(artistId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const newDownloads = queueDetails.count - queueDetails.tracksWithFiles;
|
||||||
const progress = trackCount ? (trackFileCount / trackCount) * 100 : 100;
|
const progress = trackCount ? (trackFileCount / trackCount) * 100 : 100;
|
||||||
const text = `${trackFileCount} / ${trackCount}`;
|
const text = newDownloads
|
||||||
|
? `${trackFileCount} + ${newDownloads} / ${trackCount}`
|
||||||
|
: `${trackFileCount} / ${trackCount}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
className={styles.progressBar}
|
className={styles.progressBar}
|
||||||
containerClassName={styles.progress}
|
containerClassName={isStandalone ? undefined : styles.progress}
|
||||||
progress={progress}
|
progress={progress}
|
||||||
kind={getProgressBarKind(status, monitored, progress)}
|
kind={getProgressBarKind(
|
||||||
|
status,
|
||||||
|
monitored,
|
||||||
|
progress,
|
||||||
|
queueDetails.count > 0
|
||||||
|
)}
|
||||||
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
||||||
showText={detailedProgressBar}
|
showText={detailedProgressBar}
|
||||||
text={text}
|
text={text}
|
||||||
|
@ -42,8 +62,9 @@ function ArtistIndexProgressBar(props: ArtistIndexProgressBarProps) {
|
||||||
trackFileCount,
|
trackFileCount,
|
||||||
trackCount,
|
trackCount,
|
||||||
totalTrackCount,
|
totalTrackCount,
|
||||||
|
downloadingCount: queueDetails.count,
|
||||||
})}
|
})}
|
||||||
width={posterWidth}
|
width={width}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@ import ArtistNameLink from 'Artist/ArtistNameLink';
|
||||||
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
|
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
|
||||||
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
|
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
|
||||||
import createArtistIndexItemSelector from 'Artist/Index/createArtistIndexItemSelector';
|
import createArtistIndexItemSelector from 'Artist/Index/createArtistIndexItemSelector';
|
||||||
|
import ArtistIndexProgressBar from 'Artist/Index/ProgressBar/ArtistIndexProgressBar';
|
||||||
import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell';
|
import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell';
|
||||||
import { ARTIST_SEARCH, REFRESH_ARTIST } from 'Commands/commandNames';
|
import { ARTIST_SEARCH, REFRESH_ARTIST } from 'Commands/commandNames';
|
||||||
import HeartRating from 'Components/HeartRating';
|
import HeartRating from 'Components/HeartRating';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
import ProgressBar from 'Components/ProgressBar';
|
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||||
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||||
|
@ -23,7 +23,6 @@ import Column from 'Components/Table/Column';
|
||||||
import TagListConnector from 'Components/TagListConnector';
|
import TagListConnector from 'Components/TagListConnector';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import getProgressBarKind from 'Utilities/Artist/getProgressBarKind';
|
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import AlbumsCell from './AlbumsCell';
|
import AlbumsCell from './AlbumsCell';
|
||||||
|
@ -301,23 +300,18 @@ function ArtistIndexRow(props: ArtistIndexRowProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'trackProgress') {
|
if (name === 'trackProgress') {
|
||||||
const progress = trackCount
|
|
||||||
? (trackFileCount / trackCount) * 100
|
|
||||||
: 100;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||||
<ProgressBar
|
<ArtistIndexProgressBar
|
||||||
progress={progress}
|
artistId={artistId}
|
||||||
kind={getProgressBarKind(status, monitored, progress)}
|
monitored={monitored}
|
||||||
showText={true}
|
status={status}
|
||||||
text={`${trackFileCount} / ${trackCount}`}
|
trackCount={trackCount}
|
||||||
title={translate('ArtistProgressBarText', {
|
trackFileCount={trackFileCount}
|
||||||
trackFileCount,
|
totalTrackCount={totalTrackCount}
|
||||||
trackCount,
|
|
||||||
totalTrackCount,
|
|
||||||
})}
|
|
||||||
width={125}
|
width={125}
|
||||||
|
detailedProgressBar={true}
|
||||||
|
isStandalone={true}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
|
||||||
|
export interface ArtistQueueDetails {
|
||||||
|
count: number;
|
||||||
|
tracksWithFiles: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createArtistQueueDetailsSelector(artistId: number) {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.queue.details.items,
|
||||||
|
(queueItems) => {
|
||||||
|
return queueItems.reduce(
|
||||||
|
(acc: ArtistQueueDetails, item) => {
|
||||||
|
if (item.artistId !== artistId) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.count += item.trackFileCount ?? 0;
|
||||||
|
|
||||||
|
if (item.trackHasFileCount) {
|
||||||
|
acc.tracksWithFiles += item.trackHasFileCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
count: 0,
|
||||||
|
tracksWithFiles: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createArtistQueueDetailsSelector;
|
|
@ -1,6 +1,15 @@
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
|
||||||
function getProgressBarKind(status, monitored, progress) {
|
function getProgressBarKind(
|
||||||
|
status: string,
|
||||||
|
monitored: boolean,
|
||||||
|
progress: number,
|
||||||
|
isDownloading: boolean
|
||||||
|
) {
|
||||||
|
if (isDownloading) {
|
||||||
|
return kinds.PURPLE;
|
||||||
|
}
|
||||||
|
|
||||||
if (progress === 100) {
|
if (progress === 100) {
|
||||||
return status === 'ended' ? kinds.SUCCESS : kinds.PRIMARY;
|
return status === 'ended' ? kinds.SUCCESS : kinds.PRIMARY;
|
||||||
}
|
}
|
|
@ -25,6 +25,8 @@ interface Queue extends ModelBase {
|
||||||
protocol: string;
|
protocol: string;
|
||||||
downloadClient: string;
|
downloadClient: string;
|
||||||
outputPath: string;
|
outputPath: string;
|
||||||
|
trackFileCount: number;
|
||||||
|
trackHasFileCount: number;
|
||||||
artistId?: number;
|
artistId?: number;
|
||||||
albumId?: number;
|
albumId?: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ namespace Lidarr.Api.V1.Queue
|
||||||
public bool DownloadClientHasPostImportCategory { get; set; }
|
public bool DownloadClientHasPostImportCategory { get; set; }
|
||||||
public string Indexer { get; set; }
|
public string Indexer { get; set; }
|
||||||
public string OutputPath { get; set; }
|
public string OutputPath { get; set; }
|
||||||
|
public int TrackFileCount { get; set; }
|
||||||
|
public int TrackHasFileCount { get; set; }
|
||||||
public bool DownloadForced { get; set; }
|
public bool DownloadForced { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +55,8 @@ namespace Lidarr.Api.V1.Queue
|
||||||
var customFormats = model.RemoteAlbum?.CustomFormats;
|
var customFormats = model.RemoteAlbum?.CustomFormats;
|
||||||
var customFormatScore = model.Artist?.QualityProfile?.Value?.CalculateCustomFormatScore(customFormats) ?? 0;
|
var customFormatScore = model.Artist?.QualityProfile?.Value?.CalculateCustomFormatScore(customFormats) ?? 0;
|
||||||
|
|
||||||
|
var albumRelease = model.Album?.AlbumReleases?.Value?.SingleOrDefault(x => x.Monitored);
|
||||||
|
|
||||||
return new QueueResource
|
return new QueueResource
|
||||||
{
|
{
|
||||||
Id = model.Id,
|
Id = model.Id,
|
||||||
|
@ -80,6 +84,8 @@ namespace Lidarr.Api.V1.Queue
|
||||||
DownloadClientHasPostImportCategory = model.DownloadClientHasPostImportCategory,
|
DownloadClientHasPostImportCategory = model.DownloadClientHasPostImportCategory,
|
||||||
Indexer = model.Indexer,
|
Indexer = model.Indexer,
|
||||||
OutputPath = model.OutputPath,
|
OutputPath = model.OutputPath,
|
||||||
|
TrackFileCount = albumRelease?.Tracks?.Value?.Count ?? 0,
|
||||||
|
TrackHasFileCount = albumRelease?.Tracks?.Value?.Count(x => x.HasFile) ?? 0,
|
||||||
DownloadForced = model.DownloadForced
|
DownloadForced = model.DownloadForced
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,12 +111,13 @@
|
||||||
"ArtistClickToChangeAlbum": "Click to change album",
|
"ArtistClickToChangeAlbum": "Click to change album",
|
||||||
"ArtistEditor": "Artist Editor",
|
"ArtistEditor": "Artist Editor",
|
||||||
"ArtistFolderFormat": "Artist Folder Format",
|
"ArtistFolderFormat": "Artist Folder Format",
|
||||||
|
"ArtistIndexFooterDownloading": "Downloading",
|
||||||
"ArtistIsMonitored": "Artist is monitored",
|
"ArtistIsMonitored": "Artist is monitored",
|
||||||
"ArtistIsUnmonitored": "Artist is unmonitored",
|
"ArtistIsUnmonitored": "Artist is unmonitored",
|
||||||
"ArtistMonitoring": "Artist Monitoring",
|
"ArtistMonitoring": "Artist Monitoring",
|
||||||
"ArtistName": "Artist Name",
|
"ArtistName": "Artist Name",
|
||||||
"ArtistNameHelpText": "The name of the artist/album to exclude (can be anything meaningful)",
|
"ArtistNameHelpText": "The name of the artist/album to exclude (can be anything meaningful)",
|
||||||
"ArtistProgressBarText": "{trackFileCount} / {trackCount} (Total: {totalTrackCount})",
|
"ArtistProgressBarText": "{trackFileCount} / {trackCount} (Total: {totalTrackCount}, Downloading: {downloadingCount})",
|
||||||
"ArtistType": "Artist Type",
|
"ArtistType": "Artist Type",
|
||||||
"Artists": "Artists",
|
"Artists": "Artists",
|
||||||
"ArtistsEditRootFolderHelpText": "Moving artists to the same root folder can be used to rename artist folders to match updated name or naming format",
|
"ArtistsEditRootFolderHelpText": "Moving artists to the same root folder can be used to rename artist folders to match updated name or naming format",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue