Files

249 lines
6.1 KiB
Go

package export
import (
"strconv"
"sync/atomic"
"aego/core/mathx"
"aego/editor/core/services"
"aego/editor/core/tasks"
"aego/engine/problems"
uicore "aego/ui/core"
"aego/ui/widgets"
)
const (
ID = "export"
Release = "release"
Development = "development"
labelW = 80
)
var profiles = []string{Development, Release}
type Deps struct {
Exporter services.Exporter
Tasks *tasks.Queue
}
type line struct {
icon string
col mathx.Color
text string
right string
}
type Panel struct {
d Deps
target string
profile string
running *tasks.Task
stage atomic.Value
rep *services.ExportReport
issues []problems.Diagnostic
msg string
scroll float32
}
func New(d Deps) *Panel {
return &Panel{d: d, target: "host", profile: Development}
}
func (p *Panel) ID() string {
return ID
}
func (p *Panel) Title() string {
return "Export"
}
func (p *Panel) Icon() string {
return "file.package"
}
func (p *Panel) choices(ui *uicore.Context, label string, opts []string, cur *string, key string) {
th := &ui.Theme
row := ui.Cursor().Next(0)
ui.Text(label, mathx.Vec2{X: row.X, Y: ui.TextY(row)}, th.TextDim)
x := row.X + labelW
for i, o := range opts {
w := ui.TextSize(o).X + 16
rr := mathx.Rect{X: x, Y: row.Y, W: w, H: row.H}
if widgets.ButtonRect(ui, uicore.IDSeq(ui.ID(key), i), rr, o) && p.running == nil {
*cur = o
}
if *cur == o {
ui.List().RectOutline(rr, th.Accent, 2)
}
x += w + 4
}
}
func (p *Panel) button(ui *uicore.Context, key string, row mathx.Rect, x *float32, label string, on bool) bool {
th := &ui.Theme
w := ui.TextSize(label).X + 16
rr := mathx.Rect{X: *x, Y: row.Y, W: w, H: row.H}
*x += w + 6
if !on {
ui.List().RectOutline(rr, th.TextDisabled, 1)
ui.Text(label, mathx.Vec2{X: rr.X + 8, Y: ui.TextY(rr)}, th.TextDisabled)
return false
}
return widgets.ButtonRect(ui, ui.ID(key), rr, label)
}
func (p *Panel) status() string {
if p.running != nil {
s, _ := p.stage.Load().(string)
return "Working… " + strconv.Itoa(int(p.running.Progress()*100)) + "% · " + s
}
return p.msg
}
func errorCount(ds []problems.Diagnostic) (int, int) {
e, w := 0, 0
for _, d := range ds {
switch d.Severity {
case problems.Error:
e++
case problems.Warning:
w++
}
}
return e, w
}
func (p *Panel) check() {
x := p.d.Exporter
prep, prof := x.Prepare(), p.profile
var found []problems.Diagnostic
p.stage.Store("checking")
p.running = p.d.Tasks.Work(tasks.Spec{Title: "Check export (" + prof + ")", Kind: "export"}, func(*tasks.Task) error {
found = x.Check(prep, prof)
return nil
}).Then(func(t *tasks.Task) {
p.running = nil
if t.State() != tasks.Done {
p.msg = "Check canceled"
return
}
p.rep, p.issues = nil, found
e, w := errorCount(found)
switch {
case e > 0:
p.msg = strconv.Itoa(e) + " problem(s) block the export"
case w > 0:
p.msg = "Ready to export, " + strconv.Itoa(w) + " warning(s)"
default:
p.msg = "Ready to export"
}
})
}
func (p *Panel) export(out string) {
x := p.d.Exporter
prep := x.Prepare()
req := services.ExportRequest{Target: p.target, Profile: p.profile, Out: out}
var rep services.ExportReport
var err error
p.stage.Store("starting")
p.running = p.d.Tasks.Work(tasks.Spec{Title: "Export " + req.Target + " " + req.Profile, Kind: "export", Detail: out}, func(t *tasks.Task) error {
rep, err = x.Export(prep, req, func(f float32, s string) {
t.SetProgress(f)
p.stage.Store(s)
})
return err
}).Then(func(t *tasks.Task) {
p.running = nil
if t.State() == tasks.Canceled {
p.msg = "Export canceled"
return
}
p.rep, p.issues = &rep, rep.Issues
switch {
case rep.Blocked:
e, _ := errorCount(rep.Issues)
p.msg = "Export is blocked by " + strconv.Itoa(e) + " problem(s)"
case err != nil:
p.msg = "Export failed: " + err.Error()
default:
p.msg = "Exported to " + rep.Out
}
})
}
func (p *Panel) lines(ui *uicore.Context) []line {
th := &ui.Theme
var out []line
for _, d := range p.issues {
l := line{icon: "log.info", col: th.TextDim, text: d.Message, right: d.Code}
switch d.Severity {
case problems.Error:
l.icon, l.col = "log.error", th.Error
case problems.Warning:
l.icon, l.col = "status.warning", th.Warn
}
if d.Subject.File != "" {
l.text = d.Subject.File + ": " + d.Message
}
out = append(out, l)
}
if p.rep != nil && !p.rep.Blocked {
for _, kv := range p.rep.Lines {
out = append(out, line{text: kv[0], right: kv[1], col: th.Text})
}
}
return out
}
func (p *Panel) Draw(f *services.Frame, r mathx.Rect) {
ui := f.UI
th := &ui.Theme
x := p.d.Exporter
p.choices(ui, "Target", x.Targets(), &p.target, "target")
p.choices(ui, "Profile", profiles, &p.profile, "profile")
out := x.Out(p.target, p.profile)
row := ui.Cursor().Next(0)
ui.Text("Output", mathx.Vec2{X: row.X, Y: ui.TextY(row)}, th.TextDim)
path := mathx.Rect{X: row.X + labelW, Y: row.Y, W: max(0, row.W-labelW), H: row.H}
ui.PushClip(path)
ui.Text(out, mathx.Vec2{X: path.X, Y: ui.TextY(row)}, th.Text)
ui.PopClip()
row = ui.Cursor().Next(0)
busy := p.running != nil
bx := row.X
if p.button(ui, "check", row, &bx, "Check", !busy) {
p.check()
}
if p.button(ui, "export", row, &bx, "Export", !busy) {
p.export(out)
}
done := p.rep != nil && !p.rep.Blocked && p.rep.Out != ""
if p.button(ui, "reveal", row, &bx, "Reveal in File Manager", !busy && done) {
if err := x.Reveal(p.rep.Out); err != nil {
p.msg = "Cannot open the file manager: " + err.Error()
}
}
row = ui.Cursor().Next(0)
ui.PushClip(row)
ui.Text(p.status(), mathx.Vec2{X: row.X, Y: ui.TextY(row)}, th.TextDim)
ui.PopClip()
lines := p.lines(ui)
top := ui.Cursor().Y
list := mathx.Rect{X: r.X, Y: top, W: r.W, H: max(0, r.Y+r.H-top)}
widgets.VirtualList(ui, ui.ID("rows"), list, len(lines), th.RowHeight+2, &p.scroll, func(i int, rr mathx.Rect) {
l := lines[i]
tx := rr.X + 4
if l.icon != "" {
widgets.IconAt(ui, mathx.Rect{X: rr.X + 4, Y: rr.Y + (rr.H-16)/2, W: 16, H: 16}, l.icon, l.col)
tx += 20
}
ui.PushClip(rr)
ui.Text(l.text, mathx.Vec2{X: tx, Y: ui.TextY(rr)}, l.col)
if l.right != "" {
ui.TextIn(rr, l.right, th.TextDim, uicore.AlignRight)
}
ui.PopClip()
})
}