125 lines
3.8 KiB
Go
125 lines
3.8 KiB
Go
package chat
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/core"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/icons"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/internal"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/theme"
|
|
)
|
|
|
|
// FileMessageOptions describe a file sent in a chat.
|
|
type FileMessageOptions struct {
|
|
// Name is the file name; required.
|
|
Name string
|
|
// Size is the length in bytes.
|
|
Size int64
|
|
// Type is the MIME type; empty guesses from the name's extension.
|
|
Type string
|
|
}
|
|
|
|
// FileMessageResult is a FileMessage's element and this frame's actions.
|
|
type FileMessageResult struct {
|
|
Element *ui.Element
|
|
opened, downloaded bool
|
|
}
|
|
|
|
// Opened reports a click or Enter on the file.
|
|
func (r FileMessageResult) Opened() bool { return r.opened }
|
|
|
|
// Downloaded reports a click on the download button.
|
|
func (r FileMessageResult) Downloaded() bool { return r.downloaded }
|
|
|
|
// FileMessage shows a file's type icon, name and size, with open and download actions.
|
|
func FileMessage(c *ui.Context, opts FileMessageOptions) FileMessageResult {
|
|
if opts.Name == "" {
|
|
panic("mujica: FileMessage needs a Name")
|
|
}
|
|
if opts.Size < 0 {
|
|
panic("mujica: FileMessage Size must not be negative")
|
|
}
|
|
k := core.Tokens(c)
|
|
u := core.Density(c).Unit()
|
|
size := fileMessageSize(c, opts.Size)
|
|
var r FileMessageResult
|
|
r.Element = ui.Row(c).Gap(u).AlignItems(ui.Center).Padding(u, u, u, u*2).MaxWidth(360).
|
|
Radius(theme.CardRadius).Background(k.Surface).Border(theme.BorderWidth, k.Border)
|
|
disabled := r.Element.IsDisabled()
|
|
r.Element.Children(func() {
|
|
open := ui.ButtonBase(c).Grow(1).MinWidth(0).Gap(u * 3).Padding(u).Justify(ui.Start).Radius(theme.ControlRadius).
|
|
Label(fmt.Sprintf(core.Msg(c, "file_message.open"), opts.Name, size))
|
|
internal.Ring(open, k.Focus, theme.ControlRadius)
|
|
if open.Hovered() {
|
|
open.Background(k.SurfaceHover)
|
|
}
|
|
open.Children(func() {
|
|
ui.Icon(c, icons.Must(fileMessageIcon(opts.Name, opts.Type))).Size(24, 24).Shrink(0).TextColor(k.AccentText)
|
|
ui.Column(c).MinWidth(0).Grow(1).Children(func() {
|
|
ui.Text(c, opts.Name).SingleLine().FontWeight(600)
|
|
ui.Text(c, size).FontSize(core.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted).SingleLine()
|
|
})
|
|
})
|
|
if open.Clicked() && !disabled {
|
|
r.opened = true
|
|
slog.Debug("mujica: file_message open")
|
|
}
|
|
if core.IconAction(c, icons.Must("download"), fmt.Sprintf(core.Msg(c, "file_message.download"), opts.Name)).Clicked() && !disabled {
|
|
r.downloaded = true
|
|
slog.Debug("mujica: file_message download")
|
|
}
|
|
})
|
|
return r
|
|
}
|
|
|
|
// fileMessageSize formats n bytes in binary units.
|
|
func fileMessageSize(c *ui.Context, n int64) string {
|
|
units := []string{"B", "KB", "MB", "GB", "TB"}
|
|
v, i := float64(n), 0
|
|
for v >= 1024 && i < len(units)-1 {
|
|
v /= 1024
|
|
i++
|
|
}
|
|
if i == 0 {
|
|
return fmt.Sprintf(core.Msg(c, "file_message.bytes"), n)
|
|
}
|
|
return fmt.Sprintf("%.1f %s", v, units[i])
|
|
}
|
|
|
|
// fileMessageKinds maps extensions to type icons.
|
|
var fileMessageKinds = func() map[string]string {
|
|
m := map[string]string{}
|
|
for icon, exts := range map[string]string{
|
|
"image": ".png .jpg .jpeg .gif .webp .svg",
|
|
"file-archive": ".zip .tar .gz .tgz .7z .rar",
|
|
"file-code": ".go .js .ts .py .json .sh .sql .html .css .rs .java .c .h",
|
|
"file-text": ".txt .md .pdf .doc .docx .rtf",
|
|
} {
|
|
for _, e := range strings.Fields(exts) {
|
|
m[e] = icon
|
|
}
|
|
}
|
|
return m
|
|
}()
|
|
|
|
// fileMessageIcon picks a type icon from the MIME type, else the extension.
|
|
func fileMessageIcon(name, mime string) string {
|
|
switch {
|
|
case strings.HasPrefix(mime, "image/"):
|
|
return "image"
|
|
case strings.Contains(mime, "zip"):
|
|
return "file-archive"
|
|
case strings.HasPrefix(mime, "text/"):
|
|
return "file-text"
|
|
}
|
|
if icon, ok := fileMessageKinds[strings.ToLower(path.Ext(name))]; ok {
|
|
return icon
|
|
}
|
|
return "file"
|
|
}
|