commit 40391070a55f0cc94dfce699f34739fdd0153822
parent dc2c80d4735d41cc7ba14a3c42a4576220d94c62
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date: Sat, 10 Apr 2021 16:27:19 +0800
fix: ensure project pointer points into project slice
Project pointer was pointing a to a loop variable which is a copy.
Not sure how to enforce this invariant across the code, since it's a pattern that the UI relies on.
Diffstat:
6 files changed, 73 insertions(+), 87 deletions(-)
diff --git a/cmd/kanban/ui.go b/cmd/kanban/ui.go
@@ -175,25 +175,27 @@ func (ui *UI) Update(gtx C) {
}
}
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"},
- {Name: "In Progress"},
- {Name: "Testing"},
- {Name: "Done"},
- },
- }); err != nil {
- log.Printf("creating new project: %v", err)
- } else {
- if projects, err := ui.Storage.List(); err == nil {
- ui.Projects = projects
+ if ui.ProjectForm.Mode() == ModeEdit {
+ ui.ProjectForm.Submit()
+ }
+ if ui.ProjectForm.Mode() == ModeCreate {
+ if err := ui.Storage.Create(kanban.Project{
+ ID: uuid.New(),
+ Name: ui.ProjectForm.Name.Text(),
+ Stages: []kanban.Stage{
+ {Name: "Todo"},
+ {Name: "In Progress"},
+ {Name: "Testing"},
+ {Name: "Done"},
+ },
+ }); err != nil {
+ log.Printf("creating new project: %v", err)
} else {
- log.Printf("listing projects: %v", err)
+ if projects, err := ui.Storage.List(); err == nil {
+ ui.Projects = projects
+ } else {
+ log.Printf("listing projects: %v", err)
+ }
}
}
ui.Clear()
@@ -560,7 +562,7 @@ func (ui *UI) EditProject() {
if ui.Project == nil {
return
}
- // ui.ProjectForm.Edit(*ui.Project)
+ ui.ProjectForm.Edit(ui.Project)
ui.ProjectForm.Name.Focus()
ui.Modal = func(gtx C) D {
return ui.ProjectForm.Layout(gtx, ui.Th)
@@ -573,10 +575,9 @@ type Projects []kanban.Project
// Find and return project by name.
// Boolean indicates whether the project exists.
func (plist Projects) Find(name string) (*kanban.Project, bool) {
- for _, p := range plist {
- p := p
- if p.Name == name {
- return &p, true
+ for ii := range plist {
+ if plist[ii].Name == name {
+ return &plist[ii], true
}
}
return nil, false
diff --git a/cmd/kanban/widgets.go b/cmd/kanban/widgets.go
@@ -20,6 +20,16 @@ import (
"github.com/google/uuid"
)
+// Mode signals the form mode.
+// Typically a form is either dealing with an entity to be created, or
+// manipulating an existing entity.
+type Mode int
+
+const (
+ ModeCreate Mode = iota
+ ModeEdit
+)
+
// TicketForm renders the form for ticket information.
//
// @Todo use form pattern from avisha.
@@ -99,30 +109,37 @@ func (f *TicketForm) Layout(gtx C, th *material.Theme, stage string) D {
// ProjectForm renders a form for manipulating projects.
type ProjectForm struct {
- kanban.Project
+ *kanban.Project
Name component.TextField
SubmitBtn widget.Clickable
CancelBtn widget.Clickable
}
// Edit the provided project.
-func (f *ProjectForm) Edit(p kanban.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(),
+// Submit writes form data to the entity.
+func (f *ProjectForm) Submit() {
+ f.Project.Name = f.Name.Text()
+}
+
+func (f *ProjectForm) Mode() Mode {
+ if f.Project.ID != uuid.Nil {
+ return ModeEdit
}
+ return ModeCreate
}
func (f *ProjectForm) Layout(gtx C, th *material.Theme) D {
+ var title = "Create a new Project"
+ if f.Project != nil {
+ title = "Edit Project"
+ }
return control.Card{
- Title: "Create a new Project",
+ Title: title,
Body: func(gtx C) D {
return layout.Flex{
Axis: layout.Vertical,
diff --git a/storage/bolt/bolt.go b/storage/bolt/bolt.go
@@ -10,6 +10,12 @@ import (
"github.com/google/uuid"
)
+var _ storage.Storer = (*Storer)(nil)
+
+type Storer struct {
+ *bolt.DB
+}
+
type Bucket []byte
func (b Bucket) String() string {
@@ -20,12 +26,6 @@ var (
BucketProject Bucket = Bucket("Project")
)
-var _ storage.Storer = (*Storer)(nil)
-
-type Storer struct {
- *bolt.DB
-}
-
func Open(path string) (*Storer, error) {
db, err := bolt.Open(path, 0660, nil)
if err != nil {
@@ -85,23 +85,6 @@ func (db *Storer) Save(projects ...kanban.Project) error {
})
}
-func (db *Storer) Lookup(name string) (p kanban.Project, ok bool, err error) {
- // @enhance: index by name
- return p, ok, db.View(func(tx *bolt.Tx) error {
- c := tx.Bucket(BucketProject).Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- if err := json.Unmarshal(v, &p); err != nil {
- return fmt.Errorf("deserializing project: %w", err)
- }
- if p.Name == name {
- ok = true
- break
- }
- }
- return nil
- })
-}
-
func (db *Storer) Find(id uuid.UUID) (p kanban.Project, ok bool, err error) {
key, err := id.MarshalBinary()
if err != nil {
diff --git a/storage/lazy/lazy.go b/storage/lazy/lazy.go
@@ -49,7 +49,7 @@ func (s *Storer) Create(p kanban.Project) error {
// Save a project. Only saves to disk if changed.
func (s *Storer) Save(projects ...kanban.Project) error {
for _, p := range projects {
- old, ok, err := s.Cache.Lookup(p.Name)
+ old, ok, err := s.Cache.Find(p.ID)
if err != nil {
return err
}
@@ -60,18 +60,12 @@ func (s *Storer) Save(projects ...kanban.Project) error {
if err := s.Disk.Save(p); err != nil {
return fmt.Errorf("saving to disk: %v", err)
}
- return s.Refresh(p.Name)
+ return s.Refresh(p.ID)
}
}
return nil
}
-// Load a project by name.
-// Bool indicates whether a project exists for that name.
-func (s *Storer) Lookup(name string) (kanban.Project, bool, error) {
- return s.Cache.Lookup(name)
-}
-
// Load a project by ID.
// Bool indicates whether a project exists for that ID.
func (s *Storer) Find(id uuid.UUID) (kanban.Project, bool, error) {
@@ -84,13 +78,13 @@ func (s *Storer) List() ([]kanban.Project, error) {
}
// Refresh a project entity by loading from disk.
-func (s *Storer) Refresh(name string) error {
- p, ok, err := s.Disk.Lookup(name)
+func (s *Storer) Refresh(id uuid.UUID) error {
+ p, ok, err := s.Disk.Find(id)
if err != nil {
return fmt.Errorf("loading from disk: %v", err)
}
if !ok {
- return fmt.Errorf("project does not exist: %v", name)
+ return fmt.Errorf("project does not exist: %v", id)
}
return s.Cache.Save(p)
}
diff --git a/storage/mem/mem.go b/storage/mem/mem.go
@@ -13,29 +13,29 @@ var _ storage.Storer = (*Storer)(nil)
// Storer implements in-memory storage for Projects.
type Storer struct {
- Data map[string]kanban.Project
- Order []string
+ Data map[uuid.UUID]kanban.Project
+ Order []uuid.UUID
}
func New() *Storer {
return &Storer{
- Data: make(map[string]kanban.Project),
+ Data: make(map[uuid.UUID]kanban.Project),
}
}
func (s *Storer) Create(p kanban.Project) error {
- if _, ok := s.Data[p.Name]; ok {
+ if _, ok := s.Data[p.ID]; ok {
return fmt.Errorf("project %q exists", p.Name)
}
- s.Data[p.Name] = p
- s.Order = append(s.Order, p.Name)
+ s.Data[p.ID] = p
+ s.Order = append(s.Order, p.ID)
return nil
}
func (s *Storer) Save(projects ...kanban.Project) error {
for _, p := range projects {
- if _, ok := s.Data[p.Name]; ok {
- s.Data[p.Name] = p
+ if _, ok := s.Data[p.ID]; ok {
+ s.Data[p.ID] = p
} else {
return fmt.Errorf("project %q does not exist", p.Name)
}
@@ -43,17 +43,10 @@ func (s *Storer) Save(projects ...kanban.Project) error {
return nil
}
-func (s *Storer) Lookup(name string) (kanban.Project, bool, error) {
- if p, ok := s.Data[name]; ok {
- return p, ok, nil
- }
- return kanban.Project{}, false, nil
-}
-
func (s *Storer) Find(id uuid.UUID) (kanban.Project, bool, error) {
for _, p := range s.Data {
if p.ID == id {
- return s.Data[p.Name], true, nil
+ return s.Data[p.ID], true, nil
}
}
return kanban.Project{}, false, nil
@@ -64,8 +57,8 @@ func (s *Storer) Count() (int, error) {
}
func (s *Storer) List() (list []kanban.Project, err error) {
- for _, name := range s.Order {
- if p, ok := s.Data[name]; ok {
+ for _, id := range s.Order {
+ if p, ok := s.Data[id]; ok {
list = append(list, p)
}
}
@@ -74,7 +67,7 @@ func (s *Storer) List() (list []kanban.Project, err error) {
func (s *Storer) Load(projects []kanban.Project) error {
for ii := range projects {
- p := s.Data[projects[ii].Name]
+ p := s.Data[projects[ii].ID]
projects[ii] = p
}
return nil
diff --git a/storage/storage.go b/storage/storage.go
@@ -17,8 +17,6 @@ type Storer interface {
// Allows caller to allocate and control memory.
// Avoids copying.
Load([]kanban.Project) error
- // Lookup a Project by name.
- Lookup(name string) (kanban.Project, bool, error)
// Find a Project by ID.
Find(id uuid.UUID) (kanban.Project, bool, error)
// List all existing Projects.