kanban

Kanban client in Gio
Log | Files | Refs | README | LICENSE

commit 3e03d0643accf8019fe1254ee0d779426a48f68c
parent 96899077ba06fbc4788e2fc4a7ffad74d5e15d8a
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date:   Sun, 28 Feb 2021 09:55:02 +0800

ref: use uuids to identify projects

Signed-off-by: Jack Mordaunt <jackmordaunt@gmail.com>

Diffstat:
Mcmd/kanban/ui.go | 174+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mcmd/kanban/widgets.go | 22+++++++++++++++++++---
Mkanban.go | 8+++++---
3 files changed, 145 insertions(+), 59 deletions(-)

diff --git a/cmd/kanban/ui.go b/cmd/kanban/ui.go @@ -92,7 +92,8 @@ type UI struct { // Stage kanban.ID // } - CreateProjectButton widget.Clickable + CreateProjectBtn widget.Clickable + EditProjectBtn widget.Clickable } // Loop runs the event loop until terminated. @@ -170,8 +171,12 @@ func (ui *UI) Update(gtx C) { } } } - if ui.ProjectForm.Submit.Clicked() { - p := kanban.Project{ + if ui.ProjectForm.SubmitBtn.Clicked() { + // @cleanup + // Could we enforce this relationship with the api? + // Perhaps pass in the projects slice and have it append to it. + if err := ui.Storage.Create(kanban.Project{ + ID: uuid.New(), Name: ui.ProjectForm.Name.Text(), Stages: []kanban.Stage{ {Name: "Todo"}, @@ -179,11 +184,7 @@ func (ui *UI) Update(gtx C) { {Name: "Testing"}, {Name: "Done"}, }, - } - // @cleanup - // Could we enforce this relationship with the api? - // Perhaps pass in the projects slice and have it append to it. - if err := ui.Storage.Create(p); err != nil { + }); err != nil { log.Printf("creating new project: %v", err) } else { if projects, err := ui.Storage.List(); err == nil { @@ -202,7 +203,7 @@ func (ui *UI) Update(gtx C) { } } } - if ui.ProjectForm.Cancel.Clicked() { + if ui.ProjectForm.CancelBtn.Clicked() { ui.Clear() } for ii := range ui.Panels { @@ -262,9 +263,12 @@ func (ui *UI) Update(gtx C) { if ui.TicketDetails.Cancel.Clicked() { ui.Clear() } - if ui.CreateProjectButton.Clicked() { + if ui.CreateProjectBtn.Clicked() { ui.CreateProject() } + if ui.EditProjectBtn.Clicked() { + ui.EditProject() + } } // Layout UI. @@ -323,7 +327,7 @@ func (ui *UI) layoutRail(gtx C) D { gtx, func(gtx C) D { return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D { - btn := material.IconButton(ui.Th, &ui.CreateProjectButton, icons.ContentAdd) + btn := material.IconButton(ui.Th, &ui.CreateProjectBtn, icons.ContentAdd) btn.Size = unit.Dp(20) btn.Inset = layout.UniformInset(unit.Dp(8)) return btn.Layout(gtx) @@ -334,49 +338,101 @@ func (ui *UI) layoutRail(gtx C) D { } func (ui *UI) layoutContent(gtx C) D { - return layout.Stack{}.Layout( + return layout.Flex{ + Axis: layout.Vertical, + }.Layout( gtx, - layout.Stacked(func(gtx C) D { + // @todo streamline into app bar. + layout.Rigid(func(gtx C) D { if ui.Project == nil { return D{} } - ui.TicketStates.Begin() - return layout.Flex{ - Axis: layout.Horizontal, - Spacing: layout.SpaceEvenly, - }.Layout( + return layout.Stack{}.Layout( gtx, - func() (panels []layout.FlexChild) { - // @decouple this iteration relies on the coincidence that panels are ordered the same. - for ii, stage := range ui.Project.Stages { - stage := stage - panel := ui.Panels[ii] - panels = append(panels, layout.Flexed(1, func(gtx C) D { - return panel.Layout(gtx, ui.Th, func() (tickets []layout.ListElement) { - for _, ticket := range stage.Tickets { - ticket := ticket - t := (*Ticket)(ui.TicketStates.New(ticket.Title, unsafe.Pointer(&Ticket{}))) - t.Ticket = ticket - t.Stage = stage.Name - tickets = append(tickets, func(gtx C, index int) D { - return t.Layout(gtx, ui.Th, false) - }) - } - return tickets - }()...) - })) - } - return panels - }()..., + layout.Expanded(func(gtx C) D { + return util.Rect{ + Color: color.NRGBA{A: 255}, + Size: f32.Point{ + X: float32(gtx.Constraints.Max.X), + Y: float32(gtx.Constraints.Min.Y), + }, + }.Layout(gtx) + }), + layout.Stacked(func(gtx C) D { + return layout.Inset{ + Left: unit.Dp(10), + Right: unit.Dp(10), + }.Layout(gtx, func(gtx C) D { + return layout.Flex{ + Axis: layout.Horizontal, + Alignment: layout.Middle, + }.Layout( + gtx, + layout.Rigid(func(gtx C) D { + l := material.H5(ui.Th, ui.Project.Name) + l.Color = ui.Th.ContrastFg + return l.Layout(gtx) + }), + layout.Flexed(1, func(gtx C) D { + return D{Size: image.Point{X: gtx.Constraints.Max.X, Y: gtx.Constraints.Min.Y}} + }), + layout.Rigid(func(gtx C) D { + btn := material.IconButton(ui.Th, &ui.EditProjectBtn, icons.Configuration) + btn.Background = color.NRGBA{} + btn.Inset = layout.UniformInset(unit.Dp(5)) + return btn.Layout(gtx) + }), + ) + }) + }), ) }), - layout.Expanded(func(gtx C) D { - if ui.Modal == nil { - return D{} - } - return Modal(gtx, func(gtx C) D { - return ui.Modal(gtx) - }) + layout.Flexed(1, func(gtx C) D { + return layout.Stack{}.Layout( + gtx, + layout.Stacked(func(gtx C) D { + if ui.Project == nil { + return D{} + } + ui.TicketStates.Begin() + return layout.Flex{ + Axis: layout.Horizontal, + Spacing: layout.SpaceEvenly, + }.Layout( + gtx, + func() (panels []layout.FlexChild) { + // @decouple this iteration relies on the coincidence that panels are ordered the same. + for ii, stage := range ui.Project.Stages { + stage := stage + panel := ui.Panels[ii] + panels = append(panels, layout.Flexed(1, func(gtx C) D { + return panel.Layout(gtx, ui.Th, func() (tickets []layout.ListElement) { + for _, ticket := range stage.Tickets { + ticket := ticket + t := (*Ticket)(ui.TicketStates.New(ticket.Title, unsafe.Pointer(&Ticket{}))) + t.Ticket = ticket + t.Stage = stage.Name + tickets = append(tickets, func(gtx C, index int) D { + return t.Layout(gtx, ui.Th, false) + }) + } + return tickets + }()...) + })) + } + return panels + }()..., + ) + }), + layout.Expanded(func(gtx C) D { + if ui.Modal == nil { + return D{} + } + return Modal(gtx, func(gtx C) D { + return ui.Modal(gtx) + }) + }), + ) }), ) } @@ -467,7 +523,7 @@ func (ui *UI) InspectTicket(t kanban.Ticket) { // EditTicket opens the ticket form for editing ticket data. func (ui *UI) EditTicket(t kanban.Ticket) { - ui.TicketForm.Set(t) + ui.TicketForm.Edit(t) ui.Modal = func(gtx C) D { return ui.TicketForm.Layout(gtx, ui.Th, "") } @@ -481,7 +537,15 @@ func (ui *UI) AddTicket(stage string) { } } -// CreatTicket opens the project creation dialog. +// DeleteTickets opens the confirmation dialog for deleting a ticket. +func (ui *UI) DeleteTicket(t kanban.Ticket) { + ui.DeleteDialog.Ticket = t + ui.Modal = func(gtx C) D { + return ui.DeleteDialog.Layout(gtx, ui.Th) + } +} + +// CreateProject opens the project creation dialog. func (ui *UI) CreateProject() { ui.ProjectForm.Name.Focus() ui.Modal = func(gtx C) D { @@ -489,11 +553,15 @@ func (ui *UI) CreateProject() { } } -// DeleteTickets opens the confirmation dialog for deleting a ticket. -func (ui *UI) DeleteTicket(t kanban.Ticket) { - ui.DeleteDialog.Ticket = t +// EditProject opens the project edit form. +func (ui *UI) EditProject() { + if ui.Project == nil { + return + } + // ui.ProjectForm.Edit(*ui.Project) + ui.ProjectForm.Name.Focus() ui.Modal = func(gtx C) D { - return ui.DeleteDialog.Layout(gtx, ui.Th) + return ui.ProjectForm.Layout(gtx, ui.Th) } } diff --git a/cmd/kanban/widgets.go b/cmd/kanban/widgets.go @@ -99,9 +99,25 @@ func (f *TicketForm) Layout(gtx C, th *material.Theme, stage string) D { // ProjectForm renders a form for manipulating projects. type ProjectForm struct { - Name component.TextField - Submit widget.Clickable - Cancel widget.Clickable + kanban.Project + Name component.TextField + SubmitBtn widget.Clickable + CancelBtn widget.Clickable +} + +// Edit the provided project. +func (f *ProjectForm) Edit(p kanban.Project) { + f.Project = p + f.Name.SetText(p.Name) +} + +func (f *ProjectForm) Submit() kanban.Project { + defer func() { + f.Project = kanban.Project{} + }() + return kanban.Project{ + Name: f.Name.Text(), + } } func (f *ProjectForm) Layout(gtx C, th *material.Theme) D { diff --git a/kanban.go b/kanban.go @@ -40,10 +40,12 @@ import ( // Project is a context for a given set of tickets. type Project struct { - // Name of project, must be unique. + ID uuid.UUID + // Name of the project. Name string - // Stages owned by this project. - Stages Stages + // Stages is the list of stages owned by the project. + Stages Stages + // Finalized is a psuedo stage that contains all finalized tickets. Finalized []Ticket }