From 597fd7fae3b5bfb1d015b814e7d1e36b2e7f461f Mon Sep 17 00:00:00 2001 From: Kujtim Hoxha Date: Sat, 7 Jun 2025 17:15:31 +0200 Subject: [PATCH] chore: cleanup image component --- cspell.json | 2 +- internal/tui/components/dialog/filepicker.go | 468 ------------------ .../dialogs/filepicker/filepicker.go | 65 ++- internal/tui/components/image/image.go | 15 +- internal/tui/components/image/load.go | 32 +- 5 files changed, 58 insertions(+), 524 deletions(-) delete mode 100644 internal/tui/components/dialog/filepicker.go diff --git a/cspell.json b/cspell.json index 7a440d8fbdf07a8d8274c707710d1d930dabe787..f59940e21add71cde463dbf18e50d40ff0c76594 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"words":["opencode","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps","Sourcegraph"],"version":"0.2","language":"en","flagWords":[]} \ No newline at end of file +{"flagWords":[],"language":"en","words":["opencode","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps","Sourcegraph","filepicker","imageorient","rasterx","oksvg","termenv","trashhalo","lucasb","nfnt","srwiley","Lanczos"],"version":"0.2"} \ No newline at end of file diff --git a/internal/tui/components/dialog/filepicker.go b/internal/tui/components/dialog/filepicker.go deleted file mode 100644 index 85c946b79dc5a55b1031a17138c3e4bcf4136131..0000000000000000000000000000000000000000 --- a/internal/tui/components/dialog/filepicker.go +++ /dev/null @@ -1,468 +0,0 @@ -package dialog - -// import ( -// "fmt" -// "net/http" -// "os" -// "path/filepath" -// "sort" -// "strings" -// "time" -// -// "github.com/charmbracelet/bubbles/v2/key" -// "github.com/charmbracelet/bubbles/v2/textinput" -// "github.com/charmbracelet/bubbles/v2/viewport" -// tea "github.com/charmbracelet/bubbletea/v2" -// "github.com/charmbracelet/lipgloss/v2" -// "github.com/opencode-ai/opencode/internal/app" -// "github.com/opencode-ai/opencode/internal/logging" -// "github.com/opencode-ai/opencode/internal/message" -// "github.com/opencode-ai/opencode/internal/tui/image" -// "github.com/opencode-ai/opencode/internal/tui/styles" -// "github.com/opencode-ai/opencode/internal/tui/util" -// ) -// -// const ( -// maxAttachmentSize = int64(5 * 1024 * 1024) // 5MB -// downArrow = "down" -// upArrow = "up" -// ) -// -// type FilePrickerKeyMap struct { -// Enter key.Binding -// Down key.Binding -// Up key.Binding -// Forward key.Binding -// Backward key.Binding -// OpenFilePicker key.Binding -// Esc key.Binding -// InsertCWD key.Binding -// } -// -// var filePickerKeyMap = FilePrickerKeyMap{ -// Enter: key.NewBinding( -// key.WithKeys("enter"), -// key.WithHelp("enter", "select file/enter directory"), -// ), -// Down: key.NewBinding( -// key.WithKeys("j", downArrow), -// key.WithHelp("↓/j", "down"), -// ), -// Up: key.NewBinding( -// key.WithKeys("k", upArrow), -// key.WithHelp("↑/k", "up"), -// ), -// Forward: key.NewBinding( -// key.WithKeys("l"), -// key.WithHelp("l", "enter directory"), -// ), -// Backward: key.NewBinding( -// key.WithKeys("h", "backspace"), -// key.WithHelp("h/backspace", "go back"), -// ), -// OpenFilePicker: key.NewBinding( -// key.WithKeys("ctrl+f"), -// key.WithHelp("ctrl+f", "open file picker"), -// ), -// Esc: key.NewBinding( -// key.WithKeys("esc"), -// key.WithHelp("esc", "close/exit"), -// ), -// InsertCWD: key.NewBinding( -// key.WithKeys("i"), -// key.WithHelp("i", "manual path input"), -// ), -// } -// -// type filepickerCmp struct { -// basePath string -// width int -// height int -// cursor int -// err error -// cursorChain stack -// viewport viewport.Model -// dirs []os.DirEntry -// cwdDetails *DirNode -// selectedFile string -// cwd textinput.Model -// ShowFilePicker bool -// app *app.App -// } -// -// type DirNode struct { -// parent *DirNode -// child *DirNode -// directory string -// } -// type stack []int -// -// func (s stack) Push(v int) stack { -// return append(s, v) -// } -// -// func (s stack) Pop() (stack, int) { -// l := len(s) -// return s[:l-1], s[l-1] -// } -// -// type AttachmentAddedMsg struct { -// Attachment message.Attachment -// } -// -// func (f *filepickerCmp) Init() tea.Cmd { -// return nil -// } -// -// func (f *filepickerCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { -// var cmd tea.Cmd -// switch msg := msg.(type) { -// case tea.WindowSizeMsg: -// f.width = 60 -// f.height = 20 -// f.viewport.SetWidth(80) -// f.viewport.SetHeight(22) -// f.cursor = 0 -// f.getCurrentFileBelowCursor() -// case tea.KeyPressMsg: -// if f.cwd.Focused() { -// f.cwd, cmd = f.cwd.Update(msg) -// } -// switch { -// case key.Matches(msg, filePickerKeyMap.InsertCWD): -// f.cwd.Focus() -// return f, cmd -// case key.Matches(msg, filePickerKeyMap.Esc): -// if f.cwd.Focused() { -// f.cwd.Blur() -// } -// case key.Matches(msg, filePickerKeyMap.Down): -// if !f.cwd.Focused() || msg.String() == downArrow { -// if f.cursor < len(f.dirs)-1 { -// f.cursor++ -// f.getCurrentFileBelowCursor() -// } -// } -// case key.Matches(msg, filePickerKeyMap.Up): -// if !f.cwd.Focused() || msg.String() == upArrow { -// if f.cursor > 0 { -// f.cursor-- -// f.getCurrentFileBelowCursor() -// } -// } -// case key.Matches(msg, filePickerKeyMap.Enter): -// var path string -// var isPathDir bool -// if f.cwd.Focused() { -// path = f.cwd.Value() -// fileInfo, err := os.Stat(path) -// if err != nil { -// logging.ErrorPersist("Invalid path") -// return f, cmd -// } -// isPathDir = fileInfo.IsDir() -// } else { -// path = filepath.Join(f.cwdDetails.directory, "/", f.dirs[f.cursor].Name()) -// isPathDir = f.dirs[f.cursor].IsDir() -// } -// if isPathDir { -// newWorkingDir := DirNode{parent: f.cwdDetails, directory: path} -// f.cwdDetails.child = &newWorkingDir -// f.cwdDetails = f.cwdDetails.child -// f.cursorChain = f.cursorChain.Push(f.cursor) -// f.dirs = readDir(f.cwdDetails.directory, false) -// f.cursor = 0 -// f.cwd.SetValue(f.cwdDetails.directory) -// f.getCurrentFileBelowCursor() -// } else { -// f.selectedFile = path -// return f.addAttachmentToMessage() -// } -// case key.Matches(msg, filePickerKeyMap.Esc): -// if !f.cwd.Focused() { -// f.cursorChain = make(stack, 0) -// f.cursor = 0 -// } else { -// f.cwd.Blur() -// } -// case key.Matches(msg, filePickerKeyMap.Forward): -// if !f.cwd.Focused() { -// if f.dirs[f.cursor].IsDir() { -// path := filepath.Join(f.cwdDetails.directory, "/", f.dirs[f.cursor].Name()) -// newWorkingDir := DirNode{parent: f.cwdDetails, directory: path} -// f.cwdDetails.child = &newWorkingDir -// f.cwdDetails = f.cwdDetails.child -// f.cursorChain = f.cursorChain.Push(f.cursor) -// f.dirs = readDir(f.cwdDetails.directory, false) -// f.cursor = 0 -// f.cwd.SetValue(f.cwdDetails.directory) -// f.getCurrentFileBelowCursor() -// } -// } -// case key.Matches(msg, filePickerKeyMap.Backward): -// if !f.cwd.Focused() { -// if len(f.cursorChain) != 0 && f.cwdDetails.parent != nil { -// f.cursorChain, f.cursor = f.cursorChain.Pop() -// f.cwdDetails = f.cwdDetails.parent -// f.cwdDetails.child = nil -// f.dirs = readDir(f.cwdDetails.directory, false) -// f.cwd.SetValue(f.cwdDetails.directory) -// f.getCurrentFileBelowCursor() -// } -// } -// case key.Matches(msg, filePickerKeyMap.OpenFilePicker): -// f.dirs = readDir(f.cwdDetails.directory, false) -// f.cursor = 0 -// f.getCurrentFileBelowCursor() -// } -// } -// return f, cmd -// } -// -// func (f *filepickerCmp) addAttachmentToMessage() (tea.Model, tea.Cmd) { -// // modeInfo := GetSelectedModel(config.Get()) -// // if !modeInfo.SupportsAttachments { -// // logging.ErrorPersist(fmt.Sprintf("Model %s doesn't support attachments", modeInfo.Name)) -// // return f, nil -// // } -// -// selectedFilePath := f.selectedFile -// if !isExtSupported(selectedFilePath) { -// logging.ErrorPersist("Unsupported file") -// return f, nil -// } -// -// isFileLarge, err := image.ValidateFileSize(selectedFilePath, maxAttachmentSize) -// if err != nil { -// logging.ErrorPersist("unable to read the image") -// return f, nil -// } -// if isFileLarge { -// logging.ErrorPersist("file too large, max 5MB") -// return f, nil -// } -// -// content, err := os.ReadFile(selectedFilePath) -// if err != nil { -// logging.ErrorPersist("Unable read selected file") -// return f, nil -// } -// -// mimeBufferSize := min(512, len(content)) -// mimeType := http.DetectContentType(content[:mimeBufferSize]) -// fileName := filepath.Base(selectedFilePath) -// attachment := message.Attachment{FilePath: selectedFilePath, FileName: fileName, MimeType: mimeType, Content: content} -// f.selectedFile = "" -// return f, util.CmdHandler(AttachmentAddedMsg{attachment}) -// } -// -// func (f *filepickerCmp) View() tea.View { -// t := styles.CurrentTheme() -// baseStyle := t.S().Base -// const maxVisibleDirs = 20 -// const maxWidth = 80 -// -// adjustedWidth := maxWidth -// for _, file := range f.dirs { -// if len(file.Name()) > adjustedWidth-4 { // Account for padding -// adjustedWidth = len(file.Name()) + 4 -// } -// } -// adjustedWidth = max(30, min(adjustedWidth, f.width-15)) + 1 -// -// files := make([]string, 0, maxVisibleDirs) -// startIdx := 0 -// -// if len(f.dirs) > maxVisibleDirs { -// halfVisible := maxVisibleDirs / 2 -// if f.cursor >= halfVisible && f.cursor < len(f.dirs)-halfVisible { -// startIdx = f.cursor - halfVisible -// } else if f.cursor >= len(f.dirs)-halfVisible { -// startIdx = len(f.dirs) - maxVisibleDirs -// } -// } -// -// endIdx := min(startIdx+maxVisibleDirs, len(f.dirs)) -// -// for i := startIdx; i < endIdx; i++ { -// file := f.dirs[i] -// itemStyle := t.S().Text.Width(adjustedWidth) -// -// if i == f.cursor { -// itemStyle = itemStyle. -// Background(t.Primary). -// Bold(true) -// } -// filename := file.Name() -// -// if len(filename) > adjustedWidth-4 { -// filename = filename[:adjustedWidth-7] + "..." -// } -// if file.IsDir() { -// filename = filename + "/" -// } -// // No need to reassign filename if it's not changing -// -// files = append(files, itemStyle.Padding(0, 1).Render(filename)) -// } -// -// // Pad to always show exactly 21 lines -// for len(files) < maxVisibleDirs { -// files = append(files, baseStyle.Width(adjustedWidth).Render("")) -// } -// -// currentPath := baseStyle. -// Height(1). -// Width(adjustedWidth). -// Render(f.cwd.View()) -// -// viewportstyle := baseStyle. -// Width(f.viewport.Width()). -// Border(lipgloss.RoundedBorder()). -// BorderForeground(t.BorderFocus). -// Padding(2). -// Render(f.viewport.View()) -// var insertExitText string -// if f.IsCWDFocused() { -// insertExitText = "Press esc to exit typing path" -// } else { -// insertExitText = "Press i to start typing path" -// } -// -// content := lipgloss.JoinVertical( -// lipgloss.Left, -// currentPath, -// baseStyle.Width(adjustedWidth).Render(""), -// baseStyle.Width(adjustedWidth).Render(lipgloss.JoinVertical(lipgloss.Left, files...)), -// baseStyle.Width(adjustedWidth).Render(""), -// t.S().Muted.Width(adjustedWidth).Render(insertExitText), -// ) -// -// f.cwd.SetValue(f.cwd.Value()) -// contentStyle := baseStyle.Padding(1, 2). -// Border(lipgloss.RoundedBorder()). -// BorderForeground(t.BorderFocus). -// Width(lipgloss.Width(content) + 4) -// -// return tea.NewView( -// lipgloss.JoinHorizontal(lipgloss.Center, contentStyle.Render(content), viewportstyle), -// ) -// } -// -// type FilepickerCmp interface { -// util.Model -// ToggleFilepicker(showFilepicker bool) -// IsCWDFocused() bool -// } -// -// func (f *filepickerCmp) ToggleFilepicker(showFilepicker bool) { -// f.ShowFilePicker = showFilepicker -// } -// -// func (f *filepickerCmp) IsCWDFocused() bool { -// return f.cwd.Focused() -// } -// -// func NewFilepickerCmp(app *app.App) FilepickerCmp { -// homepath, err := os.UserHomeDir() -// if err != nil { -// logging.Error("error loading user files") -// return nil -// } -// baseDir := DirNode{parent: nil, directory: homepath} -// dirs := readDir(homepath, false) -// viewport := viewport.New() -// currentDirectory := textinput.New() -// currentDirectory.CharLimit = 200 -// currentDirectory.SetWidth(44) -// currentDirectory.Cursor().Blink = true -// currentDirectory.SetValue(baseDir.directory) -// return &filepickerCmp{cwdDetails: &baseDir, dirs: dirs, cursorChain: make(stack, 0), viewport: viewport, cwd: currentDirectory, app: app} -// } -// -// func (f *filepickerCmp) getCurrentFileBelowCursor() { -// if len(f.dirs) == 0 || f.cursor < 0 || f.cursor >= len(f.dirs) { -// logging.Error(fmt.Sprintf("Invalid cursor position. Dirs length: %d, Cursor: %d", len(f.dirs), f.cursor)) -// f.viewport.SetContent("Preview unavailable") -// return -// } -// -// dir := f.dirs[f.cursor] -// filename := dir.Name() -// if !dir.IsDir() && isExtSupported(filename) { -// fullPath := f.cwdDetails.directory + "/" + dir.Name() -// -// go func() { -// imageString, err := image.ImagePreview(f.viewport.Width()-4, fullPath) -// if err != nil { -// logging.Error(err.Error()) -// f.viewport.SetContent("Preview unavailable") -// return -// } -// -// f.viewport.SetContent(imageString) -// }() -// } else { -// f.viewport.SetContent("Preview unavailable") -// } -// } -// -// func readDir(path string, showHidden bool) []os.DirEntry { -// logging.Info(fmt.Sprintf("Reading directory: %s", path)) -// -// entriesChan := make(chan []os.DirEntry, 1) -// errChan := make(chan error, 1) -// -// go func() { -// dirEntries, err := os.ReadDir(path) -// if err != nil { -// logging.ErrorPersist(err.Error()) -// errChan <- err -// return -// } -// entriesChan <- dirEntries -// }() -// -// select { -// case dirEntries := <-entriesChan: -// sort.Slice(dirEntries, func(i, j int) bool { -// if dirEntries[i].IsDir() == dirEntries[j].IsDir() { -// return dirEntries[i].Name() < dirEntries[j].Name() -// } -// return dirEntries[i].IsDir() -// }) -// -// if showHidden { -// return dirEntries -// } -// -// var sanitizedDirEntries []os.DirEntry -// for _, dirEntry := range dirEntries { -// isHidden, _ := IsHidden(dirEntry.Name()) -// if !isHidden { -// if dirEntry.IsDir() || isExtSupported(dirEntry.Name()) { -// sanitizedDirEntries = append(sanitizedDirEntries, dirEntry) -// } -// } -// } -// -// return sanitizedDirEntries -// -// case err := <-errChan: -// logging.ErrorPersist(fmt.Sprintf("Error reading directory %s", path), err) -// return []os.DirEntry{} -// -// case <-time.After(5 * time.Second): -// logging.ErrorPersist(fmt.Sprintf("Timeout reading directory %s", path), nil) -// return []os.DirEntry{} -// } -// } -// -// func IsHidden(file string) (bool, error) { -// return strings.HasPrefix(file, "."), nil -// } -// -// func isExtSupported(path string) bool { -// ext := strings.ToLower(filepath.Ext(path)) -// return (ext == ".jpg" || ext == ".jpeg" || ext == ".webp" || ext == ".png") -// } diff --git a/internal/tui/components/dialogs/filepicker/filepicker.go b/internal/tui/components/dialogs/filepicker/filepicker.go index cf1949b3c1fc0ec8020ccad0a65fee2fd58b9fce..c3bda21e1577e47ec7b679eb958c70c5160e13a0 100644 --- a/internal/tui/components/dialogs/filepicker/filepicker.go +++ b/internal/tui/components/dialogs/filepicker/filepicker.go @@ -5,12 +5,14 @@ import ( "strings" "github.com/charmbracelet/bubbles/v2/filepicker" + "github.com/charmbracelet/bubbles/v2/key" tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/lipgloss/v2" "github.com/opencode-ai/opencode/internal/tui/components/core" "github.com/opencode-ai/opencode/internal/tui/components/dialogs" "github.com/opencode-ai/opencode/internal/tui/components/image" "github.com/opencode-ai/opencode/internal/tui/styles" + "github.com/opencode-ai/opencode/internal/tui/util" ) const ( @@ -19,16 +21,19 @@ const ( fileSelectionHight = 10 ) +type FilePickedMsg struct { + FilePath string +} + type FilePicker interface { dialogs.DialogModel } -type filePicker struct { +type model struct { wWidth int wHeight int width int - filepicker filepicker.Model - selectedFile string + filePicker filepicker.Model highlightedFile string image image.Model } @@ -46,31 +51,40 @@ func NewFilePickerCmp() FilePicker { fp.SetHeight(fileSelectionHight) image := image.New(1, 1, "") - return &filePicker{filepicker: fp, image: image} + return &model{filePicker: fp, image: image} } -func (m *filePicker) Init() tea.Cmd { - return m.filepicker.Init() +func (m *model) Init() tea.Cmd { + return m.filePicker.Init() } -func (m *filePicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: m.wWidth = msg.Width m.wHeight = msg.Height m.width = min(70, m.wWidth) - styles := m.filepicker.Styles + styles := m.filePicker.Styles styles.Directory = styles.Directory.Width(m.width - 4) styles.Selected = styles.Selected.PaddingLeft(1).Width(m.width - 4) styles.DisabledSelected = styles.DisabledSelected.PaddingLeft(1).Width(m.width - 4) styles.File = styles.File.Width(m.width) - m.filepicker.Styles = styles + m.filePicker.Styles = styles return m, nil + + case tea.KeyPressMsg: + if key.Matches(msg, m.filePicker.KeyMap.Back) { + // make sure we don't go back if we are at the home directory + homeDir, _ := os.UserHomeDir() + if m.filePicker.CurrentDirectory == homeDir { + return m, nil + } + } } var cmd tea.Cmd var cmds []tea.Cmd - m.filepicker, cmd = m.filepicker.Update(msg) + m.filePicker, cmd = m.filePicker.Update(msg) cmds = append(cmds, cmd) if m.highlightedFile != m.currentImage() && m.currentImage() != "" { w, h := m.imagePreviewSize() @@ -80,37 +94,40 @@ func (m *filePicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.highlightedFile = m.currentImage() // Did the user select a file? - if didSelect, path := m.filepicker.DidSelectFile(msg); didSelect { + if didSelect, path := m.filePicker.DidSelectFile(msg); didSelect { // Get the path of the selected file. - m.selectedFile = path + return m, tea.Sequence( + util.CmdHandler(dialogs.CloseDialogMsg{}), + util.CmdHandler(FilePickedMsg{FilePath: path}), + ) } m.image, cmd = m.image.Update(msg) cmds = append(cmds, cmd) return m, tea.Batch(cmds...) } -func (m *filePicker) View() tea.View { +func (m *model) View() tea.View { t := styles.CurrentTheme() content := lipgloss.JoinVertical( lipgloss.Left, t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Add Image", m.width-4)), m.imagePreview(), - m.filepicker.View(), + m.filePicker.View(), ) return tea.NewView(m.style().Render(content)) } -func (m *filePicker) currentImage() string { - for _, ext := range m.filepicker.AllowedTypes { - if strings.HasSuffix(m.filepicker.HighlightedPath(), ext) { - return m.filepicker.HighlightedPath() +func (m *model) currentImage() string { + for _, ext := range m.filePicker.AllowedTypes { + if strings.HasSuffix(m.filePicker.HighlightedPath(), ext) { + return m.filePicker.HighlightedPath() } } return "" } -func (m *filePicker) imagePreview() string { +func (m *model) imagePreview() string { t := styles.CurrentTheme() w, h := m.imagePreviewSize() if m.currentImage() == "" { @@ -125,16 +142,16 @@ func (m *filePicker) imagePreview() string { return m.imagePreviewStyle().Width(w).Height(h).Render(m.image.View()) } -func (m *filePicker) imagePreviewStyle() lipgloss.Style { +func (m *model) imagePreviewStyle() lipgloss.Style { t := styles.CurrentTheme() return t.S().Base.Padding(1, 1, 1, 1) } -func (m *filePicker) imagePreviewSize() (int, int) { +func (m *model) imagePreviewSize() (int, int) { return m.width - 4, min(20, m.wHeight/2) } -func (m *filePicker) style() lipgloss.Style { +func (m *model) style() lipgloss.Style { t := styles.CurrentTheme() return t.S().Base. Width(m.width). @@ -143,12 +160,12 @@ func (m *filePicker) style() lipgloss.Style { } // ID implements FilePicker. -func (m *filePicker) ID() dialogs.DialogID { +func (m *model) ID() dialogs.DialogID { return FilePickerID } // Position implements FilePicker. -func (m *filePicker) Position() (int, int) { +func (m *model) Position() (int, int) { row := m.wHeight/4 - 2 // just a bit above the center col := m.wWidth / 2 col -= m.width / 2 diff --git a/internal/tui/components/image/image.go b/internal/tui/components/image/image.go index d1dbfea85c825a66c4543e152511a767c874f838..5d84c18e984c0e252064f2973263f9390118e244 100644 --- a/internal/tui/components/image/image.go +++ b/internal/tui/components/image/image.go @@ -3,7 +3,6 @@ package image import ( - "context" "fmt" _ "image/jpeg" _ "image/png" @@ -17,8 +16,6 @@ type Model struct { width uint height uint err error - - cancelAnimation context.CancelFunc } func New(width, height uint, url string) Model { @@ -38,11 +35,11 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case errMsg: m.err = msg return m, nil - case rewdrawMsg: + case redrawMsg: m.width = msg.width m.height = msg.height m.url = msg.url - return m, loadUrl(m.url) + return m, loadURL(m.url) case loadMsg: return handleLoadMsg(m, msg) } @@ -60,7 +57,7 @@ type errMsg struct{ error } func (m Model) Redraw(width uint, height uint, url string) tea.Cmd { return func() tea.Msg { - return rewdrawMsg{ + return redrawMsg{ width: width, height: height, url: url, @@ -68,9 +65,9 @@ func (m Model) Redraw(width uint, height uint, url string) tea.Cmd { } } -func (m Model) UpdateUrl(url string) tea.Cmd { +func (m Model) UpdateURL(url string) tea.Cmd { return func() tea.Msg { - return rewdrawMsg{ + return redrawMsg{ width: m.width, height: m.height, url: url, @@ -78,7 +75,7 @@ func (m Model) UpdateUrl(url string) tea.Cmd { } } -type rewdrawMsg struct { +type redrawMsg struct { width uint height uint url string diff --git a/internal/tui/components/image/load.go b/internal/tui/components/image/load.go index f6015b8e2725bf3a5380eef11357e1b779bba62f..67308ef41be5c95aa7985366ad247674161dc7bc 100644 --- a/internal/tui/components/image/load.go +++ b/internal/tui/components/image/load.go @@ -6,7 +6,6 @@ import ( "image" "image/png" "io" - "io/ioutil" "net/http" "os" "strings" @@ -24,7 +23,7 @@ type loadMsg struct { io.ReadCloser } -func loadUrl(url string) tea.Cmd { +func loadURL(url string) tea.Cmd { var r io.ReadCloser var err error @@ -52,20 +51,9 @@ func load(r io.ReadCloser) tea.Cmd { } func handleLoadMsg(m Model, msg loadMsg) (Model, tea.Cmd) { - if m.cancelAnimation != nil { - m.cancelAnimation() - } - - // blank out image so it says "loading..." - m.image = "" - - return handleLoadMsgStatic(m, msg) -} - -func handleLoadMsgStatic(m Model, msg loadMsg) (Model, tea.Cmd) { defer msg.Close() - img, err := readerToimage(m.width, m.height, m.url, msg) + img, err := readerToImage(m.width, m.height, m.url, msg) if err != nil { return m, func() tea.Msg { return errMsg{err} } } @@ -73,7 +61,7 @@ func handleLoadMsgStatic(m Model, msg loadMsg) (Model, tea.Cmd) { return m, nil } -func imageToString(width, height uint, url string, img image.Image) (string, error) { +func imageToString(width, height uint, img image.Image) (string, error) { img = resize.Thumbnail(width, height*2-4, img, resize.Lanczos3) b := img.Bounds() w := b.Max.X @@ -84,7 +72,7 @@ func imageToString(width, height uint, url string, img image.Image) (string, err for x := w; x < int(width); x = x + 2 { str.WriteString(" ") } - for x := 0; x < w; x++ { + for x := range w { c1, _ := colorful.MakeColor(img.At(x, y)) color1 := p.Color(c1.Hex()) c2, _ := colorful.MakeColor(img.At(x, y+1)) @@ -99,9 +87,9 @@ func imageToString(width, height uint, url string, img image.Image) (string, err return str.String(), nil } -func readerToimage(width uint, height uint, url string, r io.Reader) (string, error) { +func readerToImage(width uint, height uint, url string, r io.Reader) (string, error) { if strings.HasSuffix(strings.ToLower(url), ".svg") { - return svgToimage(width, height, url, r) + return svgToImage(width, height, r) } img, _, err := imageorient.Decode(r) @@ -109,15 +97,15 @@ func readerToimage(width uint, height uint, url string, r io.Reader) (string, er return "", err } - return imageToString(width, height, url, img) + return imageToString(width, height, img) } -func svgToimage(width uint, height uint, url string, r io.Reader) (string, error) { +func svgToImage(width uint, height uint, r io.Reader) (string, error) { // Original author: https://stackoverflow.com/users/10826783/usual-human // https://stackoverflow.com/questions/42993407/how-to-create-and-export-svg-to-png-jpeg-in-golang // Adapted to use size from SVG, and to use temp file. - tmpPngFile, err := ioutil.TempFile("", "imgcat.*.png") + tmpPngFile, err := os.CreateTemp("", "img.*.png") if err != nil { return "", err } @@ -153,5 +141,5 @@ func svgToimage(width uint, height uint, url string, r io.Reader) (string, error if err != nil { return "", err } - return imageToString(width, height, url, img) + return imageToString(width, height, img) }