removed unwanted bar now with waveforms

This commit is contained in:
Mark Gregory
2026-11-06 10:08:17 +00:00
parent dbaa74576d
commit 15c9f5914d
3 changed files with 35 additions and 9 deletions
+6 -2
View File
@@ -19,9 +19,10 @@ struct DJLibraryDragTitle: NSViewRepresentable {
let fontSize: Double
let select: () -> Void
var doubleClick: (() -> Void)? = nil
var selectAll: (() -> Void)? = nil
func makeNSView(context: Context) -> DJLibraryDragView { DJLibraryDragView() }
func updateNSView(_ view: DJLibraryDragView,context: Context) {
view.title = title;view.reference = reference;view.fontSize = fontSize;view.select = select;view.doubleClick = doubleClick
view.title = title;view.reference = reference;view.fontSize = fontSize;view.select = select;view.doubleClick = doubleClick;view.selectAllTracks = selectAll
view.toolTip = "Double-click to load alternating decks, or drag \(title) to either deck";view.setAccessibilityLabel(title);view.needsDisplay = true
}
func sizeThatFits(_ proposal: ProposedViewSize,nsView: DJLibraryDragView,context: Context) -> CGSize? { .init(width:proposal.width ?? 240,height:24) }
@@ -33,6 +34,9 @@ final class DJLibraryDragView: NSView {
var fontSize: Double = 11
var select: (() -> Void)?
var doubleClick: (() -> Void)?
var selectAllTracks: (() -> Void)?
override var acceptsFirstResponder: Bool { true }
@objc override func selectAll(_ sender: Any?) { selectAllTracks?() }
private var armed = false
private var dragging = false
override init(frame: NSRect) { super.init(frame:frame);setAccessibilityElement(true);setAccessibilityRole(.staticText) }
@@ -44,7 +48,7 @@ final class DJLibraryDragView: NSView {
let rect = NSRect(x:0,y:(bounds.height-ceil(font.ascender-font.descender))/2,width:bounds.width,height:ceil(font.ascender-font.descender)+2)
(title as NSString).draw(in:rect,withAttributes:[.font:font,.foregroundColor:NSColor.white,.paragraphStyle:paragraph])
}
override func mouseDown(with event: NSEvent) { armed = true;dragging = false }
override func mouseDown(with event: NSEvent) { window?.makeFirstResponder(self);armed = true;dragging = false }
override func mouseDragged(with event: NSEvent) {
guard armed else { return }
dragging = true
+27 -7
View File
@@ -809,6 +809,7 @@ struct DJConsole: View {
@State private var query = ""
@State private var playlist: Int64?
@State private var playlistIDs = [Int64]()
@State private var selection = Set<Int64>()
@State private var selected: Int64?
@State private var playlistEditor = false
@State private var playlistName = ""
@@ -819,6 +820,19 @@ struct DJConsole: View {
if playlist != nil { let lookup = Dictionary(uniqueKeysWithValues:all.map{($0.id,$0)});return playlistIDs.compactMap{lookup[$0]} }
return all
}
private var selectedTracks: [Song] { songs.filter { selection.contains($0.id) } }
private func selectTrack(_ id: Int64) {
let modifiers = NSEvent.modifierFlags
if modifiers.contains(.shift),let anchor = selected,let first = songs.firstIndex(where:{$0.id == anchor}),let last = songs.firstIndex(where:{$0.id == id}) {
let range = Set(songs[min(first,last)...max(first,last)].map(\.id))
if modifiers.contains(.command) { selection.formUnion(range) } else { selection = range }
} else if modifiers.contains(.command) {
if !selection.insert(id).inserted { selection.remove(id) }
selected = id
} else { selection = [id];selected = id }
}
private func selectAllTracks() { selection = Set(songs.map(\.id));selected = songs.first?.id }
private func contextTracks(_ song: Song) -> [Song] { selection.contains(song.id) ? selectedTracks : [song] }
private var selectedSong: Song? { system.songs.first{$0.id == selected} }
private var pages: Int { max(1,(songs.count+49)/50) }
private var visible: [Song] { Array(songs.dropFirst(min(page,pages-1)*50).prefix(50)) }
@@ -879,7 +893,8 @@ struct DJConsole: View {
.alert("DJ workstation",isPresented:Binding(get:{system.error != nil},set:{if !$0 { system.error = nil }})) { Button("OK") { system.error = nil } } message: { Text(system.error ?? "") }
.onAppear { refreshPlaylist();connectMIDI() }
.onChange(of:selected) { _ in connectMIDI() }
.onChange(of:playlist) { _ in refreshPlaylist();page = 0;selected = nil }
.onChange(of:selection) { ids in if selected == nil || !ids.contains(selected!) { selected = songs.first(where:{ids.contains($0.id)})?.id };connectMIDI() }
.onChange(of:playlist) { _ in refreshPlaylist();page = 0;selected = nil;selection = [] }
.onChange(of:system.songs) { _ in refreshPlaylist();connectMIDI() }
.onChange(of:system.playlists) { _ in if let playlist,!system.playlists.contains(where:{$0.id == playlist}) { self.playlist = nil };refreshPlaylist() }
.onChange(of:query) { _ in page = 0 }
@@ -909,7 +924,8 @@ struct DJConsole: View {
Button { favouritesOnly.toggle() } label: { Label("Favourites",systemImage:favouritesOnly ? "star.fill" : "star") }.foregroundStyle(favouritesOnly ? Color.yellow : Color.secondary).help("Show favourite tracks only")
Spacer(minLength:12)
TextField("Search DJ music",text:$query).textFieldStyle(.roundedBorder).controlSize(.regular).frame(maxWidth:260)
Button { system.automix.enqueue(songs) } label: { Image(systemName:"text.badge.plus") }.help("Add this library or playlist to the DJ queue").disabled(songs.isEmpty)
Button { system.automix.enqueue(selectedTracks) } label: { Image(systemName:"text.badge.plus") }.help("Add selected tracks to the DJ queue").disabled(selectedTracks.isEmpty)
Text("\(selection.count) selected").font(.caption).foregroundStyle(.secondary)
Button("Import Files / Folder…") { importMedia() }
Spacer(minLength:0)
Button { page = max(0,page-1) } label: { Image(systemName:"chevron.left") }.disabled(page == 0)
@@ -917,11 +933,11 @@ struct DJConsole: View {
Button { page = min(pages-1,page+1) } label: { Image(systemName:"chevron.right") }.disabled(page >= pages-1)
}.controlSize(.small)
ScrollViewReader { reader in
List(selection:$selected) {
List(selection:$selection) {
ForEach(visible) { song in
HStack(spacing:8) {
Button { system.toggleFavourite(song.id) } label: { GoldStar(filled:system.favourites.contains(song.id),size:15) }.buttonStyle(.plain).accessibilityLabel(system.favourites.contains(song.id) ? "Remove \(song.title) from favourites" : "Favourite \(song.title)")
DJLibraryDragTitle(title:song.title,reference:system.dragReference(song.id),fontSize:system.settings.libraryFont,select:{selected = song.id},doubleClick:{system.loadFromLibraryDoubleClick(song)}).frame(maxWidth:.infinity,alignment:.leading)
DJLibraryDragTitle(title:song.title,reference:system.dragReference(song.id),fontSize:system.settings.libraryFont,select:{selectTrack(song.id)},doubleClick:{system.loadFromLibraryDoubleClick(song)},selectAll:{selectAllTracks()}).frame(maxWidth:.infinity,alignment:.leading)
Text(song.artist).font(.system(size:10)).foregroundStyle(.secondary).lineLimit(1).frame(width:130,alignment:.leading)
Text(DJStyle.stamp(song.duration)).font(.system(size:10).monospacedDigit()).foregroundStyle(.secondary).frame(width:35)
Button { system.automix.enqueue([song]) } label: { Image(systemName:"plus") }.accessibilityLabel("Add \(song.title) to DJ queue")
@@ -932,9 +948,10 @@ struct DJConsole: View {
}
if visible.isEmpty { Text("Import music or choose another playlist.").font(.caption).foregroundStyle(.secondary) }
}.listStyle(.inset).scrollContentBackground(.hidden).background(DJStyle.panel,in:RoundedRectangle(cornerRadius:6))
.onCommand(#selector(NSResponder.selectAll(_:))) { selectAllTracks() }
.background(DJLibraryPageScroll { if page < pages-1 { page += 1 } })
.onChange(of:page) { _ in
selected = visible.first?.id
selected = visible.first?.id;selection = Set(visible.first.map{[$0.id]} ?? [])
if let first = visible.first?.id { DispatchQueue.main.async { reader.scrollTo(first,anchor:.top) } }
}
}
@@ -945,12 +962,15 @@ struct DJConsole: View {
Button("Load into Deck B") { system.load(song,into:"B") }
Divider()
Button(system.favourites.contains(song.id) ? "Remove from Favourites" : "Add to Favourites") { system.toggleFavourite(song.id) }
Button("Add to DJ Queue") { system.automix.enqueue([song]) }
Button("Select All Songs") { selectAllTracks() }.keyboardShortcut("a",modifiers:.command)
Button("Add \(contextTracks(song).count) Selected Tracks to DJ Queue") { system.automix.enqueue(contextTracks(song)) }
Menu("Waveforms") {
Button("Generate Waveforms for \(contextTracks(song).count) Selected Tracks") { system.generateWaveforms(Set(contextTracks(song).map(\.id))) }
Button("Generate Waveform for This Track") { system.generateWaveforms([song.id]) }
Button("Generate Waveforms for Loaded Decks") { system.generateWaveforms(Set([system.a.song?.id,system.b.song?.id].compactMap{$0})) }.disabled(system.a.song == nil && system.b.song == nil)
Button("Generate Waveforms for All DJ Library Tracks") { system.generateWaveforms() }
Divider()
Button("Regenerate Selected Waveforms") { system.generateWaveforms(Set(contextTracks(song).map(\.id)),force:true) }
Button("Regenerate This Waveform") { system.generateWaveforms([song.id],force:true) }
Button("Regenerate All DJ Library Waveforms") { system.generateWaveforms(force:true) }
}.disabled(system.generatingWaveforms || system.importing)
@@ -1000,7 +1020,7 @@ struct DJConsole: View {
let rows = songs;guard !rows.isEmpty else { return }
let current = rows.firstIndex(where:{$0.id == selected}) ?? (value > 0 ? -1 : rows.count)
let index = min(rows.count-1,max(0,current+(value >= 0 ? 1 : -1)))
selected = rows[index].id;page = index/50
selected = rows[index].id;selection = [rows[index].id];page = index/50
} else if name == "Preview selected",let selectedSong { system.previewLibrary(selectedSong) }
else { system.action(name,value:value,relative:relative,selected:selectedSong) }
}
+2
View File
@@ -91,3 +91,5 @@ Dragging a DJ waveform previews short audio grains through that deck’s EQ, lev
The DJ library advances to the next page when a user scroll reaches the bottom. Page changes return the list to the top and select the first track. Right-click a DJ track and choose Waveforms to generate its cache, prepare loaded decks or the entire DJ library, or force regeneration. Batch analysis runs on a utility worker with saved-cache reuse, progress and cancellation.
DJ waveform generation opens a modal with a coloured progress bar, percentage ring, counts and Cancel. The results stay visible until Done; any analysis issues are listed in the modal.
DJ library selection supports Command-click for separate tracks, Shift-click for ranges, and Command-A or right-click Select All Songs for all matching tracks across pages. The queue toolbar action adds the selected tracks in library order. Right-click selected tracks to generate or regenerate their saved waveforms or add the selection to the DJ queue.