From 5d58951225dba66402fc113d390ec602043d263e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 15 Sep 2020 15:51:42 -0300 Subject: better hiding track numbering in queue --- playlist-utils.js | 80 +++++++++++++++++++++++++++++++++++++++---------------- todo.txt | 2 ++ ui.js | 7 +++++ 3 files changed, 66 insertions(+), 23 deletions(-) diff --git a/playlist-utils.js b/playlist-utils.js index 452b705..68cba56 100644 --- a/playlist-utils.js +++ b/playlist-utils.js @@ -520,36 +520,70 @@ function getTrackIndexInParent(track) { const nameWithoutTrackNumberSymbol = Symbol('Cached name without track number') function getNameWithoutTrackNumber(track) { - // Be lazy and reuse an old value if possible! Don't do this if the track's - // name has changed. - const [oldName, cachedValue] = track[nameWithoutTrackNumberSymbol] || [] - if (cachedValue && track.name === oldName) { - return cachedValue + // A "part" is a series of numeric digits, separated from other parts by + // whitespace and dashes, always preceding either the first non-numeric/ + // separator character or (if there are no such characters) the first word + // (i.e. last whitespace). + const getNumberOfParts = ({ name }) => { + const match = name.match(/[^0-9\-\s]/) + if (match) { + name = name.slice(0, match.index) + } else if (name.includes(' ')) { + name = name.slice(0, name.lastIndexOf(' ')) + } else { + return 0 + } + name = name.replace(/[\-\s]+$/, '') + return name.split(/[\-\s]+/g).length + } + + const removeParts = (name, numParts) => { + const regex = new RegExp(`([0-9]+[\\-\\s]+){${numParts},${numParts}}`) + return track.name.replace(regex, '') } - // This is the expression that matches a track number at the start of - // a track's title. - const regex = /^[0-9\-\s]+/ + // Despite this function returning a single string for one track, that value + // depends on the names of all other tracks under the same parent. We still + // store individual track -> name data on the track object, but the parent + // gets an additional cache for the names of its children tracks as well as + // the number of "parts" (the value directly based upon those names, and + // useful in computing the name data for other children tracks). - // First we need to determine whether every track in the group even starts - // with a track number. const parent = track[parentSymbol] if (parent) { - const names = parent.items.filter(isTrack).map(t => t.name) - if (names.some(name => !regex.test(name))) { - // If any of the names don't match the track number regex, just return - // the track name unmodified. - return track.name + const [trackNames, cachedNumParts] = parent[nameWithoutTrackNumberSymbol] || [] + const tracks = parent.items.filter(isTrack) + if (trackNames && tracks.length === trackNames.length && tracks.every((t, i) => t.name === trackNames[i])) { + const [, oldName, oldNumParts, cachedValue] = track[nameWithoutTrackNumberSymbol] || [] + if (cachedValue && track.name === oldName && cachedNumParts === oldNumParts) { + return cachedValue + } else { + // Individual track cache outdated. + const value = removeParts(track.name, cachedNumParts) + track[nameWithoutTrackNumberSymbol] = [true, track.name, cachedNumParts, value] + return value + } + } else { + // Group (parent) cache outdated. + const numParts = Math.min(...tracks.map(getNumberOfParts)) + parent[nameWithoutTrackNumberSymbol] = [tracks.map(t => t.name), numParts] + // Parent changed so track cache changed is outdated too. + const value = removeParts(track.name, numParts) + track[nameWithoutTrackNumberSymbol] = [true, track.name, numParts, value] + return value + } + } else { + const [oldHadParent, oldName, , cachedValue] = track[nameWithoutTrackNumberSymbol] || [] + if (cachedValue && !oldHadParent && track.name === oldName) { + return cachedValue + } else { + // Track cache outdated. + const numParts = getNumberOfParts(track) + const value = removeParts(track.name, numParts) + track[nameWithoutTrackNumberSymbol] = [false, track.name, numParts, value] + return value } } - - // Now actually perform the replacement to get rid of the track number! - const value = track.name.replace(regex, '') - - // Cache the value, so we don't need to do this whole process again. - track[nameWithoutTrackNumberSymbol] = [track.name, value] - - return value } function isGroup(obj) { diff --git a/todo.txt b/todo.txt index c58c1cb..b210799 100644 --- a/todo.txt +++ b/todo.txt @@ -462,9 +462,11 @@ TODO: Only count *consistently* formatted text, across all tracks in a group, it is the only track in the group which is formatted '## # '. It does follow the formatting '## ' as all other tracks do, so only the first digits, and following whitespace, should be removed. + (Done!) TODO: Related to the above - always keep at least one word. See CANSLP's new release "untitled folder", with tracks named "01 1", "02 2", etc. + (Done!) TODO: Update to work with IPC server mpv (and socat). (Done!) diff --git a/ui.js b/ui.js index de0b214..5e6ef6e 100644 --- a/ui.js +++ b/ui.js @@ -60,6 +60,7 @@ const TuiTextEditor = require('tui-text-editor') const { promisify } = require('util') const { spawn } = require('child_process') +const { orderBy } = require('natural-orderby') const fs = require('fs') const open = require('open') const path = require('path') @@ -317,6 +318,7 @@ class AppElement extends FocusElement { {value: 'shuffle-groups', label: 'Shuffle order of groups'}, {value: 'reverse', label: 'Reverse all'}, {value: 'reverse-groups', label: 'Reverse order of groups'}, + {value: 'alphabetic', label: 'Alphabetically'}, {value: 'normal', label: 'In order'} ], this.showContextMenu) @@ -1475,6 +1477,11 @@ class AppElement extends FocusElement { item = {items: flattenGrouplike(item).items.reverse()} } else if (order === 'reverse-groups') { item = reverseOrderOfGroups(item) + } else if (order === 'alphabetic') { + item = { + name: `${oldName} (alphabetic)`, + items: orderBy(flattenGrouplike(item).items, getNameWithoutTrackNumber) + } } } else { // Make it into a grouplike that just contains itself. -- cgit 1.3.0-6-gf8a5