Ver código fonte

Refactors create widget to edit widget, and others

Also enables viewing of issues after creation, and implements a
theoretical new constructor function for the edit widget.
arianagiroux 2 semanas atrás
pai
commit
3c4e7411c6
2 arquivos alterados com 137 adições e 61 exclusões
  1. 11 0
      issue.go
  2. 126 61
      tui.go

+ 11 - 0
issue.go

@@ -217,6 +217,17 @@ func (vf VariadicField) NewFromPath(pathOnDisk string) (v VariadicField, err err
 	return vf, err
 }
 
+func (vf VariadicField) AsString() string {
+	var output string
+	for _, field := range vf.Fields {
+		output = output + strings.TrimRight(field.Path, " \t\n") + ", "
+	}
+
+	output = strings.TrimRight(output, ", ")
+
+	return output
+}
+
 // IssueCollection Definitions ------------------------------------------------
 // ----------------------------------------------------------------------------
 

+ 126 - 61
tui.go

@@ -80,7 +80,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		}
 	case widget: // widget is initialized from m.load()
 		switch T := msg.(type) {
-		case create:
+		case edit:
 			m.widget = T
 			cmds = append(cmds, T.render, T.init())
 		default:
@@ -106,7 +106,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	// finally, pass msg to widget
 	var cmd tea.Cmd
 	switch w := m.widget.(type) {
-	case create:
+	case edit:
 		m.widget, cmd = w.update(msg)
 		cmds = append(cmds, cmd, w.render)
 	case Issue:
@@ -144,7 +144,7 @@ type widget interface {
 	keyhelp() string                  // renders key usage
 }
 
-// -------- create widget definitions -----------------------------------------
+// -------- edit widget definitions -----------------------------------------
 // ----------------------------------------------------------------------------
 // TODO(create widget) implement description field in create.create
 
@@ -163,8 +163,8 @@ type inputField struct {
 	title string
 }
 
-// struct definition for create widget
-type create struct {
+// struct definition for edit widget
+type edit struct {
 	inputFields []inputField
 	Path        string
 	selected    int
@@ -172,7 +172,7 @@ type create struct {
 }
 
 // constructor for create widget
-func initialCreateWidget(path string, placeholder string) create {
+func newEditBlank(path string, placeholder string) widget { // TODO DEPRECATE
 	spawnInput := func(f bool) textinput.Model {
 		ti := textinput.New()
 		ti.Placeholder = placeholder
@@ -206,7 +206,7 @@ func initialCreateWidget(path string, placeholder string) create {
 		}
 	}
 
-	return create{
+	return edit{
 		inputFields: inputs,
 		Path:        path,
 		selected:    0,
@@ -214,46 +214,108 @@ func initialCreateWidget(path string, placeholder string) create {
 	}
 }
 
+func newEditWidget(path string) widget {
+	// data prep
+	var e edit
+	var issue Issue
+	var err error
+
+	if IsIssue(path) { // if path is existing issue, load Issue from path
+		issue, err = Issue{}.NewFromPath(path)
+		if err != nil {
+			return e
+		}
+	} else { // if path is not existing issue, create new Issue with sensible defaults
+		issue = Issue{
+			Path: path, Title: parsePathToHuman(path),
+			Status: Field{Path: "/status", Data: "open"},
+		}
+	}
+
+	// anon function for spawning instantiated textinput.Model's
+	spawnInput := func(f bool) textinput.Model {
+		ti := textinput.New()
+		if f {
+			ti.Focus()
+		}
+		ti.CharLimit = 80
+		ti.Width = 30
+		return ti
+	}
+
+	// inputFields data prep
+	var fields []inputField
+	var input textinput.Model
+
+	// title
+	input = spawnInput(true)
+	input.SetValue(issue.Title)
+	input.Placeholder = "title"
+	fields = append(fields, inputField{input: input, title: "title"})
+
+	// status
+	input = spawnInput(false)
+	input.SetValue(issue.Status.Data)
+	input.Placeholder = "status"
+	fields = append(fields, inputField{input: input, title: "status"})
+
+	// tags
+	input = spawnInput(false)
+	input.SetValue(issue.Tags.AsString())
+	input.Placeholder = "tags, separated by comma"
+	fields = append(fields, inputField{input: input, title: "tags"})
+
+	// blockedby
+	input = spawnInput(false)
+	input.SetValue(issue.Blockedby.AsString())
+	input.Placeholder = "blockers, separated by comma"
+	fields = append(fields, inputField{input: input, title: "blockedby"})
+
+	e.inputFields = fields
+
+	return e
+}
+
 // init cmd for create widget
-func (c create) init() tea.Cmd { return textinput.Blink }
+func (e edit) init() tea.Cmd { return textinput.Blink }
 
 // update cmd for create widget
-func (c create) update(msg tea.Msg) (widget, tea.Cmd) {
+func (e edit) update(msg tea.Msg) (widget, tea.Cmd) {
 	var cmds []tea.Cmd
 	var cmd tea.Cmd
 
 	// simple anon functions to increment the selected index
 	incrementSelected := func() {
-		if c.selected < len(c.inputFields) {
-			c.selected++
-			for i := 0; i < len(c.inputFields); i++ {
-				if i == c.selected {
-					c.inputFields[i].input.Focus()
+		if e.selected < len(e.inputFields) {
+			e.selected++
+			for i := 0; i < len(e.inputFields); i++ {
+				if i == e.selected {
+					e.inputFields[i].input.Focus()
 				} else {
-					c.inputFields[i].input.Blur()
+					e.inputFields[i].input.Blur()
 				}
 			}
 		} else {
-			c.selected = 0
-			c.inputFields[c.selected].input.Focus()
+			e.selected = 0
+			e.inputFields[e.selected].input.Focus()
 		}
 	}
 
 	decrementSelected := func() {
-		if c.selected != 0 {
-			c.selected--
-			for i := 0; i < len(c.inputFields); i++ {
-				if i == c.selected {
-					c.inputFields[i].input.Focus()
+		if e.selected != 0 {
+			e.selected--
+			for i := 0; i < len(e.inputFields); i++ {
+				if i == e.selected {
+					e.inputFields[i].input.Focus()
 				} else {
-					c.inputFields[i].input.Blur()
+					e.inputFields[i].input.Blur()
 				}
 			}
 		} else {
-			for i := 0; i < len(c.inputFields); i++ {
-				c.inputFields[i].input.Blur()
+			for i := 0; i < len(e.inputFields); i++ {
+				e.inputFields[i].input.Blur()
 			}
-			c.selected = len(c.inputFields)
+			e.selected = len(e.inputFields)
 		}
 	}
 
@@ -265,19 +327,19 @@ func (c create) update(msg tea.Msg) (widget, tea.Cmd) {
 		case "shift+tab":
 			decrementSelected()
 		case "enter":
-			if c.selected == len(c.inputFields) { // confirm create
-				c.selected++
-			} else if c.selected == len(c.inputFields)+1 { // confirmed
-				cmds = append(cmds, c.create)
+			if e.selected == len(e.inputFields) { // confirm create
+				e.selected++
+			} else if e.selected == len(e.inputFields)+1 { // confirmed
+				cmds = append(cmds, e.createIssueObject)
 			} else {
 				incrementSelected()
 			}
 		case "esc": // reset
-			for i, field := range c.inputFields {
+			for i, field := range e.inputFields {
 				field.input.Reset()
 				switch field.title {
 				case "title":
-					parsed := parsePathToHuman(c.Path)
+					parsed := parsePathToHuman(e.Path)
 
 					if parsed == "." {
 						parsed = ""
@@ -287,40 +349,41 @@ func (c create) update(msg tea.Msg) (widget, tea.Cmd) {
 				case "status":
 					field.input.SetValue("open")
 				}
-				c.inputFields[i] = field
+				e.inputFields[i] = field
 			}
 		}
 	case createResult:
-		cmds = append(cmds, c.editBlankDescription(Issue(msg)))
+		cmds = append(cmds, e.editBlankDescription(Issue(msg)))
 	case editorResult:
 		if msg.err != nil {
-			c.err = msg.err
+			e.err = msg.err
 		} else {
-			cmds = append(cmds, c.write(msg.issue))
+			cmds = append(cmds, e.write(msg.issue))
 		}
 	case writeResult:
 		switch value := msg.(type) {
 		case bool:
 			if !value {
 			} else {
-				cmds = append(cmds, func() tea.Msg { return loadPath(c.Path) })
+				cmds = append(cmds, func() tea.Msg { return loadPath(e.Path) })
 			}
 		case error:
-			c.err = value
+			e.selected = -100
+			e.err = value
 		}
 	}
 
-	for i, ti := range c.inputFields {
-		c.inputFields[i].input, cmd = ti.input.Update(msg)
+	for i, ti := range e.inputFields {
+		e.inputFields[i].input, cmd = ti.input.Update(msg)
 		cmds = append(cmds, cmd)
 	}
 
 	cmds = append(cmds, cmd)
-	return c, tea.Batch(cmds...)
+	return e, tea.Batch(cmds...)
 }
 
-// A tea.Cmd to translate create.inputs to a new Issue object
-func (c create) create() tea.Msg {
+// A tea.Cmd to translate createIssueObject.inputs to a new Issue object
+func (e edit) createIssueObject() tea.Msg {
 	data := make(map[string]string)
 	commaSplit := func(t string) []string {
 		s := strings.Split(t, ",")
@@ -333,12 +396,12 @@ func (c create) create() tea.Msg {
 		return s
 	}
 
-	for _, field := range c.inputFields {
+	for _, field := range e.inputFields {
 		data[field.title] = field.input.Value()
 	}
 
 	var newIssue = Issue{
-		Path:      c.Path,
+		Path:      e.Path,
 		Tags:      VariadicField{Path: "/tags"},
 		Blockedby: VariadicField{Path: "/blockedby"},
 	}
@@ -374,7 +437,7 @@ func (c create) create() tea.Msg {
 }
 
 // Wraps a tea.Cmd function, passes an initialized Issue to WriteIssue()
-func (c create) write(issue Issue) tea.Cmd {
+func (e edit) write(issue Issue) tea.Cmd {
 	return func() tea.Msg {
 		result, err := WriteIssue(issue, false)
 		if err != nil {
@@ -388,7 +451,7 @@ func (c create) write(issue Issue) tea.Cmd {
 // and errors
 //
 // WARNING! THIS METHOD HANGS UNTIL THE USER KILLS THE EDITOR!
-func (c create) editBlankDescription(issue Issue) tea.Cmd {
+func (e edit) editBlankDescription(issue Issue) tea.Cmd {
 	data, err := EditTemplate(DescriptionTemplate, InvokeEditor)
 	var output string
 	for _, line := range data {
@@ -402,12 +465,13 @@ func (c create) editBlankDescription(issue Issue) tea.Cmd {
 	}
 }
 
-func (c create) editExistingDescription(issue Issue) tea.Cmd { return func() tea.Msg { return "" } }
+// does this just call InvokeEditor?
+func (e edit) editExistingDescription(issue Issue) tea.Cmd { return func() tea.Msg { return "" } }
 
 // render cmd for create widget
-func (c create) render() tea.Msg {
-	if c.err != nil {
-		return fmt.Sprintf("failed to create issue... %s", c.err.Error())
+func (e edit) render() tea.Msg {
+	if e.err != nil {
+		return fmt.Sprintf("failed to create issue... %s", e.err.Error())
 	}
 
 	borderStyle := lipgloss.NewStyle().
@@ -418,7 +482,7 @@ func (c create) render() tea.Msg {
 	ulStyle := lipgloss.NewStyle().Underline(true)
 
 	var output string
-	for _, field := range c.inputFields {
+	for _, field := range e.inputFields {
 		output = output + fmt.Sprintf(
 			"\n%s:%s",
 			field.title,
@@ -428,14 +492,14 @@ func (c create) render() tea.Msg {
 
 	output = strings.TrimLeft(output, "\n")
 
-	if c.selected < len(c.inputFields) {
+	if e.selected < len(e.inputFields) {
 		output = output + borderStyle.Render("press enter to submit...")
-	} else if c.selected == len(c.inputFields) {
+	} else if e.selected == len(e.inputFields) {
 		output = output + borderStyle.Render(ulStyle.Render("press enter to submit..."))
-	} else if c.selected == len(c.inputFields)+1 {
+	} else if e.selected == len(e.inputFields)+1 {
 		confirmPrompt := fmt.Sprintf(
 			"create issue titled \"%s\"?\n\n%s",
-			ulStyle.Render(c.inputFields[0].input.Value()),
+			ulStyle.Render(e.inputFields[0].input.Value()),
 			ulStyle.Render("press enter to write description..."),
 		)
 		output = output + borderStyle.Render(confirmPrompt)
@@ -445,9 +509,9 @@ func (c create) render() tea.Msg {
 }
 
 // keyhelp cmd for create widget
-func (c create) keyhelp() string {
+func (e edit) keyhelp() string {
 	var output string
-	output = output + "\ntab/shift+tab: down/up\t\tenter: input value\t\tesc: reset\t\tctrl+c: quit"
+	output = output + "\ntab/shift+tab: down/up\t\tenter: input value\t\tesc: reset\t\tctrl+e: quit"
 	return output
 }
 
@@ -503,7 +567,8 @@ func (i Issue) keyhelp() string {
 	return output
 }
 
-// -------- IssueCollection widget definitions -------------------------------- // ----------------------------------------------------------------------------
+// -------- IssueCollection widget definitions --------------------------------
+// ----------------------------------------------------------------------------
 
 type ( // Type definitions for use in tea.Msg life cycle for IssueCollection widget.
 	loadPath              string // thrown when user selects a path to load.
@@ -609,7 +674,7 @@ func (w createInCollection) update(msg tea.Msg) (widget, tea.Cmd) {
 
 func (w createInCollection) create() tea.Msg {
 	w.Path = filepath.Join(w.Path, w.name)
-	return initialCreateWidget(w.Path, "lorem ipsum")
+	return newEditBlank(w.Path, "lorem ipsum")
 }
 
 func (w createInCollection) render() tea.Msg {
@@ -644,5 +709,5 @@ func (m Model) load() tea.Msg {
 		return collection
 	}
 
-	return initialCreateWidget(m.Path, "lorem ipsum")
+	return newEditWidget(m.Path)
 }