add 'Skip duplicates' option
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/dweymouth/supersonic/backend"
|
"github.com/dweymouth/supersonic/backend"
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
@@ -338,16 +339,36 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
|
|||||||
go m.App.ServerManager.Server.CreatePlaylist(query, trackIDs)
|
go m.App.ServerManager.Server.CreatePlaylist(query, trackIDs)
|
||||||
} else {
|
} else {
|
||||||
m.App.Config.Application.DefaultPlaylistID = id
|
m.App.Config.Application.DefaultPlaylistID = id
|
||||||
|
if sp.SkipDuplicates {
|
||||||
|
var filterTrackIDs []string
|
||||||
|
go func() {
|
||||||
|
if selectedPlaylist, err := m.App.ServerManager.Server.GetPlaylist(id); err != nil {
|
||||||
|
log.Printf("error getting playlist: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
var trackIDsInPaylist []string
|
||||||
|
|
||||||
|
for _, track := range selectedPlaylist.Tracks{
|
||||||
|
trackIDsInPaylist = append(trackIDsInPaylist, track.ID)
|
||||||
|
}
|
||||||
|
filterTrackIDs = sharedutil.FilterSlice(trackIDs, func(trackID string) bool {
|
||||||
|
return !slices.Contains(trackIDsInPaylist, trackID)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
m.App.ServerManager.Server.AddPlaylistTracks(id, filterTrackIDs)
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
go m.App.ServerManager.Server.AddPlaylistTracks(id, trackIDs)
|
go m.App.ServerManager.Server.AddPlaylistTracks(id, trackIDs)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
m.ClosePopUpOnEscape(pop)
|
m.ClosePopUpOnEscape(pop)
|
||||||
m.haveModal = true
|
m.haveModal = true
|
||||||
min := sp.MinSize()
|
min := sp.MinSize()
|
||||||
height := fyne.Max(min.Height, fyne.Min(min.Height*1.5, m.MainWindow.Canvas().Size().Height*0.7))
|
height := fyne.Max(min.Height, fyne.Min(min.Height*1.5, m.MainWindow.Canvas().Size().Height*0.7))
|
||||||
pop.Resize(fyne.NewSize(min.Width, height))
|
|
||||||
sp.SearchDialog.Show()
|
sp.SearchDialog.Show()
|
||||||
|
pop.Resize(fyne.NewSize(min.Width, height))
|
||||||
pop.Show()
|
pop.Show()
|
||||||
m.MainWindow.Canvas().Focus(sp.GetSearchEntry())
|
m.MainWindow.Canvas().Focus(sp.GetSearchEntry())
|
||||||
}
|
}
|
||||||
@@ -627,6 +648,7 @@ func (c *Controller) ShowQuickSearch() {
|
|||||||
c.haveModal = true
|
c.haveModal = true
|
||||||
min := qs.MinSize()
|
min := qs.MinSize()
|
||||||
height := fyne.Max(min.Height, fyne.Min(min.Height*1.5, c.MainWindow.Canvas().Size().Height*0.7))
|
height := fyne.Max(min.Height, fyne.Min(min.Height*1.5, c.MainWindow.Canvas().Size().Height*0.7))
|
||||||
|
qs.SearchDialog.Show()
|
||||||
pop.Resize(fyne.NewSize(min.Width, height))
|
pop.Resize(fyne.NewSize(min.Width, height))
|
||||||
pop.Show()
|
pop.Show()
|
||||||
c.MainWindow.Canvas().Focus(qs.GetSearchEntry())
|
c.MainWindow.Canvas().Focus(qs.GetSearchEntry())
|
||||||
|
|||||||
+32
-14
@@ -33,22 +33,24 @@ type SearchDialog struct {
|
|||||||
list *widget.List
|
list *widget.List
|
||||||
selectedIndex int
|
selectedIndex int
|
||||||
|
|
||||||
|
placeholderTitle string
|
||||||
content *fyne.Container
|
content *fyne.Container
|
||||||
|
|
||||||
OnDismiss func()
|
OnDismiss func()
|
||||||
OnNavigateTo func(mediaprovider.ContentType, string, string)
|
OnNavigateTo func(mediaprovider.ContentType, string, string)
|
||||||
OnSearched func(string) []*mediaprovider.SearchResult
|
OnSearched func(string) []*mediaprovider.SearchResult
|
||||||
OnUpdateSearchResults func(*searchResult, *mediaprovider.SearchResult)
|
OnUpdateSearchResults func(*searchResult, *mediaprovider.SearchResult)
|
||||||
OnInit func() []*mediaprovider.SearchResult
|
OnInit func() ([]*mediaprovider.SearchResult, *widget.Check)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSearchDialog(im util.ImageFetcher, placeholderTitle string, onSearched func(string) []*mediaprovider.SearchResult, onUpdateSearchResult func(*searchResult, *mediaprovider.SearchResult), onInit func() []*mediaprovider.SearchResult) *SearchDialog {
|
func NewSearchDialog(im util.ImageFetcher, placeholderTitle string, onSearched func(string) []*mediaprovider.SearchResult, onUpdateSearchResult func(*searchResult, *mediaprovider.SearchResult), onInit func() ([]*mediaprovider.SearchResult, *widget.Check)) *SearchDialog {
|
||||||
sd := &SearchDialog{
|
sd := &SearchDialog{
|
||||||
imgSource: im,
|
imgSource: im,
|
||||||
loadingDots: widgets.NewLoadingDots(),
|
loadingDots: widgets.NewLoadingDots(),
|
||||||
OnSearched: onSearched,
|
OnSearched: onSearched,
|
||||||
OnUpdateSearchResults: onUpdateSearchResult,
|
OnUpdateSearchResults: onUpdateSearchResult,
|
||||||
OnInit: onInit,
|
OnInit: onInit,
|
||||||
|
placeholderTitle: placeholderTitle,
|
||||||
}
|
}
|
||||||
sd.ExtendBaseWidget(sd)
|
sd.ExtendBaseWidget(sd)
|
||||||
|
|
||||||
@@ -80,17 +82,6 @@ func NewSearchDialog(im util.ImageFetcher, placeholderTitle string, onSearched f
|
|||||||
sd.update(sr, result)
|
sd.update(sr, result)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
dismissBtn := widget.NewButton("Close", sd.onDismiss)
|
|
||||||
title := widget.NewRichText(&widget.TextSegment{Text: placeholderTitle, Style: util.BoldRichTextStyle})
|
|
||||||
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
|
|
||||||
sd.content = container.NewStack(
|
|
||||||
container.NewBorder(
|
|
||||||
container.NewVBox(title, se),
|
|
||||||
container.NewVBox(widget.NewSeparator(), container.NewHBox(layout.NewSpacer(), dismissBtn)),
|
|
||||||
nil, nil, sd.list),
|
|
||||||
container.NewCenter(sd.loadingDots),
|
|
||||||
)
|
|
||||||
return sd
|
return sd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,18 +140,45 @@ func (sd *SearchDialog) setResults(results []*mediaprovider.SearchResult) {
|
|||||||
sd.list.Select(0)
|
sd.list.Select(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sd *SearchDialog) SetContent(checkBox *widget.Check) {
|
||||||
|
dismissBtn := widget.NewButton("Close", sd.onDismiss)
|
||||||
|
title := widget.NewRichText(&widget.TextSegment{Text:sd.placeholderTitle, Style: util.BoldRichTextStyle})
|
||||||
|
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
|
||||||
|
se := sd.SearchEntry.(fyne.CanvasObject)
|
||||||
|
if checkBox != nil {
|
||||||
|
sd.content = container.NewStack(
|
||||||
|
container.NewBorder(
|
||||||
|
container.NewVBox(title, se),
|
||||||
|
container.NewVBox(widget.NewSeparator(), container.NewHBox(checkBox, layout.NewSpacer(), dismissBtn)),
|
||||||
|
nil, nil, sd.list),
|
||||||
|
container.NewCenter(sd.loadingDots),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
sd.content = container.NewStack(
|
||||||
|
container.NewBorder(
|
||||||
|
container.NewVBox(title, se),
|
||||||
|
container.NewVBox(widget.NewSeparator(), container.NewHBox(layout.NewSpacer(), dismissBtn)),
|
||||||
|
nil, nil, sd.list),
|
||||||
|
container.NewCenter(sd.loadingDots),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func (sd *SearchDialog) onInit() {
|
func (sd *SearchDialog) onInit() {
|
||||||
if sd.OnInit == nil {
|
if sd.OnInit == nil {
|
||||||
|
sd.SetContent(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sd.loadingDots.Start()
|
sd.loadingDots.Start()
|
||||||
var results []*mediaprovider.SearchResult
|
var results []*mediaprovider.SearchResult
|
||||||
res := sd.OnInit()
|
res, checkBox := sd.OnInit()
|
||||||
if len(res) == 0 {
|
if len(res) == 0 {
|
||||||
log.Println("No results")
|
log.Println("No results")
|
||||||
} else {
|
} else {
|
||||||
results = res
|
results = res
|
||||||
}
|
}
|
||||||
|
sd.SetContent(checkBox)
|
||||||
sd.loadingDots.Stop()
|
sd.loadingDots.Stop()
|
||||||
sd.setResults(results)
|
sd.setResults(results)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type SelectPlaylist struct {
|
|||||||
mp mediaprovider.MediaProvider
|
mp mediaprovider.MediaProvider
|
||||||
loggedInUser string
|
loggedInUser string
|
||||||
allPlaylists []*mediaprovider.Playlist
|
allPlaylists []*mediaprovider.Playlist
|
||||||
|
SkipDuplicates bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSelectPlaylistDialog(mp mediaprovider.MediaProvider, im util.ImageFetcher, loggedInUser string) *SelectPlaylist {
|
func NewSelectPlaylistDialog(mp mediaprovider.MediaProvider, im util.ImageFetcher, loggedInUser string) *SelectPlaylist {
|
||||||
@@ -27,6 +28,7 @@ func NewSelectPlaylistDialog(mp mediaprovider.MediaProvider, im util.ImageFetche
|
|||||||
sp := &SelectPlaylist{
|
sp := &SelectPlaylist{
|
||||||
mp: mp,
|
mp: mp,
|
||||||
loggedInUser: loggedInUser,
|
loggedInUser: loggedInUser,
|
||||||
|
SkipDuplicates: false,
|
||||||
}
|
}
|
||||||
sd := NewSearchDialog(
|
sd := NewSearchDialog(
|
||||||
im,
|
im,
|
||||||
@@ -39,13 +41,13 @@ func NewSelectPlaylistDialog(mp mediaprovider.MediaProvider, im util.ImageFetche
|
|||||||
return sp
|
return sp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *SelectPlaylist) onInit() []*mediaprovider.SearchResult {
|
func (sp *SelectPlaylist) onInit() ([]*mediaprovider.SearchResult, *widget.Check) {
|
||||||
var results []*mediaprovider.SearchResult
|
var results []*mediaprovider.SearchResult
|
||||||
playlists, err := sp.mp.GetPlaylists()
|
playlists, err := sp.mp.GetPlaylists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: surface this error to user
|
// TODO: surface this error to user
|
||||||
log.Printf("error getting playlists: %s", err.Error())
|
log.Printf("error getting playlists: %s", err.Error())
|
||||||
return results
|
return results, nil
|
||||||
}
|
}
|
||||||
sp.allPlaylists = sharedutil.FilterSlice(playlists, func(playlist *mediaprovider.Playlist) bool {
|
sp.allPlaylists = sharedutil.FilterSlice(playlists, func(playlist *mediaprovider.Playlist) bool {
|
||||||
return playlist.Owner == sp.loggedInUser
|
return playlist.Owner == sp.loggedInUser
|
||||||
@@ -60,7 +62,10 @@ func (sp *SelectPlaylist) onInit() []*mediaprovider.SearchResult {
|
|||||||
ArtistName: playlist.Name,
|
ArtistName: playlist.Name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return results
|
skipDuplicatesCheck := widget.NewCheck("Skip duplicates", func(checked bool) {
|
||||||
|
sp.SkipDuplicates = checked
|
||||||
|
})
|
||||||
|
return results, skipDuplicatesCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *SelectPlaylist) onSearched(query string) []*mediaprovider.SearchResult {
|
func (sp *SelectPlaylist) onSearched(query string) []*mediaprovider.SearchResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user