Quellcode durchsuchen

Working on the TUI

andrea vor 4 Monaten
Ursprung
Commit
ea38c49009
75 geänderte Dateien mit 442 neuen und 147 gelöschten Zeilen
  1. 89 0
      cmd/probo-cli/list/delegate.go
  2. 158 0
      cmd/probo-cli/list/list.go
  3. 22 1
      cmd/probo-cli/main.go
  4. 31 0
      cmd/probo-cli/participant.go
  5. 4 58
      cmd/probo-cli/session.go
  6. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_0249a3d3-d62c-469f-916a-22ef2210df01.json
  7. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_0ad742cd-80f4-4905-8ddb-66b5fa2dba51.json
  8. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_196b6928-079f-41d2-bb98-cbf0a243e1bc.json
  9. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_1eb1ad71-3351-42ce-a7aa-64a8139310e5.json
  10. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_214f5576-fbe2-42ec-be78-65649c422534.json
  11. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_40b8222c-e969-4d80-b467-91b9c9f36baa.json
  12. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_4bb4d670-af9c-45b8-b556-2a330b684f0d.json
  13. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_4d3a6924-bdc9-4bf3-b136-41591cf88cb6.json
  14. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_53a36e9b-0c81-4540-ab1e-55bb3b854f7b.json
  15. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_563f99bd-b2b5-4427-b741-02bf362db5cf.json
  16. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_57e7a11a-5b96-4329-be3b-13270a11cb9f.json
  17. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_5bf90820-9ee8-4e25-a9c5-9ba028a5fd36.json
  18. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_69bb12a7-5e05-426f-b975-e5d753bd0e59.json
  19. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_6a14a5a0-c13d-4819-9d35-96f1bfff821a.json
  20. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_7813d5dd-c50f-4591-b63b-67dc3929ac3d.json
  21. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_7d6aa89f-b10f-4fef-8dd6-a96a97a83d1c.json
  22. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_7f8aaa16-1ec3-465d-97a8-845e7e6028d3.json
  23. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_8568e115-cc96-43e6-859c-966bdfc10c61.json
  24. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_861aa787-f611-4a31-8e05-ec7cff884a2d.json
  25. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_88182c1f-8cba-42d2-ac91-a5a228bb267e.json
  26. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_8b0b31cc-4d71-4ef9-b86b-444e5432910d.json
  27. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_8b2c7934-bb43-45e9-b81e-fd46a109bf69.json
  28. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_8dfcac20-0ff5-471d-add5-b622ebc12e80.json
  29. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_9208c855-6b05-4eb3-81a7-bfb44a701594.json
  30. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_95c7cf92-8c92-4b9f-bb8b-1ead62c771a2.json
  31. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_983b9fb6-c00e-4ccf-87fa-bc88a1d036d6.json
  32. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_a89835cb-52ef-4ebb-9101-ae0dbf1da81c.json
  33. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_b0bda8b2-282e-423d-b585-a4d99eecdd85.json
  34. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_baf59465-a2ea-4086-83c4-192e1d2ece38.json
  35. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_c1166a0b-d66b-4c7a-b60b-fc2f123df942.json
  36. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_c2c1cf5e-c7fa-4881-ae6a-1f303dc83253.json
  37. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_c33a1b26-f68f-4d31-9244-1074c7eab259.json
  38. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_c8d55f76-9229-4235-a7e0-19e24be4a7eb.json
  39. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_ceedbd21-8d55-403a-bd5e-fa77578aed6f.json
  40. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_dba0fb9f-9843-4ab6-9cdd-0ad0312c994b.json
  41. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_f45ea401-618e-4416-9ce6-0a7f0368bbbc.json
  42. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_f7590083-21f9-41d3-baf6-9bc8d1bf93cb.json
  43. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_faa8b2ee-2cc1-4a41-be39-df88282554f2.json
  44. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_fb2fcb6b-f5b2-4176-9c9c-b75fb0352320.json
  45. 0 0
      cmd/probo-cli/testdata.bk/exams/exam_fd7a5639-d764-42eb-a2e5-b69702fc2a77.json
  46. 0 1
      cmd/probo-cli/testdata.bk/participants/andrea.json
  47. 0 1
      cmd/probo-cli/testdata.bk/participants/participant.json
  48. 0 11
      cmd/probo-cli/testdata.bk/quizzes/quiz_1.md
  49. 0 11
      cmd/probo-cli/testdata.bk/quizzes/quiz_2.md
  50. 0 11
      cmd/probo-cli/testdata.bk/quizzes/quiz_3.md
  51. 0 1
      cmd/probo-cli/testdata.bk/responses/response_dba0fb9f-9843-4ab6-9cdd-0ad0312c994b.json
  52. 0 0
      cmd/probo-cli/testdata/exams/exam_0c3393eb-34c1-487b-b0c5-ad2df2ea91b6.json
  53. 0 0
      cmd/probo-cli/testdata/exams/exam_35654b75-e874-45ba-8c3e-ba55a7d6fc2a.json
  54. 0 0
      cmd/probo-cli/testdata/exams/exam_53e25b31-c050-4cf8-8f7a-db46934d9bd6.json
  55. 0 0
      cmd/probo-cli/testdata/exams/exam_66079641-c419-4ee7-b16f-d960167dd8ac.json
  56. 0 0
      cmd/probo-cli/testdata/exams/exam_a0a419aa-31a8-4add-8a39-33a8cb0be381.json
  57. 0 0
      cmd/probo-cli/testdata/exams/exam_ccd199ae-2ff9-4bc0-9b7a-045010e0e564.json
  58. 0 1
      cmd/probo-cli/testdata/participants/andrea.json
  59. 0 1
      cmd/probo-cli/testdata/participants/participant.json
  60. 0 11
      cmd/probo-cli/testdata/quizzes/quiz_1.md
  61. 0 11
      cmd/probo-cli/testdata/quizzes/quiz_2.md
  62. 0 11
      cmd/probo-cli/testdata/quizzes/quiz_3.md
  63. 0 1
      cmd/probo-cli/testdata/responses/response_0c3393eb-34c1-487b-b0c5-ad2df2ea91b6.json
  64. 0 1
      cmd/probo-cli/testdata/responses/response_a0a419aa-31a8-4add-8a39-33a8cb0be381.json
  65. 0 0
      cmd/probo-cli/testdata/sessions/session_d580ecd7-486a-40d1-9512-a5121f5b1ebb.json
  66. 66 0
      cmd/probo-cli/textinput/textinput.go
  67. 2 1
      go.mod
  68. 2 0
      go.sum
  69. 20 5
      lib/models/participant.go
  70. 30 3
      lib/store/file/participant.go
  71. 1 1
      lib/store/file/participant_test.go
  72. 1 1
      lib/store/file/testdata/exams/participants/jack.json
  73. 1 1
      lib/store/file/testdata/exams/participants/john.json
  74. 1 1
      lib/store/file/testdata/exams/participants/wendy.json
  75. 14 2
      lib/store/participant.go

+ 89 - 0
cmd/probo-cli/list/delegate.go

@@ -0,0 +1,89 @@
+package list
+
+import (
+	"github.com/charmbracelet/bubbles/key"
+	"github.com/charmbracelet/bubbles/list"
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+func newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
+	d := list.NewDefaultDelegate()
+
+	d.UpdateFunc = func(msg tea.Msg, m *list.Model) tea.Cmd {
+		var title string
+
+		if i, ok := m.SelectedItem().(Item); ok {
+			title = i.Title()
+		} else {
+			return nil
+		}
+
+		switch msg := msg.(type) {
+		case tea.KeyMsg:
+			switch {
+			case key.Matches(msg, keys.choose):
+				return m.NewStatusMessage(statusMessageStyle("You chose " + title))
+
+			case key.Matches(msg, keys.remove):
+				index := m.Index()
+				m.RemoveItem(index)
+				if len(m.Items()) == 0 {
+					keys.remove.SetEnabled(false)
+				}
+				return m.NewStatusMessage(statusMessageStyle("Deleted " + title))
+			}
+		}
+
+		return nil
+	}
+
+	help := []key.Binding{keys.choose, keys.remove}
+
+	d.ShortHelpFunc = func() []key.Binding {
+		return help
+	}
+
+	d.FullHelpFunc = func() [][]key.Binding {
+		return [][]key.Binding{help}
+	}
+
+	return d
+}
+
+type delegateKeyMap struct {
+	choose key.Binding
+	remove key.Binding
+}
+
+// Additional short help entries. This satisfies the help.KeyMap interface and
+// is entirely optional.
+func (d delegateKeyMap) ShortHelp() []key.Binding {
+	return []key.Binding{
+		d.choose,
+		d.remove,
+	}
+}
+
+// Additional full help entries. This satisfies the help.KeyMap interface and
+// is entirely optional.
+func (d delegateKeyMap) FullHelp() [][]key.Binding {
+	return [][]key.Binding{
+		{
+			d.choose,
+			d.remove,
+		},
+	}
+}
+
+func newDelegateKeyMap() *delegateKeyMap {
+	return &delegateKeyMap{
+		choose: key.NewBinding(
+			key.WithKeys("enter"),
+			key.WithHelp("enter", "choose"),
+		),
+		remove: key.NewBinding(
+			key.WithKeys("x", "backspace"),
+			key.WithHelp("x", "delete"),
+		),
+	}
+}

+ 158 - 0
cmd/probo-cli/list/list.go

@@ -0,0 +1,158 @@
+package list
+
+import (
+	"github.com/charmbracelet/bubbles/key"
+	"github.com/charmbracelet/bubbles/list"
+	tea "github.com/charmbracelet/bubbletea"
+	"github.com/charmbracelet/lipgloss"
+)
+
+var (
+	appStyle = lipgloss.NewStyle().Padding(1, 2)
+
+	titleStyle = lipgloss.NewStyle().
+			Foreground(lipgloss.Color("#FFFDF5")).
+			Background(lipgloss.Color("#25A065")).
+			Padding(0, 1)
+
+	statusMessageStyle = lipgloss.NewStyle().
+				Foreground(lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#04B575"}).
+				Render
+)
+
+type Item struct {
+	title       string
+	description string
+}
+
+func NewItem(title string, description string) Item {
+	return Item{title, description}
+}
+
+func (i Item) Title() string       { return i.title }
+func (i Item) Description() string { return i.description }
+func (i Item) FilterValue() string { return i.title }
+
+type listKeyMap struct {
+	toggleSpinner    key.Binding
+	toggleTitleBar   key.Binding
+	toggleStatusBar  key.Binding
+	togglePagination key.Binding
+	toggleHelpMenu   key.Binding
+}
+
+func newListKeyMap() *listKeyMap {
+	return &listKeyMap{
+		toggleSpinner: key.NewBinding(
+			key.WithKeys("s"),
+			key.WithHelp("s", "toggle spinner"),
+		),
+		toggleTitleBar: key.NewBinding(
+			key.WithKeys("T"),
+			key.WithHelp("T", "toggle title"),
+		),
+		toggleStatusBar: key.NewBinding(
+			key.WithKeys("S"),
+			key.WithHelp("S", "toggle status"),
+		),
+		togglePagination: key.NewBinding(
+			key.WithKeys("P"),
+			key.WithHelp("P", "toggle pagination"),
+		),
+		toggleHelpMenu: key.NewBinding(
+			key.WithKeys("H"),
+			key.WithHelp("H", "toggle help"),
+		),
+	}
+}
+
+type model struct {
+	list         list.Model
+	keys         *listKeyMap
+	delegateKeys *delegateKeyMap
+}
+
+func NewList(title string, items []list.Item) model {
+	var (
+		delegateKeys = newDelegateKeyMap()
+		listKeys     = newListKeyMap()
+	)
+
+	// Setup list
+	delegate := newItemDelegate(delegateKeys)
+	groceryList := list.New(items, delegate, 0, 0)
+	groceryList.Title = title
+	groceryList.Styles.Title = titleStyle
+	groceryList.AdditionalFullHelpKeys = func() []key.Binding {
+		return []key.Binding{
+			listKeys.toggleSpinner,
+			listKeys.toggleTitleBar,
+			listKeys.toggleStatusBar,
+			listKeys.togglePagination,
+			listKeys.toggleHelpMenu,
+		}
+	}
+
+	return model{
+		list:         groceryList,
+		keys:         listKeys,
+		delegateKeys: delegateKeys,
+	}
+}
+
+func (m model) Init() tea.Cmd {
+	return tea.EnterAltScreen
+}
+
+func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	var cmds []tea.Cmd
+
+	switch msg := msg.(type) {
+	case tea.WindowSizeMsg:
+		h, v := appStyle.GetFrameSize()
+		m.list.SetSize(msg.Width-h, msg.Height-v)
+
+	case tea.KeyMsg:
+		// Don't match any of the keys below if we're actively filtering.
+		if m.list.FilterState() == list.Filtering {
+			break
+		}
+
+		switch {
+		case key.Matches(msg, m.keys.toggleSpinner):
+			cmd := m.list.ToggleSpinner()
+			return m, cmd
+
+		case key.Matches(msg, m.keys.toggleTitleBar):
+			v := !m.list.ShowTitle()
+			m.list.SetShowTitle(v)
+			m.list.SetShowFilter(v)
+			m.list.SetFilteringEnabled(v)
+			return m, nil
+
+		case key.Matches(msg, m.keys.toggleStatusBar):
+			m.list.SetShowStatusBar(!m.list.ShowStatusBar())
+			return m, nil
+
+		case key.Matches(msg, m.keys.togglePagination):
+			m.list.SetShowPagination(!m.list.ShowPagination())
+			return m, nil
+
+		case key.Matches(msg, m.keys.toggleHelpMenu):
+			m.list.SetShowHelp(!m.list.ShowHelp())
+			return m, nil
+
+		}
+	}
+
+	// This will also call our delegate's update function.
+	newListModel, cmd := m.list.Update(msg)
+	m.list = newListModel
+	cmds = append(cmds, cmd)
+
+	return m, tea.Batch(cmds...)
+}
+
+func (m model) View() string {
+	return appStyle.Render(m.list.View())
+}

+ 22 - 1
cmd/probo-cli/main.go

@@ -4,15 +4,24 @@ import (
 	"log"
 	"log/slog"
 	"os"
+	"time"
 
 	"git.andreafazzi.eu/andrea/probo/lib/store/file"
+	"github.com/lmittmann/tint"
 	"github.com/urfave/cli/v2"
 )
 
 func main() {
+	slog.SetDefault(slog.New(
+		tint.NewHandler(os.Stdout, &tint.Options{
+			Level:      slog.LevelInfo,
+			TimeFormat: time.Kitchen,
+		}),
+	))
+
 	_, err := os.Stat(file.DefaultBaseDir)
 	if err != nil {
-		slog.Info("Base directory not found. Create the folder structure...")
+		slog.Info("Base directory not found. Creating the folder structure...")
 		for _, dir := range file.Dirs {
 			err := os.MkdirAll(dir, os.ModePerm)
 			if err != nil {
@@ -51,6 +60,18 @@ func main() {
 					},
 				},
 			},
+			{
+				Name:    "participant",
+				Aliases: []string{"p"},
+				Usage:   "options for command 'participant'",
+				Subcommands: []*cli.Command{
+					{
+						Name:   "import",
+						Usage:  "Import participants from a CSV file",
+						Action: importCSV,
+					},
+				},
+			},
 		},
 	}
 

+ 31 - 0
cmd/probo-cli/participant.go

@@ -0,0 +1,31 @@
+package main
+
+import (
+	"fmt"
+	"log/slog"
+
+	"git.andreafazzi.eu/andrea/probo/lib/store/file"
+	"github.com/urfave/cli/v2"
+)
+
+func importCSV(cCtx *cli.Context) error {
+	path := cCtx.Args().First()
+	if path == "" {
+		return cli.Exit("Path for the CSV file not given.", 1)
+	}
+
+	pStore, err := file.NewParticipantDefaultFileStore()
+	if err != nil {
+		return cli.Exit(fmt.Sprintf("An error occurred: %v", err), 1)
+	}
+
+	participants, err := pStore.ImportCSV(path)
+	if err != nil {
+		return cli.Exit(fmt.Sprintf("An error occurred: %v", err), 1)
+	}
+
+	slog.Info("Imported participants from csv file", "nParticipants", len(participants))
+
+	return nil
+
+}

+ 4 - 58
cmd/probo-cli/session.go

@@ -4,67 +4,13 @@ import (
 	"fmt"
 	"log"
 
+	"git.andreafazzi.eu/andrea/probo/cmd/probo-cli/textinput"
 	"git.andreafazzi.eu/andrea/probo/lib/sessionmanager"
 	"git.andreafazzi.eu/andrea/probo/lib/store/file"
-	"github.com/charmbracelet/bubbles/textinput"
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/urfave/cli/v2"
 )
 
-type model struct {
-	textInput textinput.Model
-	err       error
-}
-
-type (
-	errMsg error
-)
-
-func initialModel() *model {
-	ti := textinput.New()
-	ti.Placeholder = "My exam session"
-	ti.Focus()
-	ti.CharLimit = 156
-	ti.Width = 20
-
-	return &model{
-		textInput: ti,
-		err:       nil,
-	}
-}
-
-func (m *model) Init() tea.Cmd {
-	return textinput.Blink
-}
-
-func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-	var cmd tea.Cmd
-
-	switch msg := msg.(type) {
-	case tea.KeyMsg:
-		switch msg.Type {
-		case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
-			return m, tea.Quit
-		}
-
-	// We handle errors just like any other message
-	case errMsg:
-		m.err = msg
-		return m, nil
-	}
-
-	m.textInput, cmd = m.textInput.Update(msg)
-	return m, cmd
-}
-
-func (m *model) View() string {
-	return fmt.Sprintf(
-		"\nPlease insert the name of the session\n\n%s\n\n%s",
-		m.textInput.View(),
-		"(esc to quit)",
-	) + "\n"
-}
-
 func push(cCtx *cli.Context) error {
 	pStore, err := file.NewParticipantDefaultFileStore()
 	if err != nil {
@@ -80,12 +26,12 @@ func push(cCtx *cli.Context) error {
 	sessionName := cCtx.Args().First()
 
 	if cCtx.Args().Len() < 1 {
-		m := initialModel()
-		p := tea.NewProgram(m)
+		input := textinput.NewTextInput("My exam session")
+		p := tea.NewProgram(input)
 		if _, err := p.Run(); err != nil {
 			log.Fatal(err)
 		}
-		sessionName = m.textInput.Value()
+		sessionName = input.Value()
 	}
 
 	sStore, err := file.NewDefaultSessionFileStore()

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_0249a3d3-d62c-469f-916a-22ef2210df01.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_0ad742cd-80f4-4905-8ddb-66b5fa2dba51.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_196b6928-079f-41d2-bb98-cbf0a243e1bc.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_1eb1ad71-3351-42ce-a7aa-64a8139310e5.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_214f5576-fbe2-42ec-be78-65649c422534.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_40b8222c-e969-4d80-b467-91b9c9f36baa.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_4bb4d670-af9c-45b8-b556-2a330b684f0d.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_4d3a6924-bdc9-4bf3-b136-41591cf88cb6.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_53a36e9b-0c81-4540-ab1e-55bb3b854f7b.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_563f99bd-b2b5-4427-b741-02bf362db5cf.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_57e7a11a-5b96-4329-be3b-13270a11cb9f.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_5bf90820-9ee8-4e25-a9c5-9ba028a5fd36.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_69bb12a7-5e05-426f-b975-e5d753bd0e59.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_6a14a5a0-c13d-4819-9d35-96f1bfff821a.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_7813d5dd-c50f-4591-b63b-67dc3929ac3d.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_7d6aa89f-b10f-4fef-8dd6-a96a97a83d1c.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_7f8aaa16-1ec3-465d-97a8-845e7e6028d3.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_8568e115-cc96-43e6-859c-966bdfc10c61.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_861aa787-f611-4a31-8e05-ec7cff884a2d.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_88182c1f-8cba-42d2-ac91-a5a228bb267e.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_8b0b31cc-4d71-4ef9-b86b-444e5432910d.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_8b2c7934-bb43-45e9-b81e-fd46a109bf69.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_8dfcac20-0ff5-471d-add5-b622ebc12e80.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_9208c855-6b05-4eb3-81a7-bfb44a701594.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_95c7cf92-8c92-4b9f-bb8b-1ead62c771a2.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_983b9fb6-c00e-4ccf-87fa-bc88a1d036d6.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_a89835cb-52ef-4ebb-9101-ae0dbf1da81c.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_b0bda8b2-282e-423d-b585-a4d99eecdd85.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_baf59465-a2ea-4086-83c4-192e1d2ece38.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_c1166a0b-d66b-4c7a-b60b-fc2f123df942.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_c2c1cf5e-c7fa-4881-ae6a-1f303dc83253.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_c33a1b26-f68f-4d31-9244-1074c7eab259.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_c8d55f76-9229-4235-a7e0-19e24be4a7eb.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_ceedbd21-8d55-403a-bd5e-fa77578aed6f.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_dba0fb9f-9843-4ab6-9cdd-0ad0312c994b.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_f45ea401-618e-4416-9ce6-0a7f0368bbbc.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_f7590083-21f9-41d3-baf6-9bc8d1bf93cb.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_faa8b2ee-2cc1-4a41-be39-df88282554f2.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_fb2fcb6b-f5b2-4176-9c9c-b75fb0352320.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata.bk/exams/exam_fd7a5639-d764-42eb-a2e5-b69702fc2a77.json


+ 0 - 1
cmd/probo-cli/testdata.bk/participants/andrea.json

@@ -1 +0,0 @@
-{"id":"564d0c7f-4840-443c-8325-ecd02d03624d","created_at":"2023-12-05T21:11:37.566330708+01:00","updated_at":"2023-12-14T17:45:08.904532708+01:00","Firstname":"Andrea","Lastname":"Fazzi","Token":"333222","Attributes":{"class":"1 D LIN"}}

+ 0 - 1
cmd/probo-cli/testdata.bk/participants/participant.json

@@ -1 +0,0 @@
-{"id":"1234","created_at":"2023-12-05T21:11:37.566330708+01:00","updated_at":"2023-12-14T17:45:08.904646212+01:00","Firstname":"Giuly","Lastname":"Mon Amour","Token":"111222","Attributes":{"class":"1 D LIN"}}

+ 0 - 11
cmd/probo-cli/testdata.bk/quizzes/quiz_1.md

@@ -1,11 +0,0 @@
----
-id: 908bb025-6370-4273-8448-8470f9336f26
-created_at: 2023-12-05T21:22:06.322398595+01:00
-updated_at: 0001-01-01T00:00:00Z
----
-Chi è l'#amore più grande della mia vita?
-
-* Giuly (❤️)
-* Daisy
-* Jenny
-* Lilly

+ 0 - 11
cmd/probo-cli/testdata.bk/quizzes/quiz_2.md

@@ -1,11 +0,0 @@
----
-id: 6e989533-276c-45d5-9048-24a575289ed9
-created_at: 2023-12-12T09:25:01.783698764+01:00
-updated_at: 0001-01-01T00:00:00Z
----
-Chi è la ragazza più meravigliosa del mondo?
-
-* Daisy
-* Jenny
-* Giuly (❤️)
-* Lilly

+ 0 - 11
cmd/probo-cli/testdata.bk/quizzes/quiz_3.md

@@ -1,11 +0,0 @@
----
-id: 41c2a4a8-04b3-42c2-bbb7-10b8908fcf36
-created_at: 2023-12-12T09:25:01.783808508+01:00
-updated_at: 0001-01-01T00:00:00Z
----
-Chi è la donna più affascinante dell'Universo?
-
-* Daisy
-* Giuly (❤️)
-* Jenny
-* Lilly

+ 0 - 1
cmd/probo-cli/testdata.bk/responses/response_dba0fb9f-9843-4ab6-9cdd-0ad0312c994b.json

@@ -1 +0,0 @@
-{"id":"dba0fb9f-9843-4ab6-9cdd-0ad0312c994b","created_at":"2023-12-14T17:43:49.440956408+01:00","updated_at":"2023-12-14T17:45:08.916225135+01:00","Questions":{"3ca34fa3-adce-4cbb-b35a-650e08f280d1":"35d11599-b2e0-444e-80d5-24b391f151d0","a462361c-ff0f-4805-ae7b-2989e61bb950":"35d11599-b2e0-444e-80d5-24b391f151d0","d6723c40-1b1e-48fa-a9ce-6198a296d89e":"35d11599-b2e0-444e-80d5-24b391f151d0"}}

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata/exams/exam_0c3393eb-34c1-487b-b0c5-ad2df2ea91b6.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata/exams/exam_35654b75-e874-45ba-8c3e-ba55a7d6fc2a.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata/exams/exam_53e25b31-c050-4cf8-8f7a-db46934d9bd6.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata/exams/exam_66079641-c419-4ee7-b16f-d960167dd8ac.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata/exams/exam_a0a419aa-31a8-4add-8a39-33a8cb0be381.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata/exams/exam_ccd199ae-2ff9-4bc0-9b7a-045010e0e564.json


+ 0 - 1
cmd/probo-cli/testdata/participants/andrea.json

@@ -1 +0,0 @@
-{"id":"564d0c7f-4840-443c-8325-ecd02d03624d","created_at":"2023-12-05T21:11:37.566330708+01:00","updated_at":"2023-12-17T18:48:05.374673068+01:00","Firstname":"Andrea","Lastname":"Fazzi","Token":"333222","Attributes":{"class":"1 D LIN"}}

+ 0 - 1
cmd/probo-cli/testdata/participants/participant.json

@@ -1 +0,0 @@
-{"id":"1234","created_at":"2023-12-05T21:11:37.566330708+01:00","updated_at":"2023-12-17T18:48:05.374735039+01:00","Firstname":"Giuly","Lastname":"Mon Amour","Token":"111222","Attributes":{"class":"1 D LIN"}}

+ 0 - 11
cmd/probo-cli/testdata/quizzes/quiz_1.md

@@ -1,11 +0,0 @@
----
-id: 44812a5d-3391-4401-8b81-bd35df1ef589
-created_at: 2023-12-14T17:58:09.062782001+01:00
-updated_at: 0001-01-01T00:00:00Z
----
-Quanto fa 1+1?
-
-* 2
-* 3
-* 4
-* 0

+ 0 - 11
cmd/probo-cli/testdata/quizzes/quiz_2.md

@@ -1,11 +0,0 @@
----
-id: 64fb2348-e9b7-497a-97bb-2c1213a4d691
-created_at: 2023-12-14T17:58:09.06290311+01:00
-updated_at: 0001-01-01T00:00:00Z
----
-Quanto da 3*3?
-
-* 9
-* 7
-* 2
-* 1

+ 0 - 11
cmd/probo-cli/testdata/quizzes/quiz_3.md

@@ -1,11 +0,0 @@
----
-id: 67bb31ec-11fe-4e41-9554-a49c8c3dc9ca
-created_at: 2023-12-17T15:05:05.91016291+01:00
-updated_at: 0001-01-01T00:00:00Z
----
-Un campo elettrico
-
-* E' un campo di forse conservative
-* E' un campo di forze non conservative
-* E' un campo di grano
-* E' un campo di calcio

+ 0 - 1
cmd/probo-cli/testdata/responses/response_0c3393eb-34c1-487b-b0c5-ad2df2ea91b6.json

@@ -1 +0,0 @@
-{"id":"0c3393eb-34c1-487b-b0c5-ad2df2ea91b6","created_at":"2023-12-17T18:25:14.626099943+01:00","updated_at":"2023-12-17T18:33:13.218190593+01:00","Questions":{"1ff08b57-dac7-4ab4-9874-a351e69b5395":"a6e06a37-4151-4883-a80a-a4b8ffb71254","4ad789b0-cf77-42cd-bce6-d81d52113c01":"c21a9e2d-87d1-4a2e-9f6b-f250e4b8b41d","cbc95a46-e728-49b5-b865-9645f8859ae3":"af791e2f-7f5b-4732-b78f-20d810c0d71f"}}

+ 0 - 1
cmd/probo-cli/testdata/responses/response_a0a419aa-31a8-4add-8a39-33a8cb0be381.json

@@ -1 +0,0 @@
-{"id":"a0a419aa-31a8-4add-8a39-33a8cb0be381","created_at":"2023-12-17T18:27:40.702882786+01:00","updated_at":"2023-12-17T18:33:13.218444916+01:00","Questions":{"1ff08b57-dac7-4ab4-9874-a351e69b5395":"a6e06a37-4151-4883-a80a-a4b8ffb71254","4ad789b0-cf77-42cd-bce6-d81d52113c01":"c21a9e2d-87d1-4a2e-9f6b-f250e4b8b41d","cbc95a46-e728-49b5-b865-9645f8859ae3":"af791e2f-7f5b-4732-b78f-20d810c0d71f"}}

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
cmd/probo-cli/testdata/sessions/session_d580ecd7-486a-40d1-9512-a5121f5b1ebb.json


+ 66 - 0
cmd/probo-cli/textinput/textinput.go

@@ -0,0 +1,66 @@
+package textinput
+
+import (
+	"fmt"
+
+	"github.com/charmbracelet/bubbles/textinput"
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+type (
+	errMsg error
+)
+
+type model struct {
+	textInput textinput.Model
+	err       error
+}
+
+func NewTextInput(placeholder string) *model {
+	ti := textinput.New()
+	ti.Placeholder = placeholder
+	ti.Focus()
+	ti.CharLimit = 156
+	ti.Width = 20
+
+	return &model{
+		textInput: ti,
+		err:       nil,
+	}
+}
+
+func (m *model) Value() string {
+	return m.textInput.Value()
+}
+
+func (m *model) Init() tea.Cmd {
+	return textinput.Blink
+}
+
+func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	var cmd tea.Cmd
+
+	switch msg := msg.(type) {
+	case tea.KeyMsg:
+		switch msg.Type {
+		case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
+			return m, tea.Quit
+		}
+
+	// We handle errors just like any other message
+	case errMsg:
+		m.err = msg
+		return m, nil
+	}
+
+	m.textInput, cmd = m.textInput.Update(msg)
+	return m, cmd
+}
+
+func (m *model) View() string {
+	return fmt.Sprintf(
+		"\nPlease insert the name of the session\n\n%s\n\n%s",
+		m.textInput.View(),
+		"(esc to quit)",
+	) + "\n"
+}

+ 2 - 1
go.mod

@@ -5,6 +5,7 @@ go 1.21.5
 require (
 	github.com/charmbracelet/bubbles v0.17.1
 	github.com/charmbracelet/bubbletea v0.25.0
+	github.com/charmbracelet/lipgloss v0.9.1
 	github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
 	github.com/google/uuid v1.5.0
 	github.com/lmittmann/tint v1.0.3
@@ -17,7 +18,6 @@ require (
 require (
 	github.com/atotto/clipboard v0.1.4 // indirect
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
-	github.com/charmbracelet/lipgloss v0.9.1 // indirect
 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
@@ -32,6 +32,7 @@ require (
 	github.com/muesli/termenv v0.15.2 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
 	golang.org/x/sync v0.1.0 // indirect
 	golang.org/x/sys v0.12.0 // indirect

+ 2 - 0
go.sum

@@ -46,6 +46,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
+github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
 github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
 github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=

+ 20 - 5
lib/models/participant.go

@@ -3,8 +3,8 @@ package models
 import (
 	"crypto/sha256"
 	"encoding/json"
+	"errors"
 	"fmt"
-	"log"
 	"sort"
 	"strings"
 )
@@ -55,10 +55,25 @@ func (al AttributeList) MarshalCSV() (string, error) {
 	return result, nil
 }
 
-func (al AttributeList) UnmarshalCSV(csv string) error {
-	log.Println("test")
-	al = make(map[string]string)
-	al["foo"] = "bar"
+func (al *AttributeList) UnmarshalCSV(csv string) error {
+	if *al == nil {
+		*al = make(AttributeList)
+	}
+
+	pairs := strings.Split(csv, ",")
+
+	for _, pair := range pairs {
+		attrVal := strings.Split(pair, ":")
+		if len(attrVal) != 2 {
+			return errors.New("Invalid input format for attribute list.")
+		}
+
+		attr := strings.TrimSpace(attrVal[0])
+		val := strings.TrimSpace(attrVal[1])
+
+		(*al)[attr] = val
+	}
+
 	return nil
 }
 

+ 30 - 3
lib/store/file/participant.go

@@ -5,13 +5,24 @@ import (
 	"git.andreafazzi.eu/andrea/probo/lib/store"
 )
 
-type ParticipantFileStore = FileStore[*models.Participant, *store.ParticipantStore]
+type ParticipantFileStore struct {
+	*FileStore[*models.Participant, *store.ParticipantStore]
+}
 
 func NewParticipantFileStore(config *FileStoreConfig[*models.Participant, *store.ParticipantStore]) (*ParticipantFileStore, error) {
-	return NewFileStore[*models.Participant, *store.ParticipantStore](config, store.NewParticipantStore())
+	var err error
+
+	pStore := new(ParticipantFileStore)
+
+	pStore.FileStore, err = NewFileStore[*models.Participant, *store.ParticipantStore](config, store.NewParticipantStore())
+	if err != nil {
+		return nil, err
+	}
+
+	return pStore, nil
 }
 
-func NewParticipantDefaultFileStore() (*ParticipantFileStore, error) {
+func NewDefaultParticipantFileStore() (*ParticipantFileStore, error) {
 	return NewParticipantFileStore(
 		&FileStoreConfig[*models.Participant, *store.ParticipantStore]{
 			FilePathConfig: FilePathConfig{GetDefaultParticipantsDir(), "participant", ".json"},
@@ -24,3 +35,19 @@ func NewParticipantDefaultFileStore() (*ParticipantFileStore, error) {
 		},
 	)
 }
+
+func (ps *ParticipantFileStore) ImportCSV(path string) ([]*models.Participant, error) {
+	participants, err := ps.FileStore.Storer.ImportCSV(path)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, p := range participants {
+		_, err := ps.Create(p)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return participants, nil
+}

+ 1 - 1
lib/store/file/participant_test.go

@@ -12,7 +12,7 @@ type participantTestSuite struct {
 }
 
 func (t *participantTestSuite) TestCreate() {
-	pStore, err := NewParticipantDefaultFileStore()
+	pStore, err := NewDefaultParticipantFileStore()
 	t.Nil(err)
 
 	if !t.Failed() {

+ 1 - 1
lib/store/file/testdata/exams/participants/jack.json

@@ -1 +1 @@
-{"id":"5467","created_at":"2023-12-05T22:00:51.525533451+01:00","updated_at":"2023-12-17T18:54:31.169024304+01:00","Firstname":"Jack","Lastname":"Sparrow","Token":"333444","Attributes":{"class":"2 D LIN"}}
+{"id":"5467","created_at":"2023-12-05T22:00:51.525533451+01:00","updated_at":"2023-12-27T15:04:47.492868083+01:00","Firstname":"Jack","Lastname":"Sparrow","Token":"333444","Attributes":{"class":"2 D LIN"}}

+ 1 - 1
lib/store/file/testdata/exams/participants/john.json

@@ -1 +1 @@
-{"id":"1234","created_at":"2023-12-05T22:00:51.525601298+01:00","updated_at":"2023-12-17T18:54:31.169085845+01:00","Firstname":"John","Lastname":"Smith","Token":"111222","Attributes":{"class":"1 D LIN"}}
+{"id":"1234","created_at":"2023-12-05T22:00:51.525601298+01:00","updated_at":"2023-12-27T15:04:47.493037183+01:00","Firstname":"John","Lastname":"Smith","Token":"111222","Attributes":{"class":"1 D LIN"}}

+ 1 - 1
lib/store/file/testdata/exams/participants/wendy.json

@@ -1 +1 @@
-{"id":"567812","created_at":"2023-12-05T22:00:51.525667963+01:00","updated_at":"2023-12-17T18:54:31.169144803+01:00","Firstname":"Wendy","Lastname":"Darling","Token":"333444","Attributes":{"class":"2 D LIN"}}
+{"id":"567812","created_at":"2023-12-05T22:00:51.525667963+01:00","updated_at":"2023-12-27T15:04:47.493141634+01:00","Firstname":"Wendy","Lastname":"Darling","Token":"333444","Attributes":{"class":"2 D LIN"}}

+ 14 - 2
lib/store/participant.go

@@ -25,12 +25,24 @@ func (s *ParticipantStore) ImportCSV(path string) ([]*models.Participant, error)
 	}
 	defer file.Close()
 
-	participants := []*models.Participant{}
+	participants := make([]*models.Participant, 0)
 
 	if err := gocsv.UnmarshalFile(file, &participants); err != nil {
 		return nil, err
 	}
-	return participants, nil
+
+	memParticipants := make([]*models.Participant, 0)
+
+	for _, p := range participants {
+		memParticipant, err := s.Create(p)
+		if err != nil {
+			return nil, err
+		}
+		memParticipants = append(memParticipants, memParticipant)
+	}
+
+	return memParticipants, nil
+
 }
 
 func (s *ParticipantStore) FilterInGroup(group *models.Group, filter map[string]string) []*models.Participant {

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.