gomu/popup.go

905 lines
20 KiB
Go
Raw Normal View History

2020-06-22 00:05:56 +08:00
// Copyright (C) 2020 Raziman
2020-06-19 16:22:20 +08:00
package main
import (
2020-06-26 17:09:15 +08:00
"fmt"
2020-08-11 18:44:01 +08:00
"regexp"
2020-08-18 00:26:28 +08:00
"strings"
2021-02-20 16:21:04 +08:00
"sync"
2020-06-26 17:09:15 +08:00
"time"
2021-02-02 22:52:37 +08:00
"unicode/utf8"
2020-06-26 17:09:15 +08:00
2021-01-21 01:30:16 +08:00
"github.com/gdamore/tcell/v2"
2020-06-19 16:22:20 +08:00
"github.com/rivo/tview"
2020-08-18 00:26:28 +08:00
"github.com/sahilm/fuzzy"
"github.com/ztrue/tracerr"
2021-02-23 10:29:47 +08:00
2021-02-21 15:58:42 +08:00
"github.com/issadarkthing/gomu/invidious"
2021-02-23 15:51:36 +08:00
"github.com/issadarkthing/gomu/lyric"
2021-02-26 11:18:58 +08:00
"github.com/issadarkthing/gomu/player"
2020-06-19 16:22:20 +08:00
)
2020-06-27 00:09:59 +08:00
// this is used to make the popup unique
// this mitigates the issue of closing all popups when timeout ends
2020-06-28 14:03:30 +08:00
var (
popupCounter = 0
2020-07-03 00:50:32 +08:00
)
2020-06-27 00:09:59 +08:00
// Stack Simple stack data structure
2020-07-24 14:49:58 +08:00
type Stack struct {
popups []tview.Primitive
}
// Push popup to the stack and focus
func (s *Stack) push(p tview.Primitive) {
s.popups = append(s.popups, p)
gomu.app.SetFocus(p)
}
// Show item on the top of the stack
func (s *Stack) peekTop() tview.Primitive {
if len(s.popups)-1 < 0 {
return nil
}
return s.popups[len(s.popups)-1]
}
// Remove popup from the stack and focus previous popup
func (s *Stack) pop() tview.Primitive {
if len(s.popups) == 0 {
return nil
}
last := s.popups[len(s.popups)-1]
2021-02-21 09:32:22 +08:00
s.popups[len(s.popups)-1] = nil // avoid memory leak
2020-07-24 14:49:58 +08:00
res := s.popups[:len(s.popups)-1]
s.popups = res
// focus previous popup
if len(s.popups) > 0 {
gomu.app.SetFocus(s.popups[len(s.popups)-1])
} else {
// focus the panel if no popup left
gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive))
}
return last
}
2020-07-23 15:34:56 +08:00
// Gets popup timeout from config file
2020-07-04 09:47:41 +08:00
func getPopupTimeout() time.Duration {
2021-02-18 10:23:15 +08:00
dur := gomu.anko.GetString("General.popup_timeout")
2020-07-04 09:47:41 +08:00
m, err := time.ParseDuration(dur)
if err != nil {
2020-07-23 15:15:39 +08:00
logError(err)
2020-07-20 21:48:13 +08:00
return time.Second * 5
2020-07-04 09:47:41 +08:00
}
return m
}
2020-07-23 15:34:56 +08:00
// Simple confirmation popup. Accepts callback
2020-06-19 16:22:20 +08:00
func confirmationPopup(
text string,
2020-06-19 16:42:30 +08:00
handler func(buttonIndex int, buttonLabel string),
2020-06-19 16:22:20 +08:00
) {
modal := tview.NewModal().
2020-06-19 16:42:30 +08:00
SetText(text).
2020-08-22 14:41:03 +08:00
SetBackgroundColor(gomu.colors.popup).
2020-07-22 21:01:13 +08:00
AddButtons([]string{"no", "yes"}).
2020-08-22 14:41:03 +08:00
SetButtonBackgroundColor(gomu.colors.popup).
SetButtonTextColor(gomu.colors.accent).
2020-07-24 22:27:08 +08:00
SetDoneFunc(func(indx int, label string) {
gomu.pages.RemovePage("confirmation-popup")
gomu.popups.pop()
2020-08-25 21:12:00 +08:00
handler(indx, label)
2020-07-24 22:27:08 +08:00
})
2020-06-19 16:22:20 +08:00
2021-01-21 14:37:51 +08:00
modal.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
switch e.Rune() {
case 'h':
2021-02-02 22:52:37 +08:00
return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone)
2021-01-21 14:37:51 +08:00
case 'j':
2021-02-02 22:52:37 +08:00
return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone)
2021-01-21 14:37:51 +08:00
case 'k':
2021-02-02 22:52:37 +08:00
return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone)
case 'l':
2021-02-02 22:52:37 +08:00
return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone)
2021-01-21 14:37:51 +08:00
}
return e
})
2020-07-23 15:15:39 +08:00
gomu.pages.
2020-07-20 21:48:13 +08:00
AddPage("confirmation-popup", center(modal, 40, 10), true, true)
2020-07-24 14:49:58 +08:00
gomu.popups.push(modal)
2020-06-19 16:22:20 +08:00
}
2021-03-02 14:50:25 +08:00
// Confirmation popup for delete playlist. Accepts callback
func confirmDeleteAllPopup(selPlaylist *tview.TreeNode) (err error) {
popupID := "confirm-deleteall-input-popup"
input := newInputPopup(popupID, "Are you sure to delete the folder and all files under it?", "Type DELETE to Confirm: ", "")
input.SetDoneFunc(func(key tcell.Key) {
switch key {
case tcell.KeyEnter:
confirmationText := input.GetText()
gomu.pages.RemovePage(popupID)
gomu.popups.pop()
if confirmationText == "DELETE" {
2021-03-02 15:40:23 +08:00
err = gomu.playlist.deletePlaylist(selPlaylist.GetReference().(*AudioFile))
2021-03-02 14:50:25 +08:00
if err != nil {
2021-03-02 15:40:23 +08:00
errorPopup(err)
2021-03-02 14:50:25 +08:00
}
}
case tcell.KeyEscape:
gomu.pages.RemovePage(popupID)
gomu.popups.pop()
}
gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive))
})
return tracerr.Wrap(err)
}
2020-06-19 16:22:20 +08:00
func center(p tview.Primitive, width, height int) tview.Primitive {
return tview.NewFlex().
2020-06-19 16:42:30 +08:00
AddItem(nil, 0, 1, false).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
2020-06-19 16:22:20 +08:00
AddItem(nil, 0, 1, false).
2020-06-19 16:42:30 +08:00
AddItem(p, height, 1, false).
AddItem(nil, 0, 1, false), width, 1, false).
AddItem(nil, 0, 1, false)
2020-06-19 16:22:20 +08:00
}
2020-06-26 17:09:15 +08:00
func topRight(p tview.Primitive, width, height int) tview.Primitive {
return tview.NewFlex().
AddItem(nil, 0, 23, false).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(nil, 0, 1, false).
AddItem(p, height, 1, false).
AddItem(nil, 0, 15, false), width, 1, false).
AddItem(nil, 0, 1, false)
}
2020-08-11 18:44:01 +08:00
// Width and height parameter are optional, provide 0 for both to use deault values.
// It defaults to 70 and 7 respectively.
2020-07-20 21:48:13 +08:00
func timedPopup(
title string, desc string, timeout time.Duration, width, height int,
) {
2020-07-12 12:21:49 +08:00
if width == 0 && height == 0 {
width = 70
height = 7
}
2020-06-26 17:09:15 +08:00
textView := tview.NewTextView().
2020-07-03 00:50:32 +08:00
SetText(desc).
2020-08-22 14:41:03 +08:00
SetTextColor(gomu.colors.accent)
2020-06-26 17:09:15 +08:00
2020-08-22 14:41:03 +08:00
textView.SetTextAlign(tview.AlignCenter).SetBackgroundColor(gomu.colors.popup)
2020-06-26 17:09:15 +08:00
2020-07-12 12:21:49 +08:00
box := tview.NewFrame(textView).SetBorders(1, 0, 0, 0, 0, 0)
2020-08-22 14:41:03 +08:00
box.SetTitle(title).SetBorder(true).SetBackgroundColor(gomu.colors.popup)
popupID := fmt.Sprintf("%s %d", "timeout-popup", popupCounter)
2020-06-27 00:09:59 +08:00
popupCounter++
gomu.pages.AddPage(popupID, topRight(box, width, height), true, true)
2021-02-16 13:38:21 +08:00
// gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive))
2020-07-24 14:49:58 +08:00
2021-02-16 13:38:21 +08:00
resetFocus := func() {
2020-07-24 14:49:58 +08:00
// timed popup shouldn't get focused
// this here check if another popup exists and focus that instead of panel
// if none continue focus panel
topPopup := gomu.popups.peekTop()
if topPopup == nil {
gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive))
} else {
gomu.app.SetFocus(topPopup)
}
2021-02-16 13:38:21 +08:00
}
resetFocus()
go func() {
time.Sleep(timeout)
2021-02-21 20:21:07 +08:00
gomu.app.QueueUpdateDraw(func() {
gomu.pages.RemovePage(popupID)
resetFocus()
})
2020-06-26 17:09:15 +08:00
}()
}
2020-08-25 20:45:16 +08:00
// Wrapper for timed popup
func defaultTimedPopup(title, description string) {
timedPopup(title, description, getPopupTimeout(), 0, 0)
}
2020-07-23 15:34:56 +08:00
// Shows popup for the current volume
2020-06-26 17:09:15 +08:00
func volumePopup(volume float64) {
2021-02-26 11:18:58 +08:00
currVol := player.VolToHuman(volume)
2020-07-24 17:08:34 +08:00
maxVol := 100
// max progress bar length
maxLength := 50
progressBar := progresStr(currVol, maxVol, maxLength, "█", "-")
progress := fmt.Sprintf("\n%d |%s| %d",
currVol,
progressBar,
maxVol,
2020-06-26 17:09:15 +08:00
)
2020-08-25 20:45:16 +08:00
defaultTimedPopup(" Volume ", progress)
2020-06-26 17:09:15 +08:00
}
2020-06-27 00:09:59 +08:00
2020-07-23 15:34:56 +08:00
// Shows a list of keybind. The upper list is the local keybindings to specific
// panel only. The lower list is the global keybindings
2020-07-17 15:34:50 +08:00
func helpPopup(panel Panel) {
2020-06-27 00:09:59 +08:00
2020-07-23 15:15:39 +08:00
helpText := panel.help()
2020-07-17 15:34:50 +08:00
genHelp := []string{
" ",
2020-06-27 00:09:59 +08:00
"tab change panel",
"space toggle play/pause",
2020-07-03 12:35:29 +08:00
"esc close popup",
2020-06-27 00:09:59 +08:00
"n skip",
"q quit",
"+ volume up",
"- volume down",
"f/F forward 10/60 seconds",
"b/B rewind 10/60 seconds",
2020-06-27 00:09:59 +08:00
"? toggle help",
2021-02-19 10:24:21 +08:00
"m open repl",
"T switch lyrics",
"c show colors",
2020-07-17 15:34:50 +08:00
}
2020-06-27 00:09:59 +08:00
list := tview.NewList().ShowSecondaryText(false)
2020-08-22 14:41:03 +08:00
list.SetBackgroundColor(gomu.colors.popup).SetTitle(" Help ").
2020-06-27 17:16:49 +08:00
SetBorder(true)
2020-08-22 14:41:03 +08:00
list.SetSelectedBackgroundColor(gomu.colors.popup).
SetSelectedTextColor(gomu.colors.accent)
2020-06-27 00:09:59 +08:00
2020-07-23 15:15:39 +08:00
for _, v := range append(helpText, genHelp...) {
2021-02-02 22:52:37 +08:00
list.AddItem(" "+v, "", 0, nil)
2020-06-27 00:09:59 +08:00
}
prev := func() {
currIndex := list.GetCurrentItem()
list.SetCurrentItem(currIndex - 1)
}
next := func() {
currIndex := list.GetCurrentItem()
idx := currIndex + 1
if currIndex == list.GetItemCount()-1 {
idx = 0
}
list.SetCurrentItem(idx)
}
list.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
switch e.Rune() {
case 'j':
next()
case 'k':
prev()
}
2020-07-03 00:50:32 +08:00
2020-07-01 17:48:56 +08:00
switch e.Key() {
case tcell.KeyEsc:
2020-07-23 15:15:39 +08:00
gomu.pages.RemovePage("help-page")
2020-07-24 22:27:08 +08:00
gomu.popups.pop()
case tcell.KeyEnter:
gomu.pages.RemovePage("help-page")
gomu.popups.pop()
2020-07-01 17:48:56 +08:00
}
2020-06-27 00:09:59 +08:00
return nil
})
gomu.pages.AddPage("help-page", center(list, 50, 32), true, true)
2020-07-24 14:49:58 +08:00
gomu.popups.push(list)
2020-06-27 00:09:59 +08:00
}
2020-06-27 17:16:49 +08:00
2020-07-23 15:34:56 +08:00
// Input popup. Takes video url from youtube to be downloaded
2020-07-02 16:11:25 +08:00
func downloadMusicPopup(selPlaylist *tview.TreeNode) {
2020-06-27 17:16:49 +08:00
2020-08-11 18:54:15 +08:00
re := regexp.MustCompile(`^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$`)
2020-08-11 18:44:01 +08:00
popupID := "download-input-popup"
input := newInputPopup(popupID, " Download ", "Url: ", "")
2020-06-27 17:16:49 +08:00
2020-08-18 16:09:12 +08:00
input.SetDoneFunc(func(key tcell.Key) {
2020-06-27 17:16:49 +08:00
switch key {
case tcell.KeyEnter:
2020-08-18 16:09:12 +08:00
url := input.GetText()
2020-07-21 12:22:00 +08:00
2020-08-11 18:44:01 +08:00
// check if valid youtube url was given
if re.MatchString(url) {
go func() {
if err := ytdl(url, selPlaylist); err != nil {
errorPopup(err)
2020-08-11 18:44:01 +08:00
}
}()
} else {
2020-08-25 20:45:16 +08:00
defaultTimedPopup("Invalid url", "Invalid youtube url was given")
2020-08-11 18:44:01 +08:00
}
2020-07-23 15:15:39 +08:00
gomu.pages.RemovePage("download-input-popup")
2020-07-24 22:27:08 +08:00
gomu.popups.pop()
2020-06-27 17:16:49 +08:00
case tcell.KeyEscape:
2020-07-23 15:15:39 +08:00
gomu.pages.RemovePage("download-input-popup")
2020-07-24 22:27:08 +08:00
gomu.popups.pop()
2020-06-27 17:16:49 +08:00
}
2020-07-23 15:15:39 +08:00
gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive))
2020-07-02 16:11:25 +08:00
2020-06-27 17:16:49 +08:00
})
}
2020-07-01 17:48:56 +08:00
2020-07-23 15:34:56 +08:00
// Input popup that takes the name of directory to be created
2020-07-23 15:15:39 +08:00
func createPlaylistPopup() {
2020-07-03 00:50:32 +08:00
popupID := "mkdir-input-popup"
input := newInputPopup(popupID, " New Playlist ", "Enter playlist name: ", "")
2020-07-01 17:48:56 +08:00
2020-08-18 21:54:15 +08:00
input.SetDoneFunc(func(key tcell.Key) {
2020-07-01 17:48:56 +08:00
switch key {
case tcell.KeyEnter:
2020-08-18 21:54:15 +08:00
playListName := input.GetText()
2020-07-23 15:15:39 +08:00
err := gomu.playlist.createPlaylist(playListName)
2020-07-03 00:50:32 +08:00
if err != nil {
2020-07-23 15:15:39 +08:00
logError(err)
2020-07-03 00:50:32 +08:00
}
2020-07-23 15:15:39 +08:00
gomu.pages.RemovePage("mkdir-input-popup")
2020-07-24 14:49:58 +08:00
gomu.popups.pop()
2020-07-01 17:48:56 +08:00
case tcell.KeyEsc:
2020-07-23 15:15:39 +08:00
gomu.pages.RemovePage("mkdir-input-popup")
2020-07-24 14:49:58 +08:00
gomu.popups.pop()
2020-07-01 17:48:56 +08:00
}
})
}
2020-07-24 14:51:10 +08:00
func exitConfirmation(args Args) {
2020-07-24 14:51:10 +08:00
confirmationPopup("Are you sure to exit?", func(_ int, label string) {
if label == "no" || label == "" {
return
}
err := gomu.quit(args)
2020-08-02 16:02:07 +08:00
if err != nil {
2020-07-24 14:51:10 +08:00
logError(err)
}
})
}
2020-08-18 00:26:28 +08:00
2021-02-15 13:56:31 +08:00
func searchPopup(title string, stringsToMatch []string, handler func(selected string)) {
2020-08-18 00:26:28 +08:00
list := tview.NewList().ShowSecondaryText(false)
2020-08-22 14:41:03 +08:00
list.SetSelectedBackgroundColor(gomu.colors.accent)
2020-08-18 00:26:28 +08:00
list.SetHighlightFullLine(true)
2021-03-14 18:20:48 +08:00
list.SetBackgroundColor(gomu.colors.popup)
2020-08-18 00:26:28 +08:00
for _, v := range stringsToMatch {
list.AddItem(v, v, 0, nil)
}
input := tview.NewInputField()
2020-08-22 14:41:03 +08:00
input.SetFieldBackgroundColor(gomu.colors.popup).
2020-08-18 00:26:28 +08:00
SetLabel("[red]>[-] ")
input.SetChangedFunc(func(text string) {
list.Clear()
// list all item if input is empty
if len(text) == 0 {
for _, v := range stringsToMatch {
list.AddItem(v, v, 0, nil)
}
return
}
pattern := input.GetText()
matches := fuzzy.Find(pattern, stringsToMatch)
const highlight = "[red]%c[-]"
// const highlight = "[red]%s[-]"
2020-08-18 00:26:28 +08:00
for _, match := range matches {
var text strings.Builder
2021-02-02 22:52:37 +08:00
matchrune := []rune(match.Str)
matchruneIndexes := match.MatchedIndexes
for i := 0; i < len(match.MatchedIndexes); i++ {
2021-02-19 10:25:01 +08:00
matchruneIndexes[i] =
2021-02-18 10:24:43 +08:00
utf8.RuneCountInString(match.Str[0:match.MatchedIndexes[i]])
2021-02-02 22:52:37 +08:00
}
for i := 0; i < len(matchrune); i++ {
if contains(i, matchruneIndexes) {
textwithcolor := fmt.Sprintf(highlight, matchrune[i])
for _, j := range textwithcolor {
text.WriteRune(j)
}
} else {
text.WriteRune(matchrune[i])
}
}
2020-08-18 00:26:28 +08:00
list.AddItem(text.String(), match.Str, 0, nil)
}
})
input.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
switch e.Key() {
2020-08-22 15:06:31 +08:00
case tcell.KeyCtrlN, tcell.KeyDown, tcell.KeyCtrlJ:
2020-08-18 00:26:28 +08:00
currIndx := list.GetCurrentItem()
// if last index
if currIndx == list.GetItemCount()-1 {
currIndx = 0
} else {
currIndx++
}
list.SetCurrentItem(currIndx)
2020-08-22 15:06:31 +08:00
case tcell.KeyCtrlP, tcell.KeyUp, tcell.KeyCtrlK:
2020-08-18 00:26:28 +08:00
currIndx := list.GetCurrentItem()
if currIndx == 0 {
currIndx = list.GetItemCount() - 1
} else {
currIndx--
}
list.SetCurrentItem(currIndx)
case tcell.KeyEnter:
if list.GetItemCount() > 0 {
_, selected := list.GetItemText(list.GetCurrentItem())
gomu.pages.RemovePage("search-input-popup")
gomu.popups.pop()
handler(selected)
2020-08-18 00:26:28 +08:00
}
case tcell.KeyEscape:
gomu.pages.RemovePage("search-input-popup")
gomu.popups.pop()
}
return e
})
popup := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(input, 2, 1, true).
AddItem(list, 0, 1, false)
2021-03-14 18:20:48 +08:00
popupBox := tview.NewBox().SetBorder(true).
2020-08-22 14:41:03 +08:00
SetBackgroundColor(gomu.colors.popup).
2020-08-18 00:26:28 +08:00
SetBorderPadding(1, 1, 2, 2).
2021-02-15 13:56:31 +08:00
SetTitle(" " + title + " ")
2020-08-18 00:26:28 +08:00
2021-03-14 18:20:48 +08:00
popup.Box = popupBox
2020-08-18 00:26:28 +08:00
gomu.pages.AddPage("search-input-popup", center(popup, 70, 40), true, true)
gomu.popups.push(popup)
// This is to ensure the popup is shown even when paused
gomu.app.Draw()
2020-08-18 00:26:28 +08:00
}
2020-08-18 16:09:12 +08:00
2020-08-18 21:54:15 +08:00
// Creates new popup widget with default settings
2021-02-04 12:23:32 +08:00
func newInputPopup(popupID, title, label string, text string) *tview.InputField {
2020-08-18 16:09:12 +08:00
inputField := tview.NewInputField().
SetLabel(label).
SetFieldWidth(0).
SetAcceptanceFunc(tview.InputFieldMaxLength(50)).
2020-08-22 14:41:03 +08:00
SetFieldBackgroundColor(gomu.colors.popup).
SetFieldTextColor(gomu.colors.foreground)
2020-08-18 16:09:12 +08:00
2020-08-22 14:41:03 +08:00
inputField.SetBackgroundColor(gomu.colors.popup).
2020-08-18 16:09:12 +08:00
SetTitle(title).
SetBorder(true).
SetBorderPadding(1, 0, 2, 2)
2021-02-02 22:52:37 +08:00
inputField.SetText(text)
2020-08-18 16:09:12 +08:00
gomu.pages.
2021-02-04 12:23:32 +08:00
AddPage(popupID, center(inputField, 60, 5), true, true)
2020-08-18 16:09:12 +08:00
gomu.popups.push(inputField)
return inputField
}
func renamePopup(node *AudioFile) {
2021-02-04 12:23:32 +08:00
popupID := "rename-input-popup"
input := newInputPopup(popupID, " Rename ", "New name: ", node.name)
2020-08-18 16:09:12 +08:00
input.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
switch e.Key() {
case tcell.KeyEnter:
newName := input.GetText()
if newName == "" {
return e
}
err := gomu.playlist.rename(newName)
if err != nil {
2020-08-25 20:45:16 +08:00
defaultTimedPopup(" Error ", err.Error())
2020-08-18 16:09:12 +08:00
logError(err)
}
2021-02-04 12:23:32 +08:00
gomu.pages.RemovePage(popupID)
2020-08-18 16:09:12 +08:00
gomu.popups.pop()
gomu.playlist.refresh()
2021-02-18 10:24:43 +08:00
2021-02-02 22:52:37 +08:00
gomu.queue.updateQueueNames()
gomu.setFocusPanel(gomu.playlist)
gomu.prevPanel = gomu.playlist
2021-02-18 10:24:43 +08:00
2021-02-03 14:53:46 +08:00
root := gomu.playlist.GetRoot()
root.Walk(func(node, _ *tview.TreeNode) bool {
if strings.Contains(node.GetText(), newName) {
gomu.playlist.setHighlight(node)
}
return true
})
2020-08-18 16:09:12 +08:00
case tcell.KeyEsc:
2021-02-04 12:23:32 +08:00
gomu.pages.RemovePage(popupID)
2020-08-18 16:09:12 +08:00
gomu.popups.pop()
}
return e
})
}
2021-02-15 12:14:03 +08:00
// Show error popup with error is logged. Prefer this when its related with user
// interaction. Otherwise, use logError.
func errorPopup(message error) {
defaultTimedPopup(" Error ", tracerr.Unwrap(message).Error())
logError(message)
}
2021-02-15 12:14:03 +08:00
// Show debug popup and log debug info. Mainly used when scripting.
func debugPopup(message interface{}) {
m := fmt.Sprintf("%v", message)
defaultTimedPopup(" Debug ", m)
logDebug(m)
2021-02-15 12:14:03 +08:00
}
// Show info popup and does not log anything. Prefer this when making simple
// popup.
func infoPopup(message string) {
defaultTimedPopup(" Info ", message)
}
2021-03-21 16:05:23 +08:00
func inputPopup(prompt, placeholder string, handler func(string)) {
popupID := "general-input-popup"
2021-02-13 22:49:26 +08:00
input := newInputPopup(popupID, "", prompt+": ", "")
2021-03-21 16:05:23 +08:00
input.SetText(placeholder)
input.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
switch e.Key() {
case tcell.KeyEnter:
2021-02-15 13:56:31 +08:00
output := input.GetText()
if output == "" {
return e
}
2021-02-15 13:56:31 +08:00
handler(output)
gomu.pages.RemovePage(popupID)
gomu.popups.pop()
case tcell.KeyEsc:
gomu.pages.RemovePage(popupID)
gomu.popups.pop()
}
return e
})
}
2021-02-16 13:37:59 +08:00
func replPopup() {
2021-03-25 12:01:12 +08:00
popupID := "repl-input-popup"
2021-02-16 21:18:02 +08:00
prompt := "> "
2021-02-16 13:37:59 +08:00
textview := tview.NewTextView()
input := tview.NewInputField().
SetFieldBackgroundColor(gomu.colors.popup).
2021-02-16 21:18:02 +08:00
SetLabel(prompt)
2021-02-16 13:37:59 +08:00
// to store input history
history := []string{}
upCount := 0
2021-03-21 16:05:23 +08:00
gomu.anko.DefineGlobal("println", func(x ...interface{}) {
2021-02-16 13:37:59 +08:00
fmt.Fprintln(textview, x...)
})
2021-03-21 16:05:23 +08:00
gomu.anko.DefineGlobal("print", func(x ...interface{}) {
2021-02-16 13:37:59 +08:00
fmt.Fprint(textview, x...)
})
2021-03-21 16:05:23 +08:00
gomu.anko.DefineGlobal("printf", func(format string, x ...interface{}) {
2021-02-16 13:37:59 +08:00
fmt.Fprintf(textview, format, x...)
})
input.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyUp:
2021-02-20 15:37:22 +08:00
if upCount < len(history) {
input.SetText(history[upCount])
2021-02-16 13:37:59 +08:00
upCount++
2021-02-16 21:18:02 +08:00
}
case tcell.KeyDown:
if upCount > 0 {
2021-02-20 15:37:22 +08:00
if upCount == len(history) {
upCount -= 2
} else {
2021-03-25 12:01:12 +08:00
upCount--
2021-02-20 15:37:22 +08:00
}
2021-02-16 13:37:59 +08:00
input.SetText(history[upCount])
2021-02-16 21:18:02 +08:00
} else if upCount == 0 {
input.SetText("")
2021-02-16 13:37:59 +08:00
}
case tcell.KeyCtrlL:
textview.SetText("")
case tcell.KeyEsc:
2021-03-25 12:01:12 +08:00
gomu.pages.RemovePage(popupID)
2021-02-16 13:37:59 +08:00
gomu.popups.pop()
return nil
case tcell.KeyEnter:
text := input.GetText()
2021-02-16 21:18:02 +08:00
// most recent is placed the most front
history = append([]string{text}, history...)
upCount = 0
2021-02-16 13:37:59 +08:00
input.SetText("")
defer func() {
if err := recover(); err != nil {
fmt.Fprintf(textview, "%s%s\n%v\n\n", prompt, text, err)
}
}()
fmt.Fprintf(textview, "%s%s\n", prompt, text)
2021-02-16 13:37:59 +08:00
res, err := gomu.anko.Execute(text)
if err != nil {
fmt.Fprintf(textview, "%v\n\n", err)
return nil
2021-02-16 13:37:59 +08:00
}
2021-03-25 12:01:12 +08:00
fmt.Fprintf(textview, "%v\n\n", res)
2021-02-19 12:09:42 +08:00
2021-02-16 13:37:59 +08:00
}
return event
})
flex := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(input, 3, 1, true).
AddItem(textview, 0, 1, false)
2021-03-14 18:20:48 +08:00
flexBox := tview.NewBox().
2021-02-16 13:37:59 +08:00
SetBackgroundColor(gomu.colors.popup).
SetBorder(true).
SetBorderPadding(1, 1, 2, 2).
SetTitle(" REPL ")
2021-03-14 18:20:48 +08:00
flex.Box = flexBox
2021-03-25 12:04:58 +08:00
gomu.pages.AddPage(popupID, center(flex, 90, 30), true, true)
2021-02-16 13:37:59 +08:00
gomu.popups.push(flex)
}
2021-02-19 14:45:20 +08:00
2021-02-20 16:21:04 +08:00
func ytSearchPopup() {
2021-03-25 12:01:12 +08:00
popupID := "youtube-search-input-popup"
2021-02-20 16:21:04 +08:00
2021-03-25 12:01:12 +08:00
input := newInputPopup(popupID, " Youtube Search ", "search: ", "")
2021-02-20 16:21:04 +08:00
2021-02-21 15:58:42 +08:00
instance := gomu.anko.GetString("General.invidious_instance")
inv := invidious.Invidious{Domain: instance}
2021-02-20 16:21:04 +08:00
var mutex sync.Mutex
prefixMap := make(map[string][]string)
// quick hack to change the autocomplete text color
tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack
input.SetAutocompleteFunc(func(currentText string) []string {
// Ignore empty text.
prefix := strings.TrimSpace(strings.ToLower(currentText))
if prefix == "" {
return nil
}
mutex.Lock()
defer mutex.Unlock()
entries, ok := prefixMap[prefix]
if ok {
return entries
}
go func() {
2021-02-21 15:58:42 +08:00
suggestions, err := inv.GetSuggestions(currentText)
2021-02-20 16:21:04 +08:00
if err != nil {
logError(err)
return
}
mutex.Lock()
prefixMap[prefix] = suggestions
mutex.Unlock()
input.Autocomplete()
gomu.app.Draw()
}()
return nil
})
input.SetDoneFunc(func(key tcell.Key) {
switch key {
case tcell.KeyEnter:
search := input.GetText()
defaultTimedPopup(" Youtube Search ", "Searching for "+search)
2021-03-25 12:01:12 +08:00
gomu.pages.RemovePage(popupID)
2021-02-20 16:21:04 +08:00
gomu.popups.pop()
go func() {
2021-02-21 15:58:42 +08:00
results, err := inv.GetSearchQuery(search)
2021-02-20 16:21:04 +08:00
if err != nil {
logError(err)
defaultTimedPopup(" Error ", err.Error())
return
}
titles := []string{}
urls := make(map[string]string)
for _, result := range results {
duration, err := time.ParseDuration(fmt.Sprintf("%ds", result.LengthSeconds))
if err != nil {
logError(err)
return
}
durationText := fmt.Sprintf("[ %s ] ", fmtDuration(duration))
title := durationText + result.Title
urls[title] = `https://www.youtube.com/watch?v=` + result.VideoId
titles = append(titles, title)
}
searchPopup("Youtube Videos", titles, func(title string) {
audioFile := gomu.playlist.getCurrentFile()
var dir *tview.TreeNode
if audioFile.isAudioFile {
dir = audioFile.parent
} else {
dir = audioFile.node
}
go func() {
url := urls[title]
if err := ytdl(url, dir); err != nil {
errorPopup(err)
2021-02-20 16:21:04 +08:00
}
gomu.playlist.refresh()
}()
gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive))
})
gomu.app.Draw()
}()
case tcell.KeyEscape:
2021-03-25 12:01:12 +08:00
gomu.pages.RemovePage(popupID)
2021-02-20 16:21:04 +08:00
gomu.popups.pop()
gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive))
}
})
}
func lyricPopup(lang string, audioFile *AudioFile, wg *sync.WaitGroup) error {
2021-02-23 10:29:47 +08:00
2021-03-09 01:48:01 +08:00
var titles []string
results, err := lyric.GetLyricOptions(lang, audioFile.name)
2021-02-23 10:29:47 +08:00
if err != nil {
return tracerr.Wrap(err)
}
2021-03-09 01:48:01 +08:00
for _, v := range results {
titles = append(titles, v.TitleForPopup)
2021-02-23 10:29:47 +08:00
}
searchPopup(" Lyrics ", titles, func(selected string) {
if selected == "" {
return
}
go func() {
defer wg.Done()
2021-03-09 01:48:01 +08:00
var selectedIndex int
for i, v := range results {
if v.TitleForPopup == selected {
selectedIndex = i
break
}
2021-02-27 23:11:17 +08:00
}
lyricContent, err := lyric.GetLyric(results[selectedIndex].LangExt, results[selectedIndex])
2021-02-27 23:11:17 +08:00
if err != nil {
errorPopup(err)
gomu.app.Draw()
return
2021-02-27 23:11:17 +08:00
}
2021-03-25 16:57:44 +08:00
var lyric lyric.Lyric
err = lyric.NewFromLRC(lyricContent)
if err != nil {
errorPopup(err)
gomu.app.Draw()
return
}
lyric.LangExt = lang
err = embedLyric(audioFile.path, &lyric, false)
2021-02-27 23:11:17 +08:00
if err != nil {
errorPopup(err)
gomu.app.Draw()
return
2021-02-23 10:29:47 +08:00
}
2021-03-25 12:01:12 +08:00
infoPopup(lang + " lyric added successfully")
gomu.app.Draw()
2021-02-23 10:29:47 +08:00
}()
})
return nil
}