diff --git a/src/domains/scrobbleAlbum/partials/Tracklist.tsx b/src/domains/scrobbleAlbum/partials/Tracklist.tsx
index 2426d87..cd4787d 100644
--- a/src/domains/scrobbleAlbum/partials/Tracklist.tsx
+++ b/src/domains/scrobbleAlbum/partials/Tracklist.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, Suspense, useMemo, useCallback, ChangeEventHandler } from 'react';
+import { useState, useEffect, Suspense, useMemo, useCallback, ChangeEventHandler, useContext } from 'react';
import lazyWithPreload from 'react-lazy-with-preload';
import { useDispatch } from 'react-redux';
import { Trans, useTranslation } from 'react-i18next';
@@ -18,14 +18,13 @@ import { enqueueScrobble } from 'store/actions/scrobbleActions';
import { DEFAULT_SONG_DURATION, getAmznLink } from 'Constants';
+import { cleanTitleWithPattern, CleanupPatternContext } from '../CleanupContext';
import { EmptyDiscMessage } from './EmptyDiscMessage';
-import { TracklistCleanupForm } from './TracklistCleanupForm';
+import { TracklistFilter } from './TracklistFilter';
-import { cleanupLastEndStringOccurrence } from 'utils/common';
import type { Album, DiscogsAlbum } from 'utils/types/album';
import type { Scrobble } from 'utils/types/scrobble';
import type { Track } from 'utils/types/track';
-import { usePatternSmart } from 'hooks/usePatternSmart';
const DateTimePicker = lazyWithPreload(() => import('components/DateTimePicker'));
@@ -41,9 +40,10 @@ export default function Tracklist({ albumInfo, tracks }: { albumInfo: Album | nu
// ToDo: simplify customTimestamp + useCustomTimestamp
const [customTimestamp, setCustomTimestamp] = useState(new Date());
const [useCustomTimestamp, setUseCustomTimestamp] = useState(false);
- const [useCleanupPattern, setUseCleanupPattern] = useState('');
const [selectedTracks, setSelectedTracks] = useState>(new Set());
const [totalDuration, setTotalDuration] = useState(0);
+ const { cleanupPattern } = useContext(CleanupPatternContext);
+
const albumHasTracks = tracks && tracks.length > 0;
const hasAlbumInfo = !!albumInfo && Object.keys(albumInfo).length > 0;
const durationFormat = totalDuration > 3600 ? 'H:mm:ss' : 'mm:ss';
@@ -98,15 +98,6 @@ export default function Tracklist({ albumInfo, tracks }: { albumInfo: Album | nu
setCanScrobble(true);
};
- const handleCleanupPatternChange = useCallback>((event) => {
- setUseCleanupPattern(event.target.value);
- }, []);
-
- const cleanupPatternSmart = usePatternSmart({
- pattern: useCleanupPattern,
- totalTrackNames: Array.from(tracks) as Scrobble[],
- });
-
const scrobbleSelectedTracks = () => {
const userHasNotSelectedTracks = selectedTracks.size < 1;
const timestampCalculationSubstractsTime = !useCustomTimestamp;
@@ -125,8 +116,7 @@ export default function Tracklist({ albumInfo, tracks }: { albumInfo: Album | nu
.reduce((result, track) => {
const newTrack = {
...track,
- title:
- cleanupPatternSmart !== '' ? cleanupLastEndStringOccurrence(track.title, cleanupPatternSmart) : track.title,
+ title: cleanTitleWithPattern(track.title, cleanupPattern),
album: albumInfo?.name || '',
albumArtist: albumInfo?.artist || '',
timestamp: rollingTimestamp,
@@ -259,20 +249,18 @@ export default function Tracklist({ albumInfo, tracks }: { albumInfo: Album | nu
)}
+
-
-
>
);
}
diff --git a/src/domains/scrobbleAlbum/partials/TracklistCleanupForm.tsx b/src/domains/scrobbleAlbum/partials/TracklistCleanupForm.tsx
deleted file mode 100644
index 1af361c..0000000
--- a/src/domains/scrobbleAlbum/partials/TracklistCleanupForm.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { ChangeEventHandler, useState } from 'react';
-import { Trans } from 'react-i18next';
-import { Alert, FormGroup, Input } from 'reactstrap';
-import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-
-type TracklistCleanupFormProps = {
- onCleanupPatternChange: ChangeEventHandler
;
-};
-
-export function TracklistCleanupForm({ onCleanupPatternChange }: TracklistCleanupFormProps) {
- const [showCleanupPatternCopy, setShowCleanupPatternCopy] = useState(false);
-
- const toggleCleanupPatternCopy = () => {
- setShowCleanupPatternCopy(!showCleanupPatternCopy);
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/domains/scrobbleAlbum/partials/TracklistFilter.scss b/src/domains/scrobbleAlbum/partials/TracklistFilter.scss
new file mode 100644
index 0000000..c2cd6f8
--- /dev/null
+++ b/src/domains/scrobbleAlbum/partials/TracklistFilter.scss
@@ -0,0 +1,5 @@
+.TracklistFilter {
+ &-container {
+ height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2)); // boils down to 32px
+ }
+}
diff --git a/src/domains/scrobbleAlbum/partials/TracklistFilter.tsx b/src/domains/scrobbleAlbum/partials/TracklistFilter.tsx
new file mode 100644
index 0000000..cf73afa
--- /dev/null
+++ b/src/domains/scrobbleAlbum/partials/TracklistFilter.tsx
@@ -0,0 +1,48 @@
+import { ChangeEventHandler, useCallback, useContext, useState } from 'react';
+import { Trans } from 'react-i18next';
+import { Input } from 'reactstrap';
+import { faFilter, faX } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { CleanupPatternContext, strToCleanupPattern } from '../CleanupContext';
+
+import './TracklistFilter.scss';
+
+export function TracklistFilter() {
+ const [showCleanupPatternCopy, setShowCleanupPatternCopy] = useState(false);
+ const [showForm, setFormVisible] = useState(false);
+ const { cleanupPattern, setCleanupPattern } = useContext(CleanupPatternContext);
+ const [value, setValue] = useState('');
+
+ const handleCleanupPatternChange = useCallback>(
+ (event) => {
+ const { value } = event.target;
+ setValue(value);
+ setCleanupPattern(strToCleanupPattern(value));
+ },
+ [setCleanupPattern]
+ );
+
+ return (
+
+ );
+}
diff --git a/src/hooks/usePatternSmart.ts b/src/hooks/usePatternSmart.ts
deleted file mode 100644
index 49c1542..0000000
--- a/src/hooks/usePatternSmart.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { useMemo } from 'react';
-import { keyOfLongestArray } from 'utils/common';
-import type { Scrobble } from 'utils/types/scrobble';
-
-export type UsePatternSmartParams = {
- pattern?: string;
- totalTrackNames: Scrobble[];
-};
-
-export type UsePatternSmartResult = string;
-
-export type UsePatternSmartHook = (params: UsePatternSmartParams) => UsePatternSmartResult;
-
-const USE_PATTERN_SMART_EXTRA_CHARS = ['-', '- ', '/', '/ '];
-
-export const usePatternSmart: UsePatternSmartHook = ({ pattern, totalTrackNames }) => {
- const allPatterns = useMemo>(() => {
- const patterns: Record = {};
-
- USE_PATTERN_SMART_EXTRA_CHARS.forEach((extraChar: string) => {
- totalTrackNames.forEach((track: Scrobble) => {
- if (track.title.endsWith(extraChar + pattern)) {
- patterns[extraChar] ??= [];
- patterns[extraChar].push(track);
- }
- });
- });
-
- return patterns;
- }, [pattern, totalTrackNames]);
-
- if (!pattern) {
- return '';
- }
-
- return Object.keys(allPatterns).length > 0 ? `${keyOfLongestArray(allPatterns)}${pattern}` : pattern;
-};
diff --git a/src/utils/common.test.ts b/src/utils/common.test.ts
index 5acd5e5..214c16c 100644
--- a/src/utils/common.test.ts
+++ b/src/utils/common.test.ts
@@ -1,12 +1,4 @@
-import {
- castArray,
- usernameIsValid,
- sanitizeProvider,
- replaceLastOccurrence,
- cleanupLastEndStringOccurrence,
- keyOfLongestArray,
- sha256,
-} from './common';
+import { castArray, usernameIsValid, sanitizeProvider, sha256 } from './common';
describe('`castArray` helper', () => {
it('returns an array when given an array', () => {
@@ -57,82 +49,6 @@ describe('`sanitizeProvider` helper', () => {
});
});
-describe('`replaceLastOccurrence` helper', () => {
- it('returns full string with unique replacement done', () => {
- expect(replaceLastOccurrence('A B C D', 'D', 'E')).toBe('A B C E');
- });
-
- it('returns full string with only last replacement done', () => {
- expect(replaceLastOccurrence('A X B C X', 'X', 'E')).toBe('A X B C E');
- });
-});
-
-describe('`cleanupLastEndStringOccurrence` helper', () => {
- it('returns full string without the last ending pattern', () => {
- expect(cleanupLastEndStringOccurrence('cast no shadow live', 'live')).toBe('cast no shadow');
- });
-
- it('returns full string without the last ending occurrence', () => {
- expect(cleanupLastEndStringOccurrence('live forever live', 'live')).toBe('live forever');
- });
-
- it('returns full string without the last with extra alphabetic characters', () => {
- expect(cleanupLastEndStringOccurrence('live forever (remastered)', '(remastered)')).toBe('live forever');
- expect(
- cleanupLastEndStringOccurrence('Dazed and Confused - 3/23/69 Top Gear;Remaster', ' - 3/23/69 Top Gear;Remaster')
- ).toBe('Dazed and Confused');
- });
-
- it('returns full string if cleanup pattern is not at end of string', () => {
- expect(cleanupLastEndStringOccurrence('live forever (live)', 'live')).toBe('live forever (live)');
- });
-});
-
-describe('`keyOfLongestArray` helper', () => {
- it('return first array as longest', () => {
- const objectToAnalyze = {
- longest: Array(5).fill(null),
- second: Array(3).fill(null),
- third: Array(3).fill(null),
- };
-
- expect(keyOfLongestArray(objectToAnalyze)).toBe('longest');
- });
-
- it('return last array as longest', () => {
- const objectToAnalyze = {
- first: Array(1).fill(null),
- second: Array(3).fill(null),
- longest: Array(5).fill(null),
- };
-
- expect(keyOfLongestArray(objectToAnalyze)).toBe('longest');
- });
-
- it('return a central array as longest', () => {
- const objectToAnalyze = {
- first: Array(1).fill(null),
- second: Array(3).fill(null),
- longest: Array(5).fill(null),
- fourth: Array(3).fill(null),
- };
-
- expect(keyOfLongestArray(objectToAnalyze)).toBe('longest');
- });
-
- it('return the last array as one of the longest', () => {
- const objectToAnalyze = {
- first: Array(1).fill(null),
- second: Array(3).fill(null),
- third: Array(5).fill(null),
- fourth: Array(3).fill(null),
- longest: Array(5).fill(null),
- };
-
- expect(keyOfLongestArray(objectToAnalyze)).toBe('longest');
- });
-});
-
describe('`sha256` digest', () => {
it('returns the correct SHA-256 hash for a given string', () => {
const str = 'hello world';
diff --git a/src/utils/common.ts b/src/utils/common.ts
index 3bf9da9..b694bac 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -1,5 +1,4 @@
import { PROVIDER_DISCOGS, PROVIDER_NAME, Provider } from 'Constants';
-import type { Scrobble } from './types/scrobble';
export function castArray(x: any): Array {
return Array.isArray(x) ? x : [x];
@@ -17,38 +16,6 @@ export function sanitizeProvider(provider: string, defaultProvder: Provider = PR
return defaultProvder;
}
-export function replaceLastOccurrence(fullString: string, lastString: string, replaceString: string) {
- const lastStringIndex = fullString.lastIndexOf(lastString);
-
- if (lastStringIndex === -1) {
- return fullString;
- }
-
- return `${fullString.substring(0, lastStringIndex)}${replaceString}${fullString.substring(lastStringIndex + lastString.length)}`;
-}
-
-export function cleanupLastEndStringOccurrence(fullString: string, pattern: string) {
- if (fullString.endsWith(pattern)) {
- return fullString.substring(0, fullString.length - pattern.length).trim();
- }
-
- return fullString;
-}
-
-export function keyOfLongestArray(objectOfArrays: Record): string {
- let keyOfLongestArray: string = null;
- let longestArrayLength = -1;
-
- for (const [arrayKey, arrayItems] of Object.entries(objectOfArrays)) {
- if (arrayItems.length >= longestArrayLength) {
- longestArrayLength = arrayItems.length;
- keyOfLongestArray = arrayKey;
- }
- }
-
- return keyOfLongestArray;
-}
-
export async function sha256(str: string) {
const msgUint8 = new TextEncoder().encode(str); // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); // hash the message