Files
MujicaUI/examples/gallery/pages_files_browser.go
T

363 lines
12 KiB
Go

package main
import (
"fmt"
"path"
"strings"
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
)
var filesFolders = map[string][]string{
"/court": {"chronicles/", "decrees/", "seal.go", "notes.md"},
"/court/chronicles": {"first.md", "second.md"},
"/court/decrees": {"42.pdf", "crest.png"},
}
func filesEntries(dir string) []mujica.FileEntry {
var entries []mujica.FileEntry
for _, n := range filesFolders[dir] {
name := strings.TrimSuffix(n, "/")
entries = append(entries, mujica.FileEntry{Path: path.Join(dir, name), Name: name, Dir: name != n, Size: 2048})
}
return entries
}
func fileExplorerDemo(c *ui.Context, log func(string)) {
type state struct {
explorer mujica.FileExplorerState
disabled, empty bool
}
s := ui.Local(c.Root(), "381", func() state { return state{} })
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.empty, "Empty folder")
r := mujica.FileExplorer(c, &s.explorer, mujica.FileExplorerOptions{
Roots: []string{"/court"}, Disabled: s.disabled,
Folders: func(dir string) []string {
var dirs []string
for _, e := range filesEntries(dir) {
if e.Dir {
dirs = append(dirs, e.Path)
}
}
return dirs
},
Entries: func(dir string) []mujica.FileEntry {
if s.empty {
return nil
}
return filesEntries(dir)
},
})
r.Element.Height(320).FillWidth()
if r.Navigated {
log("Navigate: " + s.explorer.Current)
}
if r.Changed {
log(fmt.Sprintf("Selected: %v", s.explorer.Selected()))
}
if r.Opened != nil {
log("Open: " + r.Opened.Path)
}
}
func fileGridDemo(c *ui.Context, log func(string)) {
type state struct {
grid mujica.FileGridState
entries []mujica.FileEntry
disabled, thumbs, empty bool
}
s := ui.Local(c.Root(), "382", func() state {
return state{thumbs: true, entries: append(filesEntries("/court"), filesEntries("/court/decrees")...)}
})
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.thumbs, "Thumbnails")
mediaCheck(c, log, &s.empty, "Empty")
entries := s.entries
if s.empty {
entries = nil
}
r := mujica.FileGrid(c, entries, &s.grid, mujica.FileGridOptions{Disabled: s.disabled, Thumbnail: func(e mujica.FileEntry) bool {
if !s.thumbs || mujica.FileKindOf(e.Name) != mujica.FileImage {
return false
}
mujica.Image(c, mediaBitmaps[0], mujica.ImageOptions{Alt: e.Name}).Element.Fill()
return true
}})
r.Element.Height(320).FillWidth()
if r.Changed {
log(fmt.Sprintf("Selected: %v", s.grid.Selected()))
}
if r.Opened != nil {
log("Open: " + r.Opened.Path)
}
}
func fileIconDemo(c *ui.Context, log func(string)) {
type state struct {
name string
folder, open bool
size float64
}
s := ui.Local(c.Root(), "383", func() state { return state{name: "seal.go", size: 32} })
if mujica.TextInput(c, &s.name, mujica.TextInputOptions{Label: "File name"}).Changed() {
log("File name changed")
}
mediaCheck(c, log, &s.folder, "Folder")
if s.folder {
mediaCheck(c, log, &s.open, "Open folder")
}
if mujica.NumberInput(c, &s.size, mujica.NumberInputOptions{Min: 20, Max: 64, Step: 4, Label: "Icon size"}).Changed() {
log(fmt.Sprintf("Size: %.0f", s.size))
}
mujica.FileIcon(c, s.name, mujica.FileIconOptions{Folder: s.folder, Open: s.folder && s.open, Size: float32(s.size)})
ui.Row(c).Gap(12).Children(func() {
for _, name := range []string{"seal.go", "notes.md", "crest.png", "deed.pdf", "ledger.zip", "hymn.mp3", "rite.mov", "blob"} {
mujica.FileIcon(c, name, mujica.FileIconOptions{Size: 32})
}
})
}
func filePreviewDemo(c *ui.Context, log func(string)) {
type state struct {
kind int
status mujica.DataStatus
closed bool
}
s := ui.Local(c.Root(), "384", func() state { return state{} })
if mujica.Select(c, &s.kind, []mujica.SelectOption[int]{{Value: 0, Label: "Code"}, {Value: 1, Label: "Text"}, {Value: 2, Label: "Image"}, {Value: 3, Label: "Unsupported"}}, mujica.SelectOptions{Label: "Preview type"}).Changed() {
log("Preview type changed")
s.closed = false
}
filesStatus(c, log, &s.status)
if s.closed {
if mujica.Button(c, "Show preview", mujica.ButtonOptions{}).Clicked() {
s.closed = false
log("Preview reopened")
}
return
}
f := []mujica.PreviewFile{{Name: "seal.go", Text: "package court\n\nfunc Seal() {}"}, {Name: "notes.md", Text: "The seal of the court."}, {Name: "crest.png", Image: mediaBitmaps[0]}, {Name: "deed.pdf", Size: 4096}}[s.kind]
r := mujica.FilePreview(c, f, mujica.FilePreviewOptions{Status: s.status, Error: "Reading failed"})
r.Element.Height(280).FillWidth()
if r.Closed {
s.closed = true
log("Preview closed")
}
if r.Opened {
log("Open: " + f.Name)
}
if r.Retried {
s.status = mujica.DataReady
log("Read retried")
}
}
func filesStatus(c *ui.Context, log func(string), value *mujica.DataStatus) {
if mujica.Select(c, value, []mujica.SelectOption[mujica.DataStatus]{{Value: mujica.DataReady, Label: "Ready"}, {Value: mujica.DataLoading, Label: "Loading"}, {Value: mujica.DataFailed, Label: "Failed"}}, mujica.SelectOptions{Label: "Load status"}).Changed() {
log(fmt.Sprintf("Status: %d", *value))
}
}
func pathBarDemo(c *ui.Context, log func(string)) {
type state struct {
path string
disabled bool
}
s := ui.Local(c.Root(), "385", func() state { return state{path: "/court/chronicles/royal/archives/seals"} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.TextInput(c, &s.path, mujica.TextInputOptions{Label: "Path"}).Changed() {
log("Path changed")
}
if strings.Trim(s.path, "/") == "" {
ui.Text(c, "Enter a path")
return
}
if r := mujica.PathBar(c, s.path, mujica.PathBarOptions{Disabled: s.disabled}); r.Navigated != "" {
s.path = r.Navigated
log("Navigate: " + r.Navigated)
}
}
func renameInlineDemo(c *ui.Context, log func(string)) {
type state struct {
name string
editing, disabled, folder bool
}
s := ui.Local(c.Root(), "386", func() state { return state{name: "seal.txt"} })
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.folder, "Folder")
if mujica.Button(c, "Rename", mujica.ButtonOptions{Disabled: s.disabled}).Clicked() {
s.editing = true
log("Rename started")
}
r := mujica.RenameInline(c, &s.name, &s.editing, mujica.RenameInlineOptions{Disabled: s.disabled, Folder: s.folder, Validate: func(name string) string {
if strings.ContainsAny(name, "/\\") {
return "Name contains a path separator"
}
return ""
}})
if r.Renamed {
log("Renamed: " + s.name)
}
if r.Canceled {
log("Rename canceled")
}
}
func fileOperationProgressDemo(c *ui.Context, log func(string)) {
type state struct {
op mujica.FileOperation
disabled, conflict bool
canceled bool
}
s := ui.Local(c.Root(), "387", func() state {
return state{op: mujica.FileOperation{Kind: mujica.FileCopy, Total: 10, Current: "seal.go"}}
})
mediaCheck(c, log, &s.disabled, "Disabled")
mediaCheck(c, log, &s.conflict, "Conflict")
if mujica.Select(c, &s.op.Kind, []mujica.SelectOption[mujica.FileOperationKind]{{Value: mujica.FileCopy, Label: "Copy"}, {Value: mujica.FileMove, Label: "Move"}, {Value: mujica.FileDelete, Label: "Delete"}}, mujica.SelectOptions{Label: "Operation"}).Changed() {
log("Operation changed")
}
if mujica.Button(c, "Advance", mujica.ButtonOptions{Disabled: s.op.Done == s.op.Total}).Clicked() {
s.op.Done++
log(fmt.Sprintf("Done: %d", s.op.Done))
}
if mujica.Button(c, "Reset", mujica.ButtonOptions{}).Clicked() {
s.op.Done = 0
s.canceled = false
log("Operation reset")
}
op := s.op
if s.conflict {
op.Conflict = "seal.go"
}
r := mujica.FileOperationProgress(c, op, mujica.FileOperationOptions{Disabled: s.disabled || s.canceled})
if r.Canceled {
s.canceled = true
log("Operation canceled")
}
if r.Resolved != mujica.ConflictNone {
s.conflict = false
log(fmt.Sprintf("Conflict: %d; apply all: %v", r.Resolved, r.ApplyAll))
}
}
func filesTransfers() []mujica.QueuedTransfer {
return []mujica.QueuedTransfer{
{ID: "a", Name: "ledger.zip", Upload: true, Status: mujica.TransferActive, Done: 30, Total: 120},
{ID: "b", Name: "crest.png", Status: mujica.TransferPaused, Done: 1, Total: 4},
{ID: "c", Name: "deed.pdf", Upload: true, Status: mujica.TransferFailed, Total: 2, Error: "Network lost"},
}
}
func transferQueueDemo(c *ui.Context, log func(string)) {
type state struct {
items []mujica.QueuedTransfer
queue mujica.TransferQueueState
disabled bool
revision uint64
}
s := ui.Local(c.Root(), "388", func() state { return state{items: filesTransfers()} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Reset", mujica.ButtonOptions{}).Clicked() {
s.items = filesTransfers()
log("Queue reset")
}
r := mujica.TransferQueue(c, s.items, &s.queue, mujica.TransferQueueOptions{Disabled: s.disabled, Revision: s.revision})
r.Element.Height(260).FillWidth()
if r.Action == mujica.TransferNone {
return
}
for i := range s.items {
if s.items[i].ID == r.ID {
switch r.Action {
case mujica.TransferPause:
s.items[i].Status = mujica.TransferPaused
case mujica.TransferResume, mujica.TransferRetry:
s.items[i].Status = mujica.TransferActive
case mujica.TransferRemove:
s.items = append(s.items[:i:i], s.items[i+1:]...)
}
break
}
}
s.revision++
log(fmt.Sprintf("Transfer %s: %d", r.ID, r.Action))
}
func storageUsageDemo(c *ui.Context, log func(string)) {
type state struct{ photos float64 }
s := ui.Local(c.Root(), "389", func() state { return state{photos: 40} })
if mujica.NumberInput(c, &s.photos, mujica.NumberInputOptions{Min: 0, Max: 92, Step: 1, Label: "Photos (GB)"}).Changed() {
log(fmt.Sprintf("Photos: %.0f GB", s.photos))
}
mujica.StorageUsage(c, []mujica.StorageSegment{{Label: "Documents", Bytes: 24 << 30}, {Label: "Photos", Bytes: int64(s.photos) * (1 << 30)}, {Label: "Apps", Bytes: 12 << 30}}, mujica.StorageUsageOptions{Capacity: 128 << 30, Label: "Court disk"}).FillWidth()
}
func filesRecent() []mujica.RecentFile {
now := time.Date(2026, 10, 5, 15, 0, 0, 0, time.UTC)
return []mujica.RecentFile{{Path: "/court/seal.go", Opened: now}, {Path: "/court/notes.md", Opened: now.AddDate(0, 0, -1)}, {Path: "/court/crest.png", Opened: now.AddDate(0, 0, -4)}, {Path: "/court/charter.pdf", Opened: now.AddDate(0, -1, 0)}}
}
func recentFilesDemo(c *ui.Context, log func(string)) {
type state struct {
files []mujica.RecentFile
recent mujica.RecentFilesState
disabled bool
}
s := ui.Local(c.Root(), "390", func() state { return state{files: filesRecent()} })
mediaCheck(c, log, &s.disabled, "Disabled")
if mujica.Button(c, "Reset", mujica.ButtonOptions{}).Clicked() {
s.files = filesRecent()
log("Recent files reset")
}
r := mujica.RecentFiles(c, s.files, &s.recent, mujica.RecentFilesOptions{Disabled: s.disabled, Now: time.Date(2026, 10, 5, 15, 0, 0, 0, time.UTC)})
r.Element.Height(320).FillWidth()
if r.Opened != nil {
log("Open: " + r.Opened.Path)
}
if r.Cleared {
s.files = nil
log("Recent files cleared")
}
if r.Removed != "" {
for i, f := range s.files {
if f.Path == r.Removed {
s.files = append(s.files[:i:i], s.files[i+1:]...)
break
}
}
log("Removed: " + r.Removed)
}
}
func archiveViewerDemo(c *ui.Context, log func(string)) {
type state struct {
archive mujica.ArchiveViewerState
entries []mujica.ArchiveEntry
status mujica.DataStatus
empty bool
}
s := ui.Local(c.Root(), "391", func() state {
return state{entries: []mujica.ArchiveEntry{{Path: "court/seal.go", Size: 4000, Packed: 1000}, {Path: "court/decrees/42.pdf", Size: 6000, Packed: 5000}, {Path: "README.md", Size: 1000, Packed: 400}}}
})
filesStatus(c, log, &s.status)
mediaCheck(c, log, &s.empty, "Empty")
entries := s.entries
if s.empty {
entries = nil
}
r := mujica.ArchiveViewer(c, entries, &s.archive, mujica.ArchiveViewerOptions{Status: s.status, Error: "Invalid archive"})
r.Element.Height(280).FillWidth()
if r.Opened != nil {
log("Open archive entry: " + r.Opened.Path)
}
if r.Retried {
s.status = mujica.DataReady
log("Archive read retried")
}
}