From da04520d0747f94e66a07aef2880effab4908ded Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sun, 14 Jun 2020 21:57:28 +0100 Subject: [PATCH 01/21] first commit of new layout - just a rough idea. --- internal/gui/cavers.go | 8 ++-- internal/gui/caves.go | 6 +-- internal/gui/cud.go | 22 ++++----- internal/gui/gui.go | 93 +++++++++++++++++++------------------ internal/gui/keybindings.go | 36 ++++++++++---- internal/gui/monitoring.go | 2 +- internal/gui/navigate.go | 8 ++-- internal/gui/tabbar.go | 15 ++++++ internal/gui/trips.go | 8 ++-- 9 files changed, 117 insertions(+), 81 deletions(-) create mode 100644 internal/gui/tabbar.go diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index e4c5013..b3c1045 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -29,7 +29,7 @@ func newCavers(g *Gui) *cavers { } func (c *cavers) name() string { - return `people` + return `cavers` } func (c *cavers) setKeybinding(g *Gui) { @@ -39,8 +39,8 @@ func (c *cavers) setKeybinding(g *Gui) { switch event.Key() { case tcell.KeyEnter: g.inspectPerson() - case tcell.KeyTAB: - g.switchPanel(`menu`) + case tcell.KeyCtrlR: + c.setEntries(g) } switch event.Rune() { @@ -107,7 +107,7 @@ func (c *cavers) entries(g *Gui) { return } - g.state.resources.people = cavers + g.state.resources.people = cavers } func (c *cavers) focus(g *Gui) { diff --git a/internal/gui/caves.go b/internal/gui/caves.go index 637f043..40f7f85 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -29,7 +29,7 @@ func newCaves(g *Gui) *caves { } func (c *caves) name() string { - return `locations` + return `caves` } func (c *caves) setKeybinding(g *Gui) { @@ -39,8 +39,8 @@ func (c *caves) setKeybinding(g *Gui) { switch event.Key() { case tcell.KeyEnter: g.inspectCave() - case tcell.KeyTAB: - g.switchPanel(`menu`) + case tcell.KeyCtrlR: + c.setEntries(g) } switch event.Rune() { diff --git a/internal/gui/cud.go b/internal/gui/cud.go index 9ee1159..4b981cf 100644 --- a/internal/gui/cud.go +++ b/internal/gui/cud.go @@ -116,10 +116,10 @@ func (g *Gui) createLocationForm() { g.createLocation(form) }). AddButton("Cancel", func() { - g.closeAndSwitchPanel("form", "locations") + g.closeAndSwitchPanel("form", "caves") }) - g.pages.AddAndSwitchToPage("form", g.modal(form, 80, 29), true)//.ShowPage("main") + g.pages.AddAndSwitchToPage("form", g.modal(form, 80, 29), true) //REVIEW: main or trips ? ^^ } @@ -135,7 +135,7 @@ func (g *Gui) createLocation(form *tview.Form) { return } - g.closeAndSwitchPanel(`form`, `locations`) + g.closeAndSwitchPanel(`form`, `caves`) g.app.QueueUpdateDraw(func() { g.locationsPanel().setEntries(g) }) @@ -162,7 +162,7 @@ func (g *Gui) createPersonForm() { } return - }) + }) form. AddInputField("Name", "", inputWidth, nil, nil). @@ -171,7 +171,7 @@ func (g *Gui) createPersonForm() { g.createPerson(form) }). AddButton("Cancel", func() { - g.closeAndSwitchPanel("form", "people") + g.closeAndSwitchPanel("form", "cavers") // FIXME }) g.pages.AddAndSwitchToPage("form", g.modal(form, 80, 29), true) @@ -188,7 +188,7 @@ func (g *Gui) createPerson(form *tview.Form) { return } - g.closeAndSwitchPanel(`form`, `people`) + g.closeAndSwitchPanel(`form`, `cavers`) g.app.QueueUpdateDraw(func() { g.peoplePanel().setEntries(g) }) @@ -289,7 +289,7 @@ func (g *Gui) modifyPersonForm() { } return - }) + }) form. AddInputField("Name", selectedPerson.Name, inputWidth, nil, nil). @@ -298,7 +298,7 @@ func (g *Gui) modifyPersonForm() { g.modifyPerson(selectedPerson.ID, form) }). AddButton("Cancel", func() { - g.closeAndSwitchPanel("form", "people") + g.closeAndSwitchPanel("form", "cavers") }) g.pages.AddAndSwitchToPage("form", g.modal(form, 80, 29), true) @@ -315,7 +315,7 @@ func (g *Gui) modifyPerson(id string, form *tview.Form) { return } - g.closeAndSwitchPanel(`form`, `people`) + g.closeAndSwitchPanel(`form`, `cavers`) g.app.QueueUpdateDraw(func() { g.peoplePanel().updateEntries(g)//setEntries(g) }) @@ -376,7 +376,7 @@ func (g *Gui) modifyLocationForm() { g.modifyLocation(selectedLocation.ID, form) }). AddButton("Cancel", func() { - g.closeAndSwitchPanel("form", "locations") + g.closeAndSwitchPanel("form", "caves") }) g.pages.AddAndSwitchToPage("form", g.modal(form, 80, 29), true) @@ -395,7 +395,7 @@ func (g *Gui) modifyLocation(id string, form *tview.Form) { return } - g.closeAndSwitchPanel(`form`, `locations`) + g.closeAndSwitchPanel(`form`, `caves`) g.app.QueueUpdateDraw(func() { g.locationsPanel().setEntries(g) }) diff --git a/internal/gui/gui.go b/internal/gui/gui.go index a22f52e..91ab4db 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -1,8 +1,9 @@ package gui import ( - //"context" - //"fmt" + "fmt" + "strconv" + "strings" "github.com/rivo/tview" @@ -19,15 +20,16 @@ type resources struct { trips []*model.Log people []*model.Caver locations []*model.Cave - statsLocations []*model.Statistic - statsPeople []*model.Statistic - timeWindow []*model.Statistic + //statsLocations []*model.Statistic + //statsPeople []*model.Statistic + //timeWindow []*model.Statistic menu []string } type state struct { panels panels - navigate *navigate + navigate *navigate + tabBar *tview.TextView resources resources stopChans map[string]chan int } @@ -43,8 +45,8 @@ type Gui struct { pages *tview.Pages state *state db *db.Database - statsLocations *statsLocations - statsPeople *statsPeople + //statsLocations *statsLocations + //statsPeople *statsPeople } func New(db *db.Database) *Gui { @@ -86,7 +88,7 @@ func (g *Gui) tripsPanel() *trips { func (g *Gui) locationsPanel() *caves { for _, panel := range g.state.panels.panel { - if panel.name() == `locations` { + if panel.name() == `caves` { return panel.(*caves) } } @@ -95,7 +97,7 @@ func (g *Gui) locationsPanel() *caves { func (g *Gui) peoplePanel() *cavers { for _, panel := range g.state.panels.panel { - if panel.name() == `people` { + if panel.name() == `cavers` { return panel.(*cavers) } } @@ -111,7 +113,7 @@ func (g *Gui) inspectorPanel() *inspector { return nil } -func (g *Gui) statsLocationsPanel() *statsLocations { +/*func (g *Gui) statsLocationsPanel() *statsLocations { for _, panel := range g.state.panels.panel { if panel.name() == `statsLocations` { return panel.(*statsLocations) @@ -127,57 +129,58 @@ func (g *Gui) statsPeoplePanel() *statsPeople { } } return nil -} +}*/ func (g *Gui) initPanels() { + + g.state.tabBar = newTabBar(g) + // Page definitions trips := newTrips(g) cavers := newCavers(g) caves := newCaves(g) + /* + // NOTE: I would really like to get this working as it would be far neater. The issue is with the three pages being of different types. + // cannot use pg (type panel) as type tview.Primitive in argument to g.pages.AddPage: + // panel does not implement tview.Primitive (missing Blur method) + for idx, pg := range []panel{trips, cavers, caves} { + name := pg.name() + g.pages.AddPage(name, pg, true, idx == 0) + fmt.Fprintf(g.state.tabBar, ` %d ["%d"][darkcyan]%s[white][""] `, idx+1, idx, strings.Title(name)) + } + g.state.tabBar.Highlight("0") + */ + // Add pages to the "book" g.pages.AddPage(`trips`, trips, true, true) - g.pages.AddPage(`people`, cavers, true, true) - g.pages.AddPage(`locations`, caves, true, true) - + fmt.Fprintf(g.state.tabBar, ` %d ["%d"][darkcyan]%s[white][""] `, 1, 0, strings.Title(trips.name())) + g.pages.AddPage(`cavers`, cavers, true, true) + fmt.Fprintf(g.state.tabBar, ` %d ["%d"][darkcyan]%s[white][""] `, 2, 1, strings.Title(cavers.name())) + g.pages.AddPage(`caves`, caves, true, true) + fmt.Fprintf(g.state.tabBar, ` %d ["%d"][darkcyan]%s[white][""] `, 3, 2, strings.Title(caves.name())) + + g.state.tabBar.Highlight("0") + // Panels - menu := newMenu(g) - statsPeople := newStatsPeople(g) - statsLocations := newStatsLocations(g) - timeWindow := newTimeWindow(g) inspector := newInspector(g) - navigate := newNavigate() + statusBar := newNavigate() g.state.panels.panel = append(g.state.panels.panel, trips) g.state.panels.panel = append(g.state.panels.panel, cavers) g.state.panels.panel = append(g.state.panels.panel, caves) - g.state.panels.panel = append(g.state.panels.panel, menu) - g.state.panels.panel = append(g.state.panels.panel, statsPeople) - g.state.panels.panel = append(g.state.panels.panel, statsLocations) - g.state.panels.panel = append(g.state.panels.panel, timeWindow) + g.state.panels.panel = append(g.state.panels.panel, inspector) - g.state.navigate = navigate + g.state.navigate = statusBar // Arange the windows / tiles - layout := tview.NewFlex().SetDirection(tview.FlexColumn). - AddItem(tview.NewFlex(). - SetDirection(tview.FlexRow). - AddItem(menu, 0, 5, false). - AddItem(statsPeople, 0, 20, false). - AddItem(statsLocations, 0, 20, false). - AddItem(timeWindow, 0, 2, false), - 0, 1, false). - AddItem(tview.NewFlex(). - SetDirection(tview.FlexRow). - AddItem(g.pages, 0, 16, true). - AddItem(inspector, 0, 8, false). - AddItem(navigate, 0, 1, false), - 0, 6, true) - - g.statsPeople = statsPeople - g.statsLocations = statsLocations + layout := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(g.state.tabBar, 0, 1, false). + AddItem(g.pages, 0, 16, true). + AddItem(inspector, 0, 7, false). + AddItem(statusBar, 0, 1, false) g.app.SetRoot(layout, true) g.goTo(`trips`) @@ -194,6 +197,7 @@ func (g *Gui) switchPanel(panelName string) { g.state.navigate.update(panelName) panel.focus(g) g.state.panels.currentPanel = i + g.state.tabBar.Highlight(strconv.Itoa(i)).ScrollToHighlight() } else { panel.unfocus() } @@ -204,9 +208,9 @@ func (g *Gui) closeAndSwitchPanel(removePanel, switchTo string) { g.pages.RemovePage(removePanel).ShowPage("main") num := 0 switch switchTo { - case `people`: + case `cavers`: num = 1 - case `locations`: + case `caves`: num = 2 default: num = 0 @@ -274,4 +278,3 @@ func (g *Gui) selectedPerson() *model.Caver { return g.state.resources.people[row-1] } - diff --git a/internal/gui/keybindings.go b/internal/gui/keybindings.go index 5327ae5..2f0af53 100644 --- a/internal/gui/keybindings.go +++ b/internal/gui/keybindings.go @@ -8,15 +8,26 @@ import ( ) var inspectorFormat = map[string]string{ - `trips` : "Date: %s\nCave: %s\nCavers: %s\nNotes: %s", - `people` : "Name: %s\nClub: %s\nCount: %d", - `locations`: "Name: %s\nRegion: %s\nCountry: %s\nSRT: %v\nVisits: %d", + `trips` : "Date: %s\nCave: %s\nCavers: %s\nNotes: %s", + `cavers`: "Name: %s\nClub: %s\nCount: %d", + `caves` : "Name: %s\nRegion: %s\nCountry: %s\nSRT: %v\nVisits: %d", } func (g *Gui) setGlobalKeybinding(event *tcell.EventKey) { + /*switch event.Key() { + case tcell.KeyTAB: + g.nextPage() + }*/ + switch event.Rune() { case 'q': g.Stop() + case '1': + g.goTo("trips") + case '2': + g.goTo("cavers") + case '3': + g.goTo("caves") } } @@ -65,11 +76,11 @@ func (g *Gui) formatTrip(trip *model.Log) string { } func (g *Gui) formatCave(l *model.Cave) string { - return fmt.Sprintf(inspectorFormat[`locations`], l.Name, l.Region, l.Country, l.SRT, l.Visits) + return fmt.Sprintf(inspectorFormat[`caves`], l.Name, l.Region, l.Country, l.SRT, l.Visits) } func (g *Gui) formatPerson(p *model.Caver) string { - return fmt.Sprintf(inspectorFormat[`people`], p.Name, p.Club, p.Count) + return fmt.Sprintf(inspectorFormat[`cavers`], p.Name, p.Club, p.Count) } // @@ -82,9 +93,18 @@ func (g *Gui) selectPage(row, col int) string { case 0: p = `trips` case 1: - p = `people` + p = `cavers` case 2: - p = `locations` + p = `caves` } return p -} \ No newline at end of file +} + +/* +func (g *Gui) nextPage() { + slide, _ := strconv.Atoi(g.state.tabBar.GetHighlights()[0]) + slide = (slide + 1) % g.pages.GetPageCount() + //g.state.tabBar.Highlight(strconv.Itoa(slide)).ScrollToHighlight() + g.goTo(g.selectPage(slide - 1, 0)) // NOTE: If the Highlight func is fixed for the tab bar then this line will not be required +} +*/ diff --git a/internal/gui/monitoring.go b/internal/gui/monitoring.go index e0abcd5..75d4856 100644 --- a/internal/gui/monitoring.go +++ b/internal/gui/monitoring.go @@ -18,6 +18,6 @@ func (g *Gui) stopMonitoring() { func (g *Gui) updateTask() { g.app.QueueUpdateDraw(func() { - g.peoplePanel().setEntries(g) + g.peoplePanel().setEntries(g) // REVIEW: Why is this just people ? }) } diff --git a/internal/gui/navigate.go b/internal/gui/navigate.go index b80d371..fa198e0 100644 --- a/internal/gui/navigate.go +++ b/internal/gui/navigate.go @@ -14,13 +14,13 @@ func newNavigate() *navigate { navi := &navigate{ TextView: tview.NewTextView().SetTextColor(tcell.ColorWhite), keybindings: map[string]string{ - "trips": " n: New Log Entry, m: Modify Log, d: Remove Log, /: filter, Enter: Inspect ", - "locations": " n: New Cave, m: Modify Cave, d: Remove Cave, /: filter, Enter: Inspect ", - "people": " n: New Caver, m: Modify Caver, d: Remove Caver, /: filter, Enter: Inspect ", + "trips" : " n: New Log Entry, m: Modify Log, d: Remove Log, /: filter, Enter: Inspect ", + "caves" : " n: New Cave, m: Modify Cave, d: Remove Cave, /: filter, Enter: Inspect ", + "cavers": " n: New Caver, m: Modify Caver, d: Remove Caver, /: filter, Enter: Inspect ", }, } - navi.SetBorder(true) + navi.SetBorder(false) return navi } diff --git a/internal/gui/tabbar.go b/internal/gui/tabbar.go new file mode 100644 index 0000000..0f2c644 --- /dev/null +++ b/internal/gui/tabbar.go @@ -0,0 +1,15 @@ +package gui + +import ( + "github.com/rivo/tview" +) + +func newTabBar(g *Gui) *tview.TextView { + return tview.NewTextView(). + SetDynamicColors(true). + SetRegions(true). + SetWrap(false)/*. + SetHighlightedFunc(func(added, removed, remaining []string) { + g.pages.SwitchToPage(added[0]) + })*/ +} diff --git a/internal/gui/trips.go b/internal/gui/trips.go index b114110..0c81179 100644 --- a/internal/gui/trips.go +++ b/internal/gui/trips.go @@ -39,10 +39,8 @@ func (t *trips) setKeybinding(g *Gui) { switch event.Key() { case tcell.KeyEnter: g.inspectTrip() - //case tcell.KeyCtrlR: - // t.setEntries(g) - case tcell.KeyTAB: - g.switchPanel(`menu`) + case tcell.KeyCtrlR: + t.setEntries(g) } switch event.Rune() { @@ -64,7 +62,7 @@ func (t *trips) entries(g *Gui) { return } - g.state.resources.trips = trips + g.state.resources.trips = trips } func (t *trips) setEntries(g *Gui) { From 1008a0f91c873aec30d4fa38af72dbba24fba6ec Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sat, 20 Jun 2020 13:35:11 +0100 Subject: [PATCH 02/21] further proofing of the new UI --- cmd/main.go | 2 +- internal/gui/gui.go | 33 ++++++---------- internal/gui/inspector.go | 8 ++-- internal/gui/menu.go | 71 --------------------------------- internal/gui/statsLocations.go | 71 --------------------------------- internal/gui/statsPeople.go | 72 ---------------------------------- internal/gui/time.go | 32 +++++++-------- 7 files changed, 32 insertions(+), 257 deletions(-) delete mode 100644 internal/gui/menu.go delete mode 100644 internal/gui/statsLocations.go delete mode 100644 internal/gui/statsPeople.go diff --git a/cmd/main.go b/cmd/main.go index 4e4ae29..d9546ca 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -48,7 +48,7 @@ func main() { // Read config file - cfg := func (_yamlFile string) *model.Config { + cfg := func(_yamlFile string) *model.Config { var _cfg model.Config if _yamlFile == `` { diff --git a/internal/gui/gui.go b/internal/gui/gui.go index 91ab4db..cbc3ac4 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -21,9 +21,6 @@ type resources struct { people []*model.Caver locations []*model.Cave //statsLocations []*model.Statistic - //statsPeople []*model.Statistic - //timeWindow []*model.Statistic - menu []string } type state struct { @@ -46,7 +43,6 @@ type Gui struct { state *state db *db.Database //statsLocations *statsLocations - //statsPeople *statsPeople } func New(db *db.Database) *Gui { @@ -121,15 +117,7 @@ func (g *Gui) inspectorPanel() *inspector { } return nil } - -func (g *Gui) statsPeoplePanel() *statsPeople { - for _, panel := range g.state.panels.panel { - if panel.name() == `statsPeople` { - return panel.(*statsPeople) - } - } - return nil -}*/ +*/ func (g *Gui) initPanels() { @@ -142,7 +130,8 @@ func (g *Gui) initPanels() { caves := newCaves(g) /* - // NOTE: I would really like to get this working as it would be far neater. The issue is with the three pages being of different types. + // NOTE: I would really like to get this working as it would be far neater. + // The issue is with the three pages being of different types. // cannot use pg (type panel) as type tview.Primitive in argument to g.pages.AddPage: // panel does not implement tview.Primitive (missing Blur method) for idx, pg := range []panel{trips, cavers, caves} { @@ -152,14 +141,14 @@ func (g *Gui) initPanels() { } g.state.tabBar.Highlight("0") */ - + // Add pages to the "book" g.pages.AddPage(`trips`, trips, true, true) - fmt.Fprintf(g.state.tabBar, ` %d ["%d"][darkcyan]%s[white][""] `, 1, 0, strings.Title(trips.name())) + fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[white][""] `, 0, 1, strings.Title(trips.name())) g.pages.AddPage(`cavers`, cavers, true, true) - fmt.Fprintf(g.state.tabBar, ` %d ["%d"][darkcyan]%s[white][""] `, 2, 1, strings.Title(cavers.name())) + fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[white][""] `, 1, 2, strings.Title(cavers.name())) g.pages.AddPage(`caves`, caves, true, true) - fmt.Fprintf(g.state.tabBar, ` %d ["%d"][darkcyan]%s[white][""] `, 3, 2, strings.Title(caves.name())) + fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[white][""] `, 2, 3, strings.Title(caves.name())) g.state.tabBar.Highlight("0") @@ -177,10 +166,10 @@ func (g *Gui) initPanels() { // Arange the windows / tiles layout := tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(g.state.tabBar, 0, 1, false). + AddItem(g.state.tabBar, 1, 1, false). AddItem(g.pages, 0, 16, true). AddItem(inspector, 0, 7, false). - AddItem(statusBar, 0, 1, false) + AddItem(statusBar, 1, 1, false) g.app.SetRoot(layout, true) g.goTo(`trips`) @@ -243,6 +232,10 @@ func (g *Gui) warning(message, page string, labels []string, doneFunc func()) { g.pages.AddAndSwitchToPage("modal", g.modal(modal, 80, 29), true) } + +// +// Functions for returning the selected item in the table +// REVIEW: There might be better ways of doing this. func (g *Gui) selectedTrip() *model.Log { row, _ := g.tripsPanel().GetSelection() if len(g.state.resources.trips) == 0 { diff --git a/internal/gui/inspector.go b/internal/gui/inspector.go index 3fd9351..2610187 100644 --- a/internal/gui/inspector.go +++ b/internal/gui/inspector.go @@ -34,9 +34,9 @@ func (i *inspector) setEntries(g *Gui) {} func (i *inspector) setInitEntry() { i.SetText(` - __ __ __ __ - | ||_ | / / \|\/||_ - |/\||__|__\__\__/| ||__ `) + __ __ __ __ + | ||_ | / / \|\/||_ + |/\||__|__\__\__/| ||__ `) } func (i *inspector) entries(g *Gui) {} @@ -50,4 +50,4 @@ func (i *inspector) focus(g *Gui) { func (i *inspector) unfocus() { } -func (i *inspector) setFilterWord(word string) {} \ No newline at end of file +func (i *inspector) setFilterWord(word string) {} diff --git a/internal/gui/menu.go b/internal/gui/menu.go deleted file mode 100644 index 55304cc..0000000 --- a/internal/gui/menu.go +++ /dev/null @@ -1,71 +0,0 @@ -package gui - -import ( - "github.com/gdamore/tcell" - "github.com/rivo/tview" -) - -type menu struct { - *tview.Table -} - -func newMenu(g *Gui) (m *menu) { - m = &menu{ - Table: tview.NewTable().SetSelectable(true, false).Select(0,0).SetFixed(1,1), - } - - m.SetTitle(` Menu `).SetTitleAlign(tview.AlignLeft) - m.SetBorder(true) - m.setEntries(g) - m.setKeybinding(g) - return -} - -func (m *menu) name() string { - return `menu` -} - -func (m *menu) setEntries(g *Gui) { - m.entries(g) - table := m.Clear() - - for i, option := range g.state.resources.menu { - table.SetCell(i, 0, tview.NewTableCell(option). - SetTextColor(tcell.ColorWhite). - SetMaxWidth(30). - SetExpansion(1)) - } -} - -func (m *menu) setKeybinding(g *Gui) { - m.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - g.setGlobalKeybinding(event) - - switch event.Key() { - case tcell.KeyEnter: - g.goTo(g.selectPage(m.GetSelection())) - case tcell.KeyTAB: - g.goTo(g.selectPage(m.GetSelection())) - } - - return event - }) -} - -func (m *menu) entries(g *Gui) { - options := []string{`Trips`, `People`, `Locations`} - g.state.resources.menu = options -} - -func (m *menu) updateEntries(g *Gui) {} - -func (m *menu) focus(g *Gui) { - m.SetSelectable(true, false) - g.app.SetFocus(m) -} - -func (m *menu) unfocus() { - m.SetSelectable(false, false) -} - -func (m *menu) setFilterWord(word string) {} diff --git a/internal/gui/statsLocations.go b/internal/gui/statsLocations.go deleted file mode 100644 index 3ef6708..0000000 --- a/internal/gui/statsLocations.go +++ /dev/null @@ -1,71 +0,0 @@ -package gui - -import ( - "github.com/gdamore/tcell" - "github.com/rivo/tview" -) - -type statsLocations struct { - *tview.Table -} - -func newStatsLocations(g *Gui) (s *statsLocations) { - s = &statsLocations{ - Table: tview.NewTable().SetSelectable(false, false).SetFixed(3,1), - } - - s.SetTitle(` Top Caves `).SetTitleAlign(tview.AlignLeft) - s.SetBorder(true) - s.setEntries(g) - s.setKeybinding(g) - return -} - -func (s *statsLocations) name() string { - return `statsLocations` -} - -func (s *statsLocations) setEntries(g *Gui) { - s.entries(g) - table := s.Clear() - - for i, stat := range g.state.resources.statsLocations { - table.SetCell(i, 0, tview.NewTableCell(stat.Name). - SetTextColor(tcell.ColorWhite). - SetMaxWidth(30). - SetExpansion(2)) - - table.SetCell(i, 1, tview.NewTableCell(stat.Value). - SetTextColor(tcell.ColorWhite). - SetMaxWidth(30). - SetExpansion(1)) - } -} - -func (s *statsLocations) setKeybinding(g *Gui) { - s.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - g.setGlobalKeybinding(event) - return event - }) -} - -func (s *statsLocations) entries(g *Gui) { - stats, err := g.db.GetTopLocations() - if err != nil { - return - } - g.state.resources.statsLocations = stats -} - -func (s *statsLocations) updateEntries(g *Gui) {} - -func (s *statsLocations) focus(g *Gui) { - s.SetSelectable(true, false) - g.app.SetFocus(s) -} - -func (s *statsLocations) unfocus() { - s.SetSelectable(false, false) -} - -func (s *statsLocations) setFilterWord(word string) {} diff --git a/internal/gui/statsPeople.go b/internal/gui/statsPeople.go deleted file mode 100644 index 7beadd8..0000000 --- a/internal/gui/statsPeople.go +++ /dev/null @@ -1,72 +0,0 @@ -package gui - -import ( - "github.com/gdamore/tcell" - "github.com/rivo/tview" -) - -type statsPeople struct { - *tview.Table -} - -func newStatsPeople(g *Gui) (s *statsPeople) { - s = &statsPeople{ - Table: tview.NewTable().SetSelectable(false, false).SetFixed(3,1), - } - - s.SetTitle(` Top Cavers `).SetTitleAlign(tview.AlignLeft) - s.SetBorder(true) - s.setEntries(g) - s.setKeybinding(g) - return -} - -func (s *statsPeople) name() string { - return `statsPeople` -} - -func (s *statsPeople) setEntries(g *Gui) { - s.entries(g) - table := s.Clear() - - for i, stat := range g.state.resources.statsPeople { - table.SetCell(i, 0, tview.NewTableCell(stat.Name). - SetTextColor(tcell.ColorWhite). - SetMaxWidth(30). - SetExpansion(2)) - - table.SetCell(i, 1, tview.NewTableCell(stat.Value). - SetTextColor(tcell.ColorWhite). - SetMaxWidth(30). - SetExpansion(1)) - } -} - -func (s *statsPeople) setKeybinding(g *Gui) { - s.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - g.setGlobalKeybinding(event) - - return event - }) -} - -func (s *statsPeople) entries(g *Gui) { - stats, err := g.db.GetTopPeople() - if err != nil { - return - } - g.state.resources.statsPeople = stats -} - -func (s *statsPeople) updateEntries(g *Gui) {} - -func (s *statsPeople) focus(g *Gui) { - s.SetSelectable(true, false) - g.app.SetFocus(s) -} - -func (s *statsPeople) unfocus() { - s.SetSelectable(false, false) -} - -func (s *statsPeople) setFilterWord(word string) {} diff --git a/internal/gui/time.go b/internal/gui/time.go index 3a4f8c7..b5117a6 100644 --- a/internal/gui/time.go +++ b/internal/gui/time.go @@ -5,8 +5,6 @@ import ( "github.com/gdamore/tcell" "github.com/rivo/tview" - - "github.com/idlephysicist/cave-logger/internal/model" ) type timeWindow struct { @@ -18,9 +16,9 @@ func newTimeWindow(g *Gui) (t *timeWindow) { Table: tview.NewTable().SetSelectable(false, false).SetFixed(3,1), } - t.SetBorder(true) - t.setEntries(g) - t.setKeybinding(g) + t.SetBorder(false) + t.setEntries() + //t.setKeybinding(g) return } @@ -28,24 +26,21 @@ func (t *timeWindow) name() string { return `timeWindow` } -func (t *timeWindow) setEntries(g *Gui) { - t.entries(g) +func (t *timeWindow) setEntries() { table := t.Clear() - for i, stat := range g.state.resources.timeWindow { - table.SetCell(i, 0, tview.NewTableCell(stat.Name). - SetTextColor(tcell.ColorWhite). - SetMaxWidth(30). - SetExpansion(2)) + table.SetCell(0, 0, tview.NewTableCell(`Today`). + SetTextColor(tcell.ColorWhite). + SetMaxWidth(30). + SetExpansion(2)) - table.SetCell(i, 1, tview.NewTableCell(stat.Value). - SetTextColor(tcell.ColorWhite). - SetMaxWidth(30). - SetExpansion(1)) - } + table.SetCell(0, 1, tview.NewTableCell(time.Now().Format(`2006-01-02`)). + SetTextColor(tcell.ColorWhite). + SetMaxWidth(30). + SetExpansion(1)) } -func (t *timeWindow) setKeybinding(g *Gui) { +/*func (t *timeWindow) setKeybinding(g *Gui) { t.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { g.setGlobalKeybinding(event) return event @@ -73,3 +68,4 @@ func (t *timeWindow) unfocus() { } func (t *timeWindow) setFilterWord(word string) {} +*/ From e5b4d6d22a1a6b29d9c2ba17f28e24b9d88c6f6f Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sat, 20 Jun 2020 14:29:11 +0100 Subject: [PATCH 03/21] Removed yaml dep. from go and python code Also ran go mod tidy & moved python scripts to py3 --- cmd/main.go | 14 +++++++------- go.mod | 1 - scripts/csv2sqlite.py | 6 +++--- scripts/make-db.py | 20 ++++++-------------- scripts/move-configs.py | 10 +++++----- 5 files changed, 21 insertions(+), 30 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 4e4ae29..99918ae 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,8 +1,8 @@ package main import ( + "encoding/json" "fmt" - "gopkg.in/yaml.v2" "io/ioutil" "os" "strings" @@ -48,21 +48,21 @@ func main() { // Read config file - cfg := func (_yamlFile string) *model.Config { + cfg := func(_file string) *model.Config { var _cfg model.Config - if _yamlFile == `` { - _yamlFile = fmt.Sprintf("%s/.config/cave-logger/config.yml", os.Getenv("HOME")) + if _file == `` { + _file = fmt.Sprintf("%s/.config/cave-logger/config.json", os.Getenv("HOME")) } - yamlFile, err := ioutil.ReadFile(_yamlFile) + file, err := ioutil.ReadFile(_file) if err != nil { log.Fatalf("main.readfile: %v", err) } - err = yaml.Unmarshal(yamlFile, &_cfg) + err = json.Unmarshal(file, &_cfg) if err != nil { - log.Fatalf("main.unmarshalYAML: %v", err) + log.Fatalf("main.unmarshal: %v", err) } return &_cfg diff --git a/go.mod b/go.mod index 78cd834..753e990 100644 --- a/go.mod +++ b/go.mod @@ -14,5 +14,4 @@ require ( golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 // indirect golang.org/x/text v0.3.2 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect - gopkg.in/yaml.v2 v2.2.2 ) diff --git a/scripts/csv2sqlite.py b/scripts/csv2sqlite.py index 9e094b7..cd9d0e5 100755 --- a/scripts/csv2sqlite.py +++ b/scripts/csv2sqlite.py @@ -4,7 +4,7 @@ import json import subprocess import sqlite3 -import yaml +import json import os import sys @@ -15,14 +15,14 @@ CSV = sys.argv[1] INITSCRIPT = "./scripts/make-db.py" -CFG = "./config/config.yml" +CFG = "./config/config.json" class App(object): def __init__(self): out = subprocess.run(INITSCRIPT, capture_output=True) with open(CFG, 'r') as c: - self.config = yaml.safe_load(c) + self.config = json.load(c) self.conn = sqlite3.connect( self.config['database']['filename'], diff --git a/scripts/make-db.py b/scripts/make-db.py index 7cf4d34..62e6f81 100755 --- a/scripts/make-db.py +++ b/scripts/make-db.py @@ -1,23 +1,15 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from datetime import datetime import os import sys import sqlite3 import uuid -import yaml +import json NOW = datetime.now().strftime("%Y-%m-%dT%H_%M") HOME = os.environ["HOME"] NEWPATH = "{}/.config/cave-logger".format(HOME) -if sys.version_info < (3,): - print(""" -Why are you not using Python 3 by now? -Even I am... -Alas this script will still try to run.\n""" - ) - - try: os.makedirs(NEWPATH, 0o755) except FileExistsError: @@ -32,12 +24,12 @@ for f in files: if '.db' in f: db = f - elif 'config.yml' == f: + elif 'config.json' == f: cfg = f with open(cfg, 'r') as f: try: - fn = yaml.safe_load(f)['database']['filename'] + fn = json.load(f)['database']['filename'] if db in fn: print("Found pre-existing configs aborting...") sys.exit() @@ -95,7 +87,7 @@ print("Created {} database tables".format(len(tables))) conn.close() -CONFIG_FN = '{}/config.yml'.format(NEWPATH) +CONFIG_FN = '{}/config.json'.format(NEWPATH) with open(CONFIG_FN, 'w') as c: config = { 'database': { @@ -103,5 +95,5 @@ 'created' : NOW } } - yaml.dump(config, c) + json.dump(config, c) print("Wrote database name to config file") diff --git a/scripts/move-configs.py b/scripts/move-configs.py index 9d7d22d..242afac 100755 --- a/scripts/move-configs.py +++ b/scripts/move-configs.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 -import yaml +import json import os import shutil HOME = os.environ["HOME"] -CFGFILE = "./config/config.yml" +CFGFILE = "./config/config.json" NEWPATH = ".config/cave-logger" @@ -15,7 +15,7 @@ print("Directory exists moving on") with open(CFGFILE, 'r') as c: - cfg = yaml.safe_load(c) + cfg = json.load(c) shutil.copy( '/'.join([HOME,cfg['database']['filename']]), @@ -25,5 +25,5 @@ cfg['database']['filename'] = f"{HOME}/{NEWPATH}/{cfg['database']['filename'].split('/')[-1]}" -with open(f"{HOME}/{NEWPATH}/config.yml", 'w') as c: - yaml.dump(cfg, c) +with open(f"{HOME}/{NEWPATH}/config.json", 'w') as c: + json.dump(cfg, c) From bf362872b06da0c1d66d556c0f84c24255e4a266 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sat, 20 Jun 2020 14:33:30 +0100 Subject: [PATCH 04/21] =?UTF-8?q?Tiny=20change=20to=20hopefully=20?= =?UTF-8?q?=F0=9F=A4=9E=20avoid=20a=20merge=20conflict.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/main.go b/cmd/main.go index d9546ca..4e4ae29 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -48,7 +48,7 @@ func main() { // Read config file - cfg := func(_yamlFile string) *model.Config { + cfg := func (_yamlFile string) *model.Config { var _cfg model.Config if _yamlFile == `` { From 35d7c5bbbe0a6553a376c7902ae04ea7600ea1c8 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sat, 20 Jun 2020 17:52:41 +0100 Subject: [PATCH 05/21] Tidy up following review of PR --- internal/gui/cud.go | 35 ++++++------------- internal/gui/gui.go | 8 ++--- internal/gui/monitoring.go | 10 +++--- internal/gui/time.go | 71 -------------------------------------- 4 files changed, 19 insertions(+), 105 deletions(-) delete mode 100644 internal/gui/time.go diff --git a/internal/gui/cud.go b/internal/gui/cud.go index 4b981cf..a61f42e 100644 --- a/internal/gui/cud.go +++ b/internal/gui/cud.go @@ -47,8 +47,7 @@ func (g *Gui) createTripForm() { g.closeAndSwitchPanel("form", "trips") }) - g.pages.AddAndSwitchToPage("form", g.modal(form, 80, 29), true)//.ShowPage("main") - //REVIEW: main or trips ? ^^ + g.pages.AddAndSwitchToPage("form", g.modal(form, 80, 29), true) } func (g *Gui) createTrip(form *tview.Form) { @@ -64,9 +63,7 @@ func (g *Gui) createTrip(form *tview.Form) { } g.closeAndSwitchPanel(`form`, `trips`) - g.app.QueueUpdateDraw(func() { - g.tripsPanel().setEntries(g) - }) + g.tripsPanel().updateEntries(g) } func (g *Gui) createLocationForm() { @@ -120,7 +117,6 @@ func (g *Gui) createLocationForm() { }) g.pages.AddAndSwitchToPage("form", g.modal(form, 80, 29), true) - //REVIEW: main or trips ? ^^ } func (g *Gui) createLocation(form *tview.Form) { @@ -136,9 +132,7 @@ func (g *Gui) createLocation(form *tview.Form) { } g.closeAndSwitchPanel(`form`, `caves`) - g.app.QueueUpdateDraw(func() { - g.locationsPanel().setEntries(g) - }) + g.cavesPanel().updateEntries(g) } func (g *Gui) createPersonForm() { @@ -171,11 +165,10 @@ func (g *Gui) createPersonForm() { g.createPerson(form) }). AddButton("Cancel", func() { - g.closeAndSwitchPanel("form", "cavers") // FIXME + g.closeAndSwitchPanel("form", "cavers") }) g.pages.AddAndSwitchToPage("form", g.modal(form, 80, 29), true) - //REVIEW: main or trips ? ^^ } func (g *Gui) createPerson(form *tview.Form) { @@ -189,9 +182,7 @@ func (g *Gui) createPerson(form *tview.Form) { } g.closeAndSwitchPanel(`form`, `cavers`) - g.app.QueueUpdateDraw(func() { - g.peoplePanel().setEntries(g) - }) + g.caversPanel().updateEntries(g) } // @@ -257,9 +248,7 @@ func (g *Gui) modifyTrip(id string, form *tview.Form) { } g.closeAndSwitchPanel(`form`, `trips`) - g.app.QueueUpdateDraw(func() { - g.tripsPanel().setEntries(g) - }) + g.tripsPanel().updateEntries(g) } @@ -316,9 +305,7 @@ func (g *Gui) modifyPerson(id string, form *tview.Form) { } g.closeAndSwitchPanel(`form`, `cavers`) - g.app.QueueUpdateDraw(func() { - g.peoplePanel().updateEntries(g)//setEntries(g) - }) + g.caversPanel().updateEntries(g) } @@ -396,9 +383,7 @@ func (g *Gui) modifyLocation(id string, form *tview.Form) { } g.closeAndSwitchPanel(`form`, `caves`) - g.app.QueueUpdateDraw(func() { - g.locationsPanel().setEntries(g) - }) + g.cavesPanel().updateEntries(g) } @@ -434,7 +419,7 @@ func (g *Gui) deleteLocation() { g.warning(err.Error(), `form`, []string{`OK`}, func() {return}) return } - g.locationsPanel().updateEntries(g) + g.cavesPanel().updateEntries(g) }) } @@ -451,6 +436,6 @@ func (g *Gui) deletePerson() { g.warning(err.Error(), `form`, []string{`OK`}, func() {return}) return } - g.peoplePanel().updateEntries(g) + g.caversPanel().updateEntries(g) }) } diff --git a/internal/gui/gui.go b/internal/gui/gui.go index cbc3ac4..3c6ffb8 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -82,7 +82,7 @@ func (g *Gui) tripsPanel() *trips { return nil } -func (g *Gui) locationsPanel() *caves { +func (g *Gui) cavesPanel() *caves { for _, panel := range g.state.panels.panel { if panel.name() == `caves` { return panel.(*caves) @@ -91,7 +91,7 @@ func (g *Gui) locationsPanel() *caves { return nil } -func (g *Gui) peoplePanel() *cavers { +func (g *Gui) caversPanel() *cavers { for _, panel := range g.state.panels.panel { if panel.name() == `cavers` { return panel.(*cavers) @@ -249,7 +249,7 @@ func (g *Gui) selectedTrip() *model.Log { } func (g *Gui) selectedLocation() *model.Cave { - row, _ := g.locationsPanel().GetSelection() + row, _ := g.cavesPanel().GetSelection() if len(g.state.resources.locations) == 0 { return nil } @@ -261,7 +261,7 @@ func (g *Gui) selectedLocation() *model.Cave { } func (g *Gui) selectedPerson() *model.Caver { - row, _ := g.peoplePanel().GetSelection() + row, _ := g.caversPanel().GetSelection() if len(g.state.resources.people) == 0 { return nil } diff --git a/internal/gui/monitoring.go b/internal/gui/monitoring.go index 75d4856..136be9b 100644 --- a/internal/gui/monitoring.go +++ b/internal/gui/monitoring.go @@ -6,8 +6,8 @@ func (g *Gui) startMonitoring() { g.state.stopChans["caves"] = stop g.state.stopChans["cavers"] = stop go g.tripsPanel().monitoringTrips(g) - go g.locationsPanel().monitoringCaves(g) - go g.peoplePanel().monitoringCavers(g) + go g.cavesPanel().monitoringCaves(g) + go g.caversPanel().monitoringCavers(g) } func (g *Gui) stopMonitoring() { @@ -16,8 +16,8 @@ func (g *Gui) stopMonitoring() { g.state.stopChans["cavers"] <- 1 } -func (g *Gui) updateTask() { +/*func (g *Gui) updateTask() { g.app.QueueUpdateDraw(func() { - g.peoplePanel().setEntries(g) // REVIEW: Why is this just people ? + g.caversPanel().setEntries(g) // REVIEW: Why is this just people ? }) -} +}*/ diff --git a/internal/gui/time.go b/internal/gui/time.go deleted file mode 100644 index b5117a6..0000000 --- a/internal/gui/time.go +++ /dev/null @@ -1,71 +0,0 @@ -package gui - -import ( - "time" - - "github.com/gdamore/tcell" - "github.com/rivo/tview" -) - -type timeWindow struct { - *tview.Table -} - -func newTimeWindow(g *Gui) (t *timeWindow) { - t = &timeWindow{ - Table: tview.NewTable().SetSelectable(false, false).SetFixed(3,1), - } - - t.SetBorder(false) - t.setEntries() - //t.setKeybinding(g) - return -} - -func (t *timeWindow) name() string { - return `timeWindow` -} - -func (t *timeWindow) setEntries() { - table := t.Clear() - - table.SetCell(0, 0, tview.NewTableCell(`Today`). - SetTextColor(tcell.ColorWhite). - SetMaxWidth(30). - SetExpansion(2)) - - table.SetCell(0, 1, tview.NewTableCell(time.Now().Format(`2006-01-02`)). - SetTextColor(tcell.ColorWhite). - SetMaxWidth(30). - SetExpansion(1)) -} - -/*func (t *timeWindow) setKeybinding(g *Gui) { - t.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - g.setGlobalKeybinding(event) - return event - }) -} - -func (t *timeWindow) entries(g *Gui) { - timeSlice := make([]*model.Statistic, 0) - timeSlice = append( - timeSlice, - &model.Statistic{Name: `Today`, Value: time.Now().Format(`2006-01-02`)}, - ) - g.state.resources.timeWindow = timeSlice -} - -func (t *timeWindow) updateEntries(g *Gui) {} - -func (t *timeWindow) focus(g *Gui) { - t.SetSelectable(true, false) - g.app.SetFocus(t) -} - -func (t *timeWindow) unfocus() { - t.SetSelectable(false, false) -} - -func (t *timeWindow) setFilterWord(word string) {} -*/ From 55df52a9e8def2d1195474d07a6fbdd54c8c6b16 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sun, 21 Jun 2020 15:59:32 +0100 Subject: [PATCH 06/21] Go code converted to use user inputted colors --- cmd/main.go | 1 + internal/gui/cavers.go | 10 ++++----- internal/gui/caves.go | 16 +++++++------- internal/gui/gui.go | 48 ++++++++++++++++++++++++++++++++++------ internal/gui/navigate.go | 7 ++---- internal/gui/trips.go | 10 ++++----- internal/model/config.go | 1 + 7 files changed, 63 insertions(+), 30 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 99918ae..0da5a20 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -78,6 +78,7 @@ func main() { // Initialise the Gui / Tui gui := gui.New(db) + gui.ProcessColors(cfg.Colors) if err := gui.Start(); err != nil { log.Fatalf("main: Cannot start tui: %s", err) diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index b3c1045..7ab1cd0 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -71,25 +71,25 @@ func (c *cavers) setEntries(g *Gui) { Text: header, NotSelectable: true, Align: tview.AlignLeft, - Color: tcell.ColorWhite, - BackgroundColor: tcell.ColorDefault, + Color: tview.Styles.PrimaryTextColor, + BackgroundColor: tview.Styles.PrimitiveBackgroundColor, Attributes: tcell.AttrBold, }) } for i, caver := range g.state.resources.people { table.SetCell(i+1, 0, tview.NewTableCell(caver.Name). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(30). SetExpansion(1)) table.SetCell(i+1, 1, tview.NewTableCell(caver.Club). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(0). SetExpansion(1)) table.SetCell(i+1, 2, tview.NewTableCell(strconv.FormatInt(caver.Count, 10)). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(0). SetExpansion(1)) } diff --git a/internal/gui/caves.go b/internal/gui/caves.go index 40f7f85..221fb19 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -62,7 +62,7 @@ func (c *caves) entries(g *Gui) { return } - g.state.resources.locations = caves + g.state.resources.locations = caves } func (c *caves) setEntries(g *Gui) { @@ -82,35 +82,35 @@ func (c *caves) setEntries(g *Gui) { Text: header, NotSelectable: true, Align: tview.AlignLeft, - Color: tcell.ColorWhite, - BackgroundColor: tcell.ColorDefault, + Color: tview.Styles.PrimaryTextColor, + BackgroundColor: tview.Styles.PrimitiveBackgroundColor, Attributes: tcell.AttrBold, }) } for i, cave := range g.state.resources.locations { table.SetCell(i+1, 0, tview.NewTableCell(cave.Name). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(30). SetExpansion(1)) table.SetCell(i+1, 1, tview.NewTableCell(cave.Region). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(30). SetExpansion(1)) table.SetCell(i+1, 2, tview.NewTableCell(cave.Country). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(0). SetExpansion(1)) table.SetCell(i+1, 3, tview.NewTableCell(strconv.FormatBool(cave.SRT)). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(0). SetExpansion(1)) table.SetCell(i+1, 4, tview.NewTableCell(strconv.FormatInt(cave.Visits, 10)). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(0). SetExpansion(1)) } diff --git a/internal/gui/gui.go b/internal/gui/gui.go index 3c6ffb8..50dda35 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/rivo/tview" + "github.com/gdamore/tcell" "github.com/idlephysicist/cave-logger/internal/db" "github.com/idlephysicist/cave-logger/internal/model" @@ -38,10 +39,10 @@ func newState() *state { } type Gui struct { - app *tview.Application - pages *tview.Pages - state *state - db *db.Database + app *tview.Application + pages *tview.Pages + state *state + db *db.Database //statsLocations *statsLocations } @@ -54,6 +55,38 @@ func New(db *db.Database) *Gui { } } +func (g *Gui) ProcessColors(colors map[string]string) { + for color, hex := range colors { + if hex == "" { + continue + } + switch color { + case "primitiveBackground": + tview.Styles.PrimitiveBackgroundColor = tcell.GetColor(hex) + case "contrastBackground": + tview.Styles.ContrastBackgroundColor = tcell.GetColor(hex) + case "moreContrastBackground": + tview.Styles.MoreContrastBackgroundColor = tcell.GetColor(hex) + case "border": + tview.Styles.BorderColor = tcell.GetColor(hex) + case "title": + tview.Styles.TitleColor = tcell.GetColor(hex) + case "graphics": + tview.Styles.GraphicsColor = tcell.GetColor(hex) + case "primaryText": + tview.Styles.PrimaryTextColor = tcell.GetColor(hex) + case "secondaryText": + tview.Styles.SecondaryTextColor = tcell.GetColor(hex) + case "tertiaryText": + tview.Styles.TertiaryTextColor = tcell.GetColor(hex) + case "inverseText": + tview.Styles.InverseTextColor = tcell.GetColor(hex) + case "contrastSecondaryText": + tview.Styles.ContrastSecondaryTextColor = tcell.GetColor(hex) + } + } +} + // Start start application func (g *Gui) Start() error { g.initPanels() @@ -144,11 +177,11 @@ func (g *Gui) initPanels() { // Add pages to the "book" g.pages.AddPage(`trips`, trips, true, true) - fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[white][""] `, 0, 1, strings.Title(trips.name())) + fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[""] `, 0, 1, strings.Title(trips.name())) g.pages.AddPage(`cavers`, cavers, true, true) - fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[white][""] `, 1, 2, strings.Title(cavers.name())) + fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[""] `, 1, 2, strings.Title(cavers.name())) g.pages.AddPage(`caves`, caves, true, true) - fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[white][""] `, 2, 3, strings.Title(caves.name())) + fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[""] `, 2, 3, strings.Title(caves.name())) g.state.tabBar.Highlight("0") @@ -271,3 +304,4 @@ func (g *Gui) selectedPerson() *model.Caver { return g.state.resources.people[row-1] } + diff --git a/internal/gui/navigate.go b/internal/gui/navigate.go index fa198e0..96f148f 100644 --- a/internal/gui/navigate.go +++ b/internal/gui/navigate.go @@ -1,9 +1,6 @@ package gui -import ( - "github.com/gdamore/tcell" - "github.com/rivo/tview" -) +import "github.com/rivo/tview" type navigate struct { *tview.TextView @@ -12,7 +9,7 @@ type navigate struct { func newNavigate() *navigate { navi := &navigate{ - TextView: tview.NewTextView().SetTextColor(tcell.ColorWhite), + TextView: tview.NewTextView().SetTextColor(tview.Styles.PrimaryTextColor), keybindings: map[string]string{ "trips" : " n: New Log Entry, m: Modify Log, d: Remove Log, /: filter, Enter: Inspect ", "caves" : " n: New Cave, m: Modify Cave, d: Remove Cave, /: filter, Enter: Inspect ", diff --git a/internal/gui/trips.go b/internal/gui/trips.go index 0c81179..36c7524 100644 --- a/internal/gui/trips.go +++ b/internal/gui/trips.go @@ -80,25 +80,25 @@ func (t *trips) setEntries(g *Gui) { Text: header, NotSelectable: true, Align: tview.AlignLeft, - Color: tcell.ColorWhite, - BackgroundColor: tcell.ColorDefault, + Color: tview.Styles.PrimaryTextColor, + BackgroundColor: tview.Styles.PrimitiveBackgroundColor, Attributes: tcell.AttrBold, }) } for i, trip := range g.state.resources.trips { table.SetCell(i+1, 0, tview.NewTableCell(trip.Date). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(30). SetExpansion(1)) table.SetCell(i+1, 1, tview.NewTableCell(trip.Cave). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(30). SetExpansion(1)) table.SetCell(i+1, 2, tview.NewTableCell(trip.Names). - SetTextColor(tcell.ColorWhite). + SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(0). SetExpansion(2)) } diff --git a/internal/model/config.go b/internal/model/config.go index ec01644..980b225 100644 --- a/internal/model/config.go +++ b/internal/model/config.go @@ -5,5 +5,6 @@ type Config struct { Created string `json:"created"` Filename string `json:"filename"` } `json:"database"` + Colors map[string]string `json:"colors"` } From 42e18ebd1d00c21bd982b00a3f3570bcfbb808a0 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sun, 21 Jun 2020 16:37:40 +0100 Subject: [PATCH 07/21] Updated script for generating configs The Python script now adds the kv pairs for defining custom colours --- scripts/make-db.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/make-db.py b/scripts/make-db.py index 62e6f81..7ccbc6e 100755 --- a/scripts/make-db.py +++ b/scripts/make-db.py @@ -93,6 +93,19 @@ 'database': { 'filename': '/'.join([NEWPATH, sqliteFile]), 'created' : NOW + }, + 'colors': { + 'primitiveBackground': '', + 'contrastBackground': '', + 'moreContrastBackground': '', + 'border': '', + 'title': '', + 'graphics': '', + 'primaryText': '', + 'secondaryText': '', + 'tertiaryText': '', + 'inverseText': '', + 'contrastSecondaryText': '' } } json.dump(config, c) From 2abd427e581242f6cf2a2bcbab6e61c754e63b9f Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sun, 21 Jun 2020 18:38:03 +0100 Subject: [PATCH 08/21] Rudimentary filtering added issue #20 --- internal/gui/cavers.go | 12 +++++++++-- internal/gui/caves.go | 12 +++++++++-- internal/gui/keybindings.go | 41 +++++++++++++++++++++++++++++++++++++ internal/gui/trips.go | 12 +++++++++-- 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index 7ab1cd0..fd5610f 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -2,6 +2,7 @@ package gui import ( "strconv" + "strings" "time" "github.com/gdamore/tcell" @@ -107,7 +108,14 @@ func (c *cavers) entries(g *Gui) { return } - g.state.resources.people = cavers + var filteredCavers []*model.Caver + for _, caver := range cavers { + if strings.Index(caver.Name, c.filterWord) == -1 { + continue + } + filteredCavers = append(filteredCavers, caver) + } + g.state.resources.people = filteredCavers } func (c *cavers) focus(g *Gui) { @@ -124,7 +132,7 @@ func (c *cavers) setFilterWord(word string) { } func (c *cavers) monitoringCavers(g *Gui) { - ticker := time.NewTicker(5 * time.Second) + ticker := time.NewTicker(5 * time.Minute) LOOP: for { diff --git a/internal/gui/caves.go b/internal/gui/caves.go index 221fb19..67122e8 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -2,6 +2,7 @@ package gui import ( "strconv" + "strings" "time" "github.com/gdamore/tcell" @@ -62,7 +63,14 @@ func (c *caves) entries(g *Gui) { return } - g.state.resources.locations = caves + var filteredCaves []*model.Cave + for _, cave := range caves { + if strings.Index(cave.Name, c.filterWord) == -1 { + continue + } + filteredCaves = append(filteredCaves, cave) + } + g.state.resources.locations = filteredCaves } func (c *caves) setEntries(g *Gui) { @@ -136,7 +144,7 @@ func (c *caves) setFilterWord(word string) { } func (c *caves) monitoringCaves(g *Gui) { - ticker := time.NewTicker(5 * time.Second) + ticker := time.NewTicker(5 * time.Minute) LOOP: for { diff --git a/internal/gui/keybindings.go b/internal/gui/keybindings.go index 2f0af53..10fa0e4 100644 --- a/internal/gui/keybindings.go +++ b/internal/gui/keybindings.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/gdamore/tcell" + "github.com/rivo/tview" + "github.com/idlephysicist/cave-logger/internal/model" ) @@ -28,9 +30,48 @@ func (g *Gui) setGlobalKeybinding(event *tcell.EventKey) { g.goTo("cavers") case '3': g.goTo("caves") + case '/': + g.filter() } } +func (g *Gui) filter() { + currentPanel := g.state.panels.panel[g.state.panels.currentPanel] + currentPanel.setFilterWord("") + currentPanel.updateEntries(g) + + viewName := "filter" + searchInput := tview.NewInputField().SetLabel("Parameter") + searchInput.SetLabelWidth(10) + searchInput.SetTitle(" Filter ") + searchInput.SetTitleAlign(tview.AlignLeft) + searchInput.SetBorder(true) + + closeSearchInput := func() { + g.closeAndSwitchPanel(viewName, g.state.panels.panel[g.state.panels.currentPanel].name()) + } + + searchInput.SetDoneFunc(func(key tcell.Key) { + if key == tcell.KeyEnter { + closeSearchInput() + } + }) + + searchInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEsc { + closeSearchInput() + } + return event + }) + + searchInput.SetChangedFunc(func(text string) { + currentPanel.setFilterWord(text) + currentPanel.updateEntries(g) + }) + + g.pages.AddAndSwitchToPage(viewName, g.modal(searchInput, 80, 3), true).ShowPage("main") +} + // // INSPECTION FUNCS // diff --git a/internal/gui/trips.go b/internal/gui/trips.go index 36c7524..f6efa46 100644 --- a/internal/gui/trips.go +++ b/internal/gui/trips.go @@ -1,6 +1,7 @@ package gui import ( + "strings" "time" "github.com/gdamore/tcell" @@ -62,7 +63,14 @@ func (t *trips) entries(g *Gui) { return } - g.state.resources.trips = trips + var filteredTrips []*model.Log + for _, trip := range trips { + if strings.Index(trip.Cave, t.filterWord) == -1 { + continue + } + filteredTrips = append(filteredTrips, trip) + } + g.state.resources.trips = filteredTrips } func (t *trips) setEntries(g *Gui) { @@ -124,7 +132,7 @@ func (t *trips) setFilterWord(word string) { } func (t *trips) monitoringTrips(g *Gui) { - ticker := time.NewTicker(5 * time.Second) + ticker := time.NewTicker(5 * time.Minute) LOOP: for { From 4fef088be26c821ef17905a2a291a5350f8efb80 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sun, 21 Jun 2020 21:32:04 +0100 Subject: [PATCH 09/21] Update to move detail inspection window Detail inspection window is now hidden until brought to the front, it is also only accessible from the trips page. --- internal/gui/cavers.go | 4 +- internal/gui/caves.go | 4 +- internal/gui/gui.go | 14 +----- internal/gui/inspector.go | 90 +++++++++++++++++++++++++------------ internal/gui/keybindings.go | 62 ------------------------- internal/gui/navigate.go | 7 +-- internal/gui/trips.go | 1 + 7 files changed, 71 insertions(+), 111 deletions(-) diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index fd5610f..34e0cac 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -38,8 +38,8 @@ func (c *cavers) setKeybinding(g *Gui) { g.setGlobalKeybinding(event) switch event.Key() { - case tcell.KeyEnter: - g.inspectPerson() + //case tcell.KeyEnter: + // g.inspectPerson() case tcell.KeyCtrlR: c.setEntries(g) } diff --git a/internal/gui/caves.go b/internal/gui/caves.go index 67122e8..b2ab7a0 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -38,8 +38,8 @@ func (c *caves) setKeybinding(g *Gui) { g.setGlobalKeybinding(event) switch event.Key() { - case tcell.KeyEnter: - g.inspectCave() + //case tcell.KeyEnter: + // g.inspectCave() case tcell.KeyCtrlR: c.setEntries(g) } diff --git a/internal/gui/gui.go b/internal/gui/gui.go index 50dda35..cad30c1 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -133,15 +133,6 @@ func (g *Gui) caversPanel() *cavers { return nil } -func (g *Gui) inspectorPanel() *inspector { - for _, panel := range g.state.panels.panel { - if panel.name() == `inspector` { - return panel.(*inspector) - } - } - return nil -} - /*func (g *Gui) statsLocationsPanel() *statsLocations { for _, panel := range g.state.panels.panel { if panel.name() == `statsLocations` { @@ -186,22 +177,19 @@ func (g *Gui) initPanels() { g.state.tabBar.Highlight("0") // Panels - inspector := newInspector(g) statusBar := newNavigate() g.state.panels.panel = append(g.state.panels.panel, trips) g.state.panels.panel = append(g.state.panels.panel, cavers) g.state.panels.panel = append(g.state.panels.panel, caves) - g.state.panels.panel = append(g.state.panels.panel, inspector) - g.state.navigate = statusBar // Arange the windows / tiles layout := tview.NewFlex().SetDirection(tview.FlexRow). AddItem(g.state.tabBar, 1, 1, false). AddItem(g.pages, 0, 16, true). - AddItem(inspector, 0, 7, false). + //AddItem(inspector, 0, 7, false). AddItem(statusBar, 1, 1, false) g.app.SetRoot(layout, true) diff --git a/internal/gui/inspector.go b/internal/gui/inspector.go index 2610187..dbd1c78 100644 --- a/internal/gui/inspector.go +++ b/internal/gui/inspector.go @@ -1,53 +1,85 @@ package gui import ( + "fmt" + "github.com/rivo/tview" + "github.com/gdamore/tcell" + + "github.com/idlephysicist/cave-logger/internal/model" ) -type inspector struct { - *tview.TextView +var inspectorFormat = map[string]string{ + `trips` : "\tDate: %s\n\tCave: %s\n\tCavers: %s\n\tNotes: %s", + `cavers`: "\tName: %s\n\tClub: %s\n\tCount: %d", + `caves` : "\tName: %s\n\tRegion: %s\n\tCountry: %s\n\tSRT: %v\n\tVisits: %d", } -func newInspector(g *Gui) (insp *inspector) { - insp = &inspector{ - //Frame: tview.NewFrame(tview.NewTextView()),//.SetBorder(true).SetTitle(" Inspector "), - TextView: tview.NewTextView(), - } +func (g *Gui) displayInspect(data, page string) { + text := tview.NewTextView() + text.SetTitle(" Detail ").SetTitleAlign(tview.AlignLeft) + text.SetBorder(true) + text.SetText(data) - insp.SetTitle(` Inspector `).SetTitleAlign(tview.AlignLeft) - insp.SetBorder(true) - insp.setInitEntry() - return -} + text.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEsc || event.Rune() == 'q' { + g.closeAndSwitchPanel("detail", page) + } + return event + }) -func (i *inspector) name() string { - return `inspector` + g.pages.AddAndSwitchToPage("detail", text, true) } -func (i *inspector) setEntry(text string) { - i.SetText(text) +// +// INSPECTION FUNCS +// + +func (g *Gui) inspectTrip() { + selected := g.selectedTrip() + + trip, err := g.db.GetTrip(selected.ID) + if err != nil { + return + } + + g.displayInspect(g.formatTrip(trip), "trips") } -func (i *inspector) setKeybinding(g *Gui) {} +/*func (g *Gui) inspectCave() { + selected := g.selectedLocation() -func (i *inspector) setEntries(g *Gui) {} + cave, err := g.db.GetLocation(selected.ID) + if err != nil { + return + } -func (i *inspector) setInitEntry() { - i.SetText(` - __ __ __ __ - | ||_ | / / \|\/||_ - |/\||__|__\__\__/| ||__ `) + g.inspectorPanel().setEntry(g.formatCave(cave)) } -func (i *inspector) entries(g *Gui) {} +func (g *Gui) inspectPerson() { + selected := g.selectedPerson() -func (i *inspector) updateEntries(g *Gui) {} + caver, err := g.db.GetPerson(selected.ID) + if err != nil { + return + } + + g.inspectorPanel().setEntry(g.formatPerson(caver)) +}*/ -func (i *inspector) focus(g *Gui) { - g.app.SetFocus(i) +// +// Formatting Functions +// +func (g *Gui) formatTrip(trip *model.Log) string { + return fmt.Sprintf(inspectorFormat[`trips`], trip.Date, trip.Cave, trip.Names, trip.Notes) } -func (i *inspector) unfocus() { +/*func (g *Gui) formatCave(l *model.Cave) string { + return fmt.Sprintf(inspectorFormat[`caves`], l.Name, l.Region, l.Country, l.SRT, l.Visits) } -func (i *inspector) setFilterWord(word string) {} +func (g *Gui) formatPerson(p *model.Caver) string { + return fmt.Sprintf(inspectorFormat[`cavers`], p.Name, p.Club, p.Count) +}*/ + diff --git a/internal/gui/keybindings.go b/internal/gui/keybindings.go index 10fa0e4..c06bffc 100644 --- a/internal/gui/keybindings.go +++ b/internal/gui/keybindings.go @@ -1,20 +1,10 @@ package gui import ( - "fmt" - "github.com/gdamore/tcell" "github.com/rivo/tview" - - "github.com/idlephysicist/cave-logger/internal/model" ) -var inspectorFormat = map[string]string{ - `trips` : "Date: %s\nCave: %s\nCavers: %s\nNotes: %s", - `cavers`: "Name: %s\nClub: %s\nCount: %d", - `caves` : "Name: %s\nRegion: %s\nCountry: %s\nSRT: %v\nVisits: %d", -} - func (g *Gui) setGlobalKeybinding(event *tcell.EventKey) { /*switch event.Key() { case tcell.KeyTAB: @@ -72,58 +62,6 @@ func (g *Gui) filter() { g.pages.AddAndSwitchToPage(viewName, g.modal(searchInput, 80, 3), true).ShowPage("main") } -// -// INSPECTION FUNCS -// - -func (g *Gui) inspectTrip() { - selected := g.selectedTrip() - - trip, err := g.db.GetTrip(selected.ID) - if err != nil { - return - } - - g.inspectorPanel().setEntry(g.formatTrip(trip)) -} - -func (g *Gui) inspectCave() { - selected := g.selectedLocation() - - cave, err := g.db.GetLocation(selected.ID) - if err != nil { - return - } - - g.inspectorPanel().setEntry(g.formatCave(cave)) -} - -func (g *Gui) inspectPerson() { - selected := g.selectedPerson() - - caver, err := g.db.GetPerson(selected.ID) - if err != nil { - return - } - - g.inspectorPanel().setEntry(g.formatPerson(caver)) -} - -// -// Formatting Functions -// -func (g *Gui) formatTrip(trip *model.Log) string { - return fmt.Sprintf(inspectorFormat[`trips`], trip.Date, trip.Cave, trip.Names, trip.Notes) -} - -func (g *Gui) formatCave(l *model.Cave) string { - return fmt.Sprintf(inspectorFormat[`caves`], l.Name, l.Region, l.Country, l.SRT, l.Visits) -} - -func (g *Gui) formatPerson(p *model.Caver) string { - return fmt.Sprintf(inspectorFormat[`cavers`], p.Name, p.Club, p.Count) -} - // // MISC // diff --git a/internal/gui/navigate.go b/internal/gui/navigate.go index 96f148f..5d0b2c9 100644 --- a/internal/gui/navigate.go +++ b/internal/gui/navigate.go @@ -11,9 +11,10 @@ func newNavigate() *navigate { navi := &navigate{ TextView: tview.NewTextView().SetTextColor(tview.Styles.PrimaryTextColor), keybindings: map[string]string{ - "trips" : " n: New Log Entry, m: Modify Log, d: Remove Log, /: filter, Enter: Inspect ", - "caves" : " n: New Cave, m: Modify Cave, d: Remove Cave, /: filter, Enter: Inspect ", - "cavers": " n: New Caver, m: Modify Caver, d: Remove Caver, /: filter, Enter: Inspect ", + "trips" : " n: New Log Entry, m: Modify Log, d: Remove Log, /: Filter, Enter: Inspect Detail ", + "caves" : " n: New Cave, m: Modify Cave, d: Remove Cave, /: Filter ", + "cavers": " n: New Caver, m: Modify Caver, d: Remove Caver, /: Filter ", + "detail": " q | ESC: Exit Detail ", }, } diff --git a/internal/gui/trips.go b/internal/gui/trips.go index f6efa46..5b7311e 100644 --- a/internal/gui/trips.go +++ b/internal/gui/trips.go @@ -39,6 +39,7 @@ func (t *trips) setKeybinding(g *Gui) { switch event.Key() { case tcell.KeyEnter: + g.state.navigate.update("detail") g.inspectTrip() case tcell.KeyCtrlR: t.setEntries(g) From 411cad03080fd4198ba6a734fda22d418dd9cd1b Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sun, 21 Jun 2020 21:39:19 +0100 Subject: [PATCH 10/21] resolving review comments from PR --- internal/gui/cavers.go | 2 -- internal/gui/caves.go | 2 -- internal/gui/gui.go | 1 - 3 files changed, 5 deletions(-) diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index 34e0cac..b039b7c 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -38,8 +38,6 @@ func (c *cavers) setKeybinding(g *Gui) { g.setGlobalKeybinding(event) switch event.Key() { - //case tcell.KeyEnter: - // g.inspectPerson() case tcell.KeyCtrlR: c.setEntries(g) } diff --git a/internal/gui/caves.go b/internal/gui/caves.go index b2ab7a0..c891ab7 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -38,8 +38,6 @@ func (c *caves) setKeybinding(g *Gui) { g.setGlobalKeybinding(event) switch event.Key() { - //case tcell.KeyEnter: - // g.inspectCave() case tcell.KeyCtrlR: c.setEntries(g) } diff --git a/internal/gui/gui.go b/internal/gui/gui.go index cad30c1..6ebc0c4 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -189,7 +189,6 @@ func (g *Gui) initPanels() { layout := tview.NewFlex().SetDirection(tview.FlexRow). AddItem(g.state.tabBar, 1, 1, false). AddItem(g.pages, 0, 16, true). - //AddItem(inspector, 0, 7, false). AddItem(statusBar, 1, 1, false) g.app.SetRoot(layout, true) From 3a2b68b074bca932fa66bbf9e67ee9ad597dcee3 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sun, 21 Jun 2020 21:45:55 +0100 Subject: [PATCH 11/21] Removing page title from border Minor change to remove the page title from the border because it is already highlighted on the tab bar. --- internal/gui/cavers.go | 2 +- internal/gui/caves.go | 2 +- internal/gui/trips.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index b039b7c..bf05417 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -22,7 +22,7 @@ func newCavers(g *Gui) *cavers { Table: tview.NewTable().SetSelectable(true, false).Select(0,0).SetFixed(1,1), } - cavers.SetTitle(` Cavers `).SetTitleAlign(tview.AlignLeft) + cavers.SetTitle(``).SetTitleAlign(tview.AlignLeft) cavers.SetBorder(true) cavers.setEntries(g) cavers.setKeybinding(g) diff --git a/internal/gui/caves.go b/internal/gui/caves.go index c891ab7..2ee3ba8 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -22,7 +22,7 @@ func newCaves(g *Gui) *caves { Table: tview.NewTable().SetSelectable(true, false).Select(0,0).SetFixed(1,1), } - caves.SetTitle(` Caves `).SetTitleAlign(tview.AlignLeft) + caves.SetTitle(``).SetTitleAlign(tview.AlignLeft) caves.SetBorder(true) caves.setEntries(g) caves.setKeybinding(g) diff --git a/internal/gui/trips.go b/internal/gui/trips.go index 5b7311e..b126bcf 100644 --- a/internal/gui/trips.go +++ b/internal/gui/trips.go @@ -22,7 +22,7 @@ func newTrips(g *Gui) *trips { trips: make(chan *model.Log), } - trips.SetTitle(` Trips `).SetTitleAlign(tview.AlignLeft) + trips.SetTitle(``).SetTitleAlign(tview.AlignLeft) trips.SetBorder(true) trips.setEntries(g) trips.setKeybinding(g) From 8f51b7ef15151052471b68add44e2a0802158a9f Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Mon, 22 Jun 2020 14:54:55 +0100 Subject: [PATCH 12/21] Fixing issue with make-db script - Fix path for database that is inserted into the config script - Pretty print the JSON into the config file --- scripts/make-db.py | 6 +++--- scripts/move-configs.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/make-db.py b/scripts/make-db.py index 7ccbc6e..d294484 100755 --- a/scripts/make-db.py +++ b/scripts/make-db.py @@ -8,7 +8,7 @@ NOW = datetime.now().strftime("%Y-%m-%dT%H_%M") HOME = os.environ["HOME"] -NEWPATH = "{}/.config/cave-logger".format(HOME) +NEWPATH = f"{HOME}/.config/cave-logger" try: os.makedirs(NEWPATH, 0o755) @@ -91,7 +91,7 @@ with open(CONFIG_FN, 'w') as c: config = { 'database': { - 'filename': '/'.join([NEWPATH, sqliteFile]), + 'filename': '/'.join([".config/cave-logger", sqliteFile]), 'created' : NOW }, 'colors': { @@ -108,5 +108,5 @@ 'contrastSecondaryText': '' } } - json.dump(config, c) + json.dump(config, c, indent=2) print("Wrote database name to config file") diff --git a/scripts/move-configs.py b/scripts/move-configs.py index 242afac..2aa1682 100755 --- a/scripts/move-configs.py +++ b/scripts/move-configs.py @@ -26,4 +26,4 @@ cfg['database']['filename'] = f"{HOME}/{NEWPATH}/{cfg['database']['filename'].split('/')[-1]}" with open(f"{HOME}/{NEWPATH}/config.json", 'w') as c: - json.dump(cfg, c) + json.dump(cfg, c, indent=2) From 03ac32957349d7eb5770a7b87de5e83979f83790 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Wed, 15 Jul 2020 00:46:25 +0100 Subject: [PATCH 13/21] Fixes #23 Check for nil pointer in inspection of trip --- internal/db/db.go | 62 +++++++++++++++++++-------------------- internal/gui/inspector.go | 10 +++++++ 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/internal/db/db.go b/internal/db/db.go index 31140c3..909ec2f 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -52,11 +52,11 @@ func (db *Database) AddTrip(date, location, names, notes string) error { if err != nil { return err } - + if err = db.conn.Begin(); err != nil { return err } - + // Insert the trip itself tripID, err := db.execute(query, params) if err != nil { @@ -163,14 +163,14 @@ func (db *Database) GetAllTrips() ([]*model.Log, error) { trips.notes AS 'notes' FROM trips, locations WHERE trips.caveid = locations.id` - + result, err := db.conn.Prepare(query) if err != nil { db.log.Errorf("db.prepare: Failed to query database", err) return nil, err } defer result.Close() - + trips := make([]*model.Log, 0) for { var stamp int64 @@ -185,7 +185,7 @@ func (db *Database) GetAllTrips() ([]*model.Log, error) { if !rowExists { break } - + err = result.Scan(&trip.ID, &stamp, &trip.Cave, &trip.Names, &trip.Notes) if err != nil { db.log.Error(err) @@ -193,9 +193,9 @@ func (db *Database) GetAllTrips() ([]*model.Log, error) { } trip.Date = time.Unix(stamp, 0).Format(date) - + // Add this formatted row to the rows map - trips = append(trips, &trip) + trips = append(trips, &trip) } return trips, err @@ -225,7 +225,7 @@ func (db *Database) GetTrip(logID string) (*model.Log, error) { //FIXME: return nil, err } defer result.Close() - + trips := make([]*model.Log, 0) for { var stamp int64 @@ -240,7 +240,7 @@ func (db *Database) GetTrip(logID string) (*model.Log, error) { //FIXME: if !rowExists { break } - + err = result.Scan(&trip.ID, &stamp, &trip.Cave, &trip.Names, &trip.Notes) if err != nil { db.log.Error(err) @@ -248,9 +248,9 @@ func (db *Database) GetTrip(logID string) (*model.Log, error) { //FIXME: } trip.Date = time.Unix(stamp, 0).Format(date) - + // Add this formatted row to the rows map - trips = append(trips, &trip) + trips = append(trips, &trip) } return trips[0], err @@ -281,7 +281,7 @@ func (db *Database) GetAllPeople() ([]*model.Caver, error) { cavers := make([]*model.Caver, 0) for { var c model.Caver - + rowExists, err := result.Step() if err != nil { db.log.Errorf("db.get: Step error: %s", err) @@ -291,7 +291,7 @@ func (db *Database) GetAllPeople() ([]*model.Caver, error) { if !rowExists { break } - + err = result.Scan(&c.ID, &c.Name, &c.Club, &c.Count) if err != nil { db.log.Errorf("Scan: %v", err) @@ -321,7 +321,7 @@ func (db *Database) GetTopPeople() ([]*model.Statistic, error) { cavers := make([]*model.Statistic, 0) for { var c model.Statistic - + rowExists, err := result.Step() if err != nil { db.log.Errorf("db.get: Step error: %s", err) @@ -331,7 +331,7 @@ func (db *Database) GetTopPeople() ([]*model.Statistic, error) { if !rowExists { break } - + err = result.Scan(&c.Name, &c.Value) if err != nil { db.log.Errorf("Scan: %v", err) @@ -363,7 +363,7 @@ func (db *Database) GetPerson(personID string) (*model.Caver, error) { return nil, err } defer result.Close() - + people := make([]*model.Caver, 0) for { var person model.Caver @@ -377,7 +377,7 @@ func (db *Database) GetPerson(personID string) (*model.Caver, error) { if !rowExists { break } - + err = result.Scan(&person.ID, &person.Name, &person.Club, &person.Count) if err != nil { db.log.Error(err) @@ -385,7 +385,7 @@ func (db *Database) GetPerson(personID string) (*model.Caver, error) { } // Add this formatted row to the rows map - people = append(people, &person) + people = append(people, &person) } return people[0], err @@ -418,7 +418,7 @@ func (db *Database) GetAllLocations() ([]*model.Cave, error) { caves := make([]*model.Cave, 0) for { var c model.Cave - + rowExists, err := result.Step() if err != nil { db.log.Errorf("db.get: Step error: %s", err) @@ -428,7 +428,7 @@ func (db *Database) GetAllLocations() ([]*model.Cave, error) { if !rowExists { break } - + err = result.Scan(&c.ID, &c.Name, &c.Region, &c.Country, &c.SRT, &c.Visits) if err != nil { db.log.Errorf("Scan: %v", err) @@ -458,7 +458,7 @@ func (db *Database) GetTopLocations() ([]*model.Statistic, error) { stats := make([]*model.Statistic, 0) for { var s model.Statistic - + rowExists, err := result.Step() if err != nil { db.log.Errorf("db.get: Step error: %s", err) @@ -468,7 +468,7 @@ func (db *Database) GetTopLocations() ([]*model.Statistic, error) { if !rowExists { break } - + err = result.Scan(&s.Name, &s.Value) if err != nil { db.log.Errorf("Scan: %v", err) @@ -500,7 +500,7 @@ func (db *Database) GetLocation(caveID string) (*model.Cave, error) { return nil, err } defer result.Close() - + caves := make([]*model.Cave, 0) for { //var caverIDstr string @@ -516,7 +516,7 @@ func (db *Database) GetLocation(caveID string) (*model.Cave, error) { if !rowExists { break } - + err = result.Scan(&cave.ID, &cave.Name, &cave.Region, &cave.Country, &cave.SRT, &cave.Visits) if err != nil { db.log.Error(err) @@ -524,7 +524,7 @@ func (db *Database) GetLocation(caveID string) (*model.Cave, error) { } // Add this formatted row to the rows map - caves = append(caves, &cave) + caves = append(caves, &cave) } return caves[0], err @@ -538,7 +538,7 @@ func (db *Database) RemoveTrip(id string) error { if err := db.conn.Begin(); err != nil { return err } - + // Delete trip entry err := db.conn.Exec(`DELETE FROM trips WHERE id = ?`, id) if err != nil { @@ -571,7 +571,7 @@ func (db *Database) RemovePerson(id string) error { if err := db.conn.Begin(); err != nil { return err } - + // Delete trip entry err := db.conn.Exec(`DELETE FROM people WHERE id = ?`, id) if err != nil { @@ -596,7 +596,7 @@ func (db *Database) RemoveLocation(id string) error { if err := db.conn.Begin(); err != nil { return err } - + // Delete trip entry err := db.conn.Exec(`DELETE FROM locations WHERE id = ?`, id) if err != nil { @@ -627,13 +627,13 @@ func (db *Database) ModifyTrip(id, date, location, names, notes string) error { if err != nil { return err } - + params = append(params, id) if err = db.conn.Begin(); err != nil { return err } - + // Update the trip itself _, err = db.execute(query, params) if err != nil { @@ -650,7 +650,7 @@ func (db *Database) ModifyTrip(id, date, location, names, notes string) error { panic(rb_err) } return err - } + } _, err = db.execute(db.addTripGroups(id, caverIDs)) if err != nil { diff --git a/internal/gui/inspector.go b/internal/gui/inspector.go index dbd1c78..359ddbe 100644 --- a/internal/gui/inspector.go +++ b/internal/gui/inspector.go @@ -43,6 +43,16 @@ func (g *Gui) inspectTrip() { return } + if trip == nil { + g.warning( + fmt.Sprintf("Trip (id: %s) not found", selected.ID), + `trips`, + []string{`OK`}, + func() {return}, + ) + return + } + g.displayInspect(g.formatTrip(trip), "trips") } From 6b1db18b78ddbbc89fb8b25fdafc0bdc8275da63 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Wed, 15 Jul 2020 12:34:34 +0100 Subject: [PATCH 14/21] Closes #23 for real Checks for nil entries in table --- internal/gui/inspector.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/internal/gui/inspector.go b/internal/gui/inspector.go index 359ddbe..e7b8341 100644 --- a/internal/gui/inspector.go +++ b/internal/gui/inspector.go @@ -38,18 +38,13 @@ func (g *Gui) displayInspect(data, page string) { func (g *Gui) inspectTrip() { selected := g.selectedTrip() - trip, err := g.db.GetTrip(selected.ID) - if err != nil { + if selected == nil { + g.warning("No trips in table", `trips`, []string{`OK`}, func() {return}) return } - if trip == nil { - g.warning( - fmt.Sprintf("Trip (id: %s) not found", selected.ID), - `trips`, - []string{`OK`}, - func() {return}, - ) + trip, err := g.db.GetTrip(selected.ID) + if err != nil { return } From 24dd455c2d0ae71d23afa63c5399cca6edfcbaa8 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Mon, 3 Aug 2020 14:38:35 +0100 Subject: [PATCH 15/21] Adding Licence --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cdbd6e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Eoghan Conlon O'Neill (IdlePhysicist) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From fc294900edfe86ff7cc6132f322332f28d834e5a Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sun, 9 Aug 2020 16:36:28 +0100 Subject: [PATCH 16/21] Commit to change import path to PascalCase --- cmd/main.go | 6 +++--- docker/Dockerfile | 2 +- go.mod | 8 +------- go.sum | 23 +---------------------- internal/db/db.go | 2 +- internal/gui/cavers.go | 2 +- internal/gui/caves.go | 2 +- internal/gui/gui.go | 4 ++-- internal/gui/inspector.go | 2 +- internal/gui/trips.go | 2 +- 10 files changed, 13 insertions(+), 40 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 0da5a20..18fff79 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -10,9 +10,9 @@ import ( "github.com/sirupsen/logrus" flag "github.com/spf13/pflag" - "github.com/idlephysicist/cave-logger/internal/db" - "github.com/idlephysicist/cave-logger/internal/gui" - "github.com/idlephysicist/cave-logger/internal/model" + "github.com/IdlePhysicist/cave-logger/internal/db" + "github.com/IdlePhysicist/cave-logger/internal/gui" + "github.com/IdlePhysicist/cave-logger/internal/model" ) var commit, version, date string diff --git a/docker/Dockerfile b/docker/Dockerfile index 42a6e1a..31c66c8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,7 +10,7 @@ RUN apk add \ RUN mkdir /root/.config -WORKDIR /go/src/github.com/idlephysicist/cave-logger +WORKDIR /go/src/github.com/IdlePhysicist/cave-logger COPY go.* ./ #COPY go.sum . diff --git a/go.mod b/go.mod index 753e990..663b150 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,11 @@ -module github.com/idlephysicist/cave-logger +module github.com/IdlePhysicist/cave-logger go 1.13 require ( github.com/bvinc/go-sqlite-lite v0.6.1 github.com/gdamore/tcell v1.2.0 - github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect - github.com/kr/pretty v0.1.0 // indirect github.com/rivo/tview v0.0.0-20190829161255-f8bc69b90341 github.com/sirupsen/logrus v1.4.2 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.4.0 // indirect - golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 // indirect - golang.org/x/text v0.3.2 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index 92907be..9de4f6f 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,6 @@ github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFD github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/bvinc/go-sqlite-lite v0.6.1 h1:JU8Rz5YAOZQiU3WEulKF084wfXpytRiqD2IaW2QjPz4= github.com/bvinc/go-sqlite-lite v0.6.1/go.mod h1:2GiE60NUdb0aNhDdY+LXgrqAVDpi2Ijc6dB6ZMp9x6s= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= @@ -12,13 +11,6 @@ github.com/gdamore/tcell v1.2.0 h1:ikixzsxc8K8o3V2/CEmyoEW8mJZaNYQQ3NP3VIQdUe4= github.com/gdamore/tcell v1.2.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= @@ -33,24 +25,11 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 h1:wYqz/tQaWUgGKyx+B/rssSE6wkIKdY5Ee6ryOmzarIg= -golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/db/db.go b/internal/db/db.go index 909ec2f..a20f36d 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -8,7 +8,7 @@ import ( "github.com/bvinc/go-sqlite-lite/sqlite3" "github.com/sirupsen/logrus" - "github.com/idlephysicist/cave-logger/internal/model" + "github.com/IdlePhysicist/cave-logger/internal/model" ) const datetime = `2006-01-02T15:04:05Z` diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index bf05417..7cf7720 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -8,7 +8,7 @@ import ( "github.com/gdamore/tcell" "github.com/rivo/tview" - "github.com/idlephysicist/cave-logger/internal/model" + "github.com/IdlePhysicist/cave-logger/internal/model" ) type cavers struct { diff --git a/internal/gui/caves.go b/internal/gui/caves.go index 2ee3ba8..abc4dca 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -8,7 +8,7 @@ import ( "github.com/gdamore/tcell" "github.com/rivo/tview" - "github.com/idlephysicist/cave-logger/internal/model" + "github.com/IdlePhysicist/cave-logger/internal/model" ) type caves struct { diff --git a/internal/gui/gui.go b/internal/gui/gui.go index 6ebc0c4..d0d1f93 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -8,8 +8,8 @@ import ( "github.com/rivo/tview" "github.com/gdamore/tcell" - "github.com/idlephysicist/cave-logger/internal/db" - "github.com/idlephysicist/cave-logger/internal/model" + "github.com/IdlePhysicist/cave-logger/internal/db" + "github.com/IdlePhysicist/cave-logger/internal/model" ) type panels struct { diff --git a/internal/gui/inspector.go b/internal/gui/inspector.go index e7b8341..eb7a5fb 100644 --- a/internal/gui/inspector.go +++ b/internal/gui/inspector.go @@ -6,7 +6,7 @@ import ( "github.com/rivo/tview" "github.com/gdamore/tcell" - "github.com/idlephysicist/cave-logger/internal/model" + "github.com/IdlePhysicist/cave-logger/internal/model" ) var inspectorFormat = map[string]string{ diff --git a/internal/gui/trips.go b/internal/gui/trips.go index b126bcf..cb7560d 100644 --- a/internal/gui/trips.go +++ b/internal/gui/trips.go @@ -7,7 +7,7 @@ import ( "github.com/gdamore/tcell" "github.com/rivo/tview" - "github.com/idlephysicist/cave-logger/internal/model" + "github.com/IdlePhysicist/cave-logger/internal/model" ) type trips struct { From 91760ab2722ad59fbd0e6e7b0a5dbd6e8e8d3e46 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sat, 15 Aug 2020 10:26:40 +0100 Subject: [PATCH 17/21] Revert "Commit to change import path to PascalCase" Because no one uses PascalCase import paths... This reverts commit fc294900edfe86ff7cc6132f322332f28d834e5a. --- cmd/main.go | 6 +++--- docker/Dockerfile | 2 +- go.mod | 8 +++++++- go.sum | 23 ++++++++++++++++++++++- internal/db/db.go | 2 +- internal/gui/cavers.go | 2 +- internal/gui/caves.go | 2 +- internal/gui/gui.go | 4 ++-- internal/gui/inspector.go | 2 +- internal/gui/trips.go | 2 +- 10 files changed, 40 insertions(+), 13 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 18fff79..0da5a20 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -10,9 +10,9 @@ import ( "github.com/sirupsen/logrus" flag "github.com/spf13/pflag" - "github.com/IdlePhysicist/cave-logger/internal/db" - "github.com/IdlePhysicist/cave-logger/internal/gui" - "github.com/IdlePhysicist/cave-logger/internal/model" + "github.com/idlephysicist/cave-logger/internal/db" + "github.com/idlephysicist/cave-logger/internal/gui" + "github.com/idlephysicist/cave-logger/internal/model" ) var commit, version, date string diff --git a/docker/Dockerfile b/docker/Dockerfile index 31c66c8..42a6e1a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,7 +10,7 @@ RUN apk add \ RUN mkdir /root/.config -WORKDIR /go/src/github.com/IdlePhysicist/cave-logger +WORKDIR /go/src/github.com/idlephysicist/cave-logger COPY go.* ./ #COPY go.sum . diff --git a/go.mod b/go.mod index 663b150..753e990 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,17 @@ -module github.com/IdlePhysicist/cave-logger +module github.com/idlephysicist/cave-logger go 1.13 require ( github.com/bvinc/go-sqlite-lite v0.6.1 github.com/gdamore/tcell v1.2.0 + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/kr/pretty v0.1.0 // indirect github.com/rivo/tview v0.0.0-20190829161255-f8bc69b90341 github.com/sirupsen/logrus v1.4.2 github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.4.0 // indirect + golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 // indirect + golang.org/x/text v0.3.2 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index 9de4f6f..92907be 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFD github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/bvinc/go-sqlite-lite v0.6.1 h1:JU8Rz5YAOZQiU3WEulKF084wfXpytRiqD2IaW2QjPz4= github.com/bvinc/go-sqlite-lite v0.6.1/go.mod h1:2GiE60NUdb0aNhDdY+LXgrqAVDpi2Ijc6dB6ZMp9x6s= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= @@ -11,6 +12,13 @@ github.com/gdamore/tcell v1.2.0 h1:ikixzsxc8K8o3V2/CEmyoEW8mJZaNYQQ3NP3VIQdUe4= github.com/gdamore/tcell v1.2.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= @@ -25,11 +33,24 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 h1:wYqz/tQaWUgGKyx+B/rssSE6wkIKdY5Ee6ryOmzarIg= +golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/db/db.go b/internal/db/db.go index a20f36d..909ec2f 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -8,7 +8,7 @@ import ( "github.com/bvinc/go-sqlite-lite/sqlite3" "github.com/sirupsen/logrus" - "github.com/IdlePhysicist/cave-logger/internal/model" + "github.com/idlephysicist/cave-logger/internal/model" ) const datetime = `2006-01-02T15:04:05Z` diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index 7cf7720..bf05417 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -8,7 +8,7 @@ import ( "github.com/gdamore/tcell" "github.com/rivo/tview" - "github.com/IdlePhysicist/cave-logger/internal/model" + "github.com/idlephysicist/cave-logger/internal/model" ) type cavers struct { diff --git a/internal/gui/caves.go b/internal/gui/caves.go index abc4dca..2ee3ba8 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -8,7 +8,7 @@ import ( "github.com/gdamore/tcell" "github.com/rivo/tview" - "github.com/IdlePhysicist/cave-logger/internal/model" + "github.com/idlephysicist/cave-logger/internal/model" ) type caves struct { diff --git a/internal/gui/gui.go b/internal/gui/gui.go index d0d1f93..6ebc0c4 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -8,8 +8,8 @@ import ( "github.com/rivo/tview" "github.com/gdamore/tcell" - "github.com/IdlePhysicist/cave-logger/internal/db" - "github.com/IdlePhysicist/cave-logger/internal/model" + "github.com/idlephysicist/cave-logger/internal/db" + "github.com/idlephysicist/cave-logger/internal/model" ) type panels struct { diff --git a/internal/gui/inspector.go b/internal/gui/inspector.go index eb7a5fb..e7b8341 100644 --- a/internal/gui/inspector.go +++ b/internal/gui/inspector.go @@ -6,7 +6,7 @@ import ( "github.com/rivo/tview" "github.com/gdamore/tcell" - "github.com/IdlePhysicist/cave-logger/internal/model" + "github.com/idlephysicist/cave-logger/internal/model" ) var inspectorFormat = map[string]string{ diff --git a/internal/gui/trips.go b/internal/gui/trips.go index cb7560d..b126bcf 100644 --- a/internal/gui/trips.go +++ b/internal/gui/trips.go @@ -7,7 +7,7 @@ import ( "github.com/gdamore/tcell" "github.com/rivo/tview" - "github.com/IdlePhysicist/cave-logger/internal/model" + "github.com/idlephysicist/cave-logger/internal/model" ) type trips struct { From da11a8812418ebafdeeb3e102eec5431f7c50dd6 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Sat, 15 Aug 2020 12:16:10 +0100 Subject: [PATCH 18/21] Nicely format display of SRT value in caves view Fixes #31 --- internal/gui/caves.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/gui/caves.go b/internal/gui/caves.go index 2ee3ba8..8024312 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -110,7 +110,7 @@ func (c *caves) setEntries(g *Gui) { SetMaxWidth(0). SetExpansion(1)) - table.SetCell(i+1, 3, tview.NewTableCell(strconv.FormatBool(cave.SRT)). + table.SetCell(i+1, 3, tview.NewTableCell(yesOrNo(cave.SRT)). SetTextColor(tview.Styles.PrimaryTextColor). SetMaxWidth(0). SetExpansion(1)) @@ -183,3 +183,11 @@ func (g *Gui) uniqueCountry(input []*model.Cave) []string { return uniq } + +func yesOrNo(val bool) string { + if val { + return `Y` + } else { + return `N` + } +} From a3680aab26a51d5b3cf9f177d711e5360174fc82 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Mon, 17 Aug 2020 23:36:48 +0100 Subject: [PATCH 19/21] First commit of advanced search Most string columns can now be selected and searched for. It could be done for other numerical and boolean fields too. But it will not be implemented for all fields. --- internal/gui/cavers.go | 26 ++++++++++++++++++++++---- internal/gui/caves.go | 31 +++++++++++++++++++++++++++---- internal/gui/keybindings.go | 14 +++++++++++--- internal/gui/panel.go | 2 +- internal/gui/trips.go | 23 ++++++++++++++++++----- 5 files changed, 79 insertions(+), 17 deletions(-) diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index bf05417..cb3e67d 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -14,7 +14,7 @@ import ( type cavers struct { *tview.Table cavers chan *model.Caver - filterWord string + filterCol, filterTerm string } func newCavers(g *Gui) *cavers { @@ -108,7 +108,7 @@ func (c *cavers) entries(g *Gui) { var filteredCavers []*model.Caver for _, caver := range cavers { - if strings.Index(caver.Name, c.filterWord) == -1 { + if c.search(caver) { continue } filteredCavers = append(filteredCavers, caver) @@ -125,8 +125,9 @@ func (c *cavers) unfocus() { c.SetSelectable(false, false) } -func (c *cavers) setFilterWord(word string) { - c.filterWord = word +func (c *cavers) setFilter(col, term string) { + c.filterCol = col + c.filterTerm = term } func (c *cavers) monitoringCavers(g *Gui) { @@ -157,3 +158,20 @@ func (g *Gui) uniqueClubs(input []*model.Caver) []string { return uniq } + +func (c *cavers) search(caver *model.Caver) bool { + switch c.filterCol { + case "name": + if strings.Index(caver.Name, c.filterTerm) == -1 { + return true + } + return false + case "club": + if strings.Index(caver.Club, c.filterTerm) == -1 { + return true + } + return false + default: + return false + } +} diff --git a/internal/gui/caves.go b/internal/gui/caves.go index 8024312..592706a 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -14,7 +14,7 @@ import ( type caves struct { *tview.Table caves chan *model.Cave - filterWord string + filterCol, filterTerm string } func newCaves(g *Gui) *caves { @@ -63,7 +63,7 @@ func (c *caves) entries(g *Gui) { var filteredCaves []*model.Cave for _, cave := range caves { - if strings.Index(cave.Name, c.filterWord) == -1 { + if c.search(cave) { continue } filteredCaves = append(filteredCaves, cave) @@ -137,8 +137,9 @@ func (c *caves) unfocus() { c.SetSelectable(false, false) } -func (c *caves) setFilterWord(word string) { - c.filterWord = word +func (c *caves) setFilter(col, term string) { + c.filterCol = col + c.filterTerm = term } func (c *caves) monitoringCaves(g *Gui) { @@ -191,3 +192,25 @@ func yesOrNo(val bool) string { return `N` } } + +func (c *caves) search(cave *model.Cave) bool { + switch c.filterCol { + case "name": + if strings.Index(cave.Name, c.filterTerm) == -1 { + return true + } + return false + case "region": + if strings.Index(cave.Region, c.filterTerm) == -1 { + return true + } + return false + case "country": + if strings.Index(cave.Country, c.filterTerm) == -1 { + return true + } + return false + default: + return false + } +} diff --git a/internal/gui/keybindings.go b/internal/gui/keybindings.go index c06bffc..08bf2ee 100644 --- a/internal/gui/keybindings.go +++ b/internal/gui/keybindings.go @@ -1,6 +1,8 @@ package gui import ( + "strings" + "github.com/gdamore/tcell" "github.com/rivo/tview" ) @@ -27,7 +29,7 @@ func (g *Gui) setGlobalKeybinding(event *tcell.EventKey) { func (g *Gui) filter() { currentPanel := g.state.panels.panel[g.state.panels.currentPanel] - currentPanel.setFilterWord("") + currentPanel.setFilter("", "") currentPanel.updateEntries(g) viewName := "filter" @@ -55,8 +57,14 @@ func (g *Gui) filter() { }) searchInput.SetChangedFunc(func(text string) { - currentPanel.setFilterWord(text) - currentPanel.updateEntries(g) + if strings.Contains(text, "/") { + textSl := strings.Split(text, "/") + + if len(textSl) == 2 { + currentPanel.setFilter(textSl[0], textSl[1]) + currentPanel.updateEntries(g) + } + } }) g.pages.AddAndSwitchToPage(viewName, g.modal(searchInput, 80, 3), true).ShowPage("main") diff --git a/internal/gui/panel.go b/internal/gui/panel.go index 0e1becc..aa0c718 100644 --- a/internal/gui/panel.go +++ b/internal/gui/panel.go @@ -8,5 +8,5 @@ type panel interface { setKeybinding(*Gui) focus(*Gui) unfocus() - setFilterWord(string) + setFilter(string, string) } diff --git a/internal/gui/trips.go b/internal/gui/trips.go index b126bcf..e573465 100644 --- a/internal/gui/trips.go +++ b/internal/gui/trips.go @@ -12,8 +12,8 @@ import ( type trips struct { *tview.Table - trips chan *model.Log - filterWord string + trips chan *model.Log + filterCol, filterTerm string } func newTrips(g *Gui) *trips { @@ -66,7 +66,7 @@ func (t *trips) entries(g *Gui) { var filteredTrips []*model.Log for _, trip := range trips { - if strings.Index(trip.Cave, t.filterWord) == -1 { + if t.search(trip) { continue } filteredTrips = append(filteredTrips, trip) @@ -128,8 +128,9 @@ func (t *trips) unfocus() { t.SetSelectable(false, false) } -func (t *trips) setFilterWord(word string) { - t.filterWord = word +func (t *trips) setFilter(col, term string) { + t.filterCol = col + t.filterTerm = term } func (t *trips) monitoringTrips(g *Gui) { @@ -146,3 +147,15 @@ LOOP: } } } + +func (t *trips) search(trip *model.Log) bool { + switch t.filterCol { + case "cave": + if strings.Index(trip.Cave, t.filterTerm) == -1 { + return true + } + return false + default: + return false + } +} From eeabbfd905769aa0d671aae92c31571aa08639ce Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Tue, 18 Aug 2020 10:48:07 +0100 Subject: [PATCH 20/21] Case insensitive search implemented Default columns have also been implemented for each view pane too. --- internal/gui/cavers.go | 6 +++--- internal/gui/caves.go | 8 ++++---- internal/gui/keybindings.go | 6 +++--- internal/gui/trips.go | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/gui/cavers.go b/internal/gui/cavers.go index cb3e67d..61b02f6 100644 --- a/internal/gui/cavers.go +++ b/internal/gui/cavers.go @@ -161,13 +161,13 @@ func (g *Gui) uniqueClubs(input []*model.Caver) []string { func (c *cavers) search(caver *model.Caver) bool { switch c.filterCol { - case "name": - if strings.Index(caver.Name, c.filterTerm) == -1 { + case "name", "": + if strings.Index(strings.ToLower(caver.Name), c.filterTerm) == -1 { return true } return false case "club": - if strings.Index(caver.Club, c.filterTerm) == -1 { + if strings.Index(strings.ToLower(caver.Club), c.filterTerm) == -1 { return true } return false diff --git a/internal/gui/caves.go b/internal/gui/caves.go index 592706a..dfcb398 100644 --- a/internal/gui/caves.go +++ b/internal/gui/caves.go @@ -195,18 +195,18 @@ func yesOrNo(val bool) string { func (c *caves) search(cave *model.Cave) bool { switch c.filterCol { - case "name": - if strings.Index(cave.Name, c.filterTerm) == -1 { + case "name", "": + if strings.Index(strings.ToLower(cave.Name), c.filterTerm) == -1 { return true } return false case "region": - if strings.Index(cave.Region, c.filterTerm) == -1 { + if strings.Index(strings.ToLower(cave.Region), c.filterTerm) == -1 { return true } return false case "country": - if strings.Index(cave.Country, c.filterTerm) == -1 { + if strings.Index(strings.ToLower(cave.Country), c.filterTerm) == -1 { return true } return false diff --git a/internal/gui/keybindings.go b/internal/gui/keybindings.go index 08bf2ee..abfd4ac 100644 --- a/internal/gui/keybindings.go +++ b/internal/gui/keybindings.go @@ -33,8 +33,8 @@ func (g *Gui) filter() { currentPanel.updateEntries(g) viewName := "filter" - searchInput := tview.NewInputField().SetLabel("Parameter") - searchInput.SetLabelWidth(10) + searchInput := tview.NewInputField().SetLabel("Column/Parameter") + searchInput.SetLabelWidth(17) searchInput.SetTitle(" Filter ") searchInput.SetTitleAlign(tview.AlignLeft) searchInput.SetBorder(true) @@ -58,7 +58,7 @@ func (g *Gui) filter() { searchInput.SetChangedFunc(func(text string) { if strings.Contains(text, "/") { - textSl := strings.Split(text, "/") + textSl := strings.Split(strings.ToLower(text), "/") if len(textSl) == 2 { currentPanel.setFilter(textSl[0], textSl[1]) diff --git a/internal/gui/trips.go b/internal/gui/trips.go index e573465..943c5a7 100644 --- a/internal/gui/trips.go +++ b/internal/gui/trips.go @@ -150,8 +150,8 @@ LOOP: func (t *trips) search(trip *model.Log) bool { switch t.filterCol { - case "cave": - if strings.Index(trip.Cave, t.filterTerm) == -1 { + case "cave", "": + if strings.Index(strings.ToLower(trip.Cave), t.filterTerm) == -1 { return true } return false From 4080b1de981fd000aa676e54de472f10648a26c8 Mon Sep 17 00:00:00 2001 From: IdlePhysicist Date: Wed, 19 Aug 2020 19:04:40 +0100 Subject: [PATCH 21/21] Updated readme and screenshot --- .gitignore | 2 +- README.md | 21 --------------------- assets/screenshot.png | Bin 69895 -> 49998 bytes 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 2b36552..486a0b9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ # Project *.db -config/config.yml +config/ build/ _old diff --git a/README.md b/README.md index 7a42359..0a84985 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ ## Summary Cave Logger is a basic SQLite database interface written in Go, and it allows cavers to track the caves that they have been to, who with, and when. -I indend to make the code more generic to allow other outdoorsy people to use this app with less fuss. - ## What It Looks Like

@@ -35,22 +33,3 @@ To run in docker: 2. Follow step 3 from above 3. `./docker/run.sh` -## Help - -### Keybindings - -| Key | Function | -|:---:|:--------:| -| q | quit | -| n | new | -| u | update | -| d | delete | -| j | down | -| k | up | -| g | end | -| G | home | -| Tab | see menu | -| Enter | inspect record | - -### Menu -In the Menu the Tab key will select the highlighted item, and hitting Tab again will navigate to the Menu. diff --git a/assets/screenshot.png b/assets/screenshot.png index 097b2b163ab026bb0ed280991a33d0814c9a586c..6b84395a2dd207bd379d84f36792b7bb67fa5fa7 100644 GIT binary patch literal 49998 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 LQ_KF<^6viuw))&? literal 69895 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 z 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⪙LDkaB0mV4pPL_(`X(u=)uV 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