[Go language] Erstellen Sie eine TUI-App mit Elm Architecture. Erstellen Sie eine leicht reichhaltige ToDo-App mit bubbletea

Vorwort

In diesem Artikel erstellen wir eine ToDo-App mit einem Framework namens bubbletea, mit dem Sie eine TUI-App in Elm Architecture wie erstellen können.

Sie können Aufgaben / erledigte Aufgaben anzeigen, hinzufügen, bearbeiten, abschließen und löschen. Bild Bild ↓ イメージ画像

Über Elm Architecture

Für diejenigen, die Elm Architecture nicht kennen, Official Guide (japanische Übersetzung) und This article Ich denke, es ist einfacher zu verstehen, wenn Sie 27a2b6f8b4a1bfac4bd3) kurz lesen. (Dieser Artikel beschreibt selten die Ulmenarchitektur.)

Ich habe ein wenig Kontakt zu Elm, daher habe ich ein schwaches Verständnis für Elm Architecture. Wenn Sie also Fehler haben, teilen Sie uns dies bitte in den Kommentaren oder auf Twitter mit.

In diesem Artikel implementierte Funktionen

In diesem Artikel werde ich versuchen, die Aufgabenliste anzuzeigen und bis zum Hinzufügen von Aufgaben zu erklären (Anleitung). Von dort füge ich einfach Code hinzu. Für diejenigen, die nur den Code lesen und die Atmosphäre spüren möchten, diejenigen, die detailliert wissen möchten, wie andere Funktionen implementiert werden, und diejenigen, die Masakari [yuzuy / todo-cli] werfen (https://github.com/yuzuy/) Siehe todo-cli)!

Beginnen wir jetzt mit der Implementierung!

Implementierung

Autorenumgebung

Ausführung
OS macOS Catalina 10.15.7
iTerm 3.3.12
Go 1.15.2
bubbletea 0.7.0
bubbles 0.7.0

Hinweis: Über Bubble Tea. Während der Implementierung dieser App gab es einige zerstörerische Änderungen. Wenn Sie also Probleme mit der Verwendung einer anderen Version von bubbletea haben, lesen Sie bitte das Repository. Bitte.

Liste der Aufgaben

In diesem Abschnitt zeigen wir die Aufgabenliste an und implementieren sie, bis Sie die Aufgabe mit dem Cursor auswählen können.

Lassen Sie uns zuerst das Paket holen.

// bubbletea
go get github.com/charmbracelet/bubbletea

// utility
go get github.com/charmbracelet/bubbles

Model

Definieren Sie zunächst die Struktur der Aufgabe.

main.go


type Task struct {
    ID        int
    Name      string
    IsDone    bool
    CreatedAt time.Time
}

Es hat eine minimale Struktur. Wenn Sie also etwas anderes wie "Fertig" möchten, fügen Sie es hinzu.

Als nächstes definieren Sie "Modell". Dies ist das Modell der Ulmenarchitektur. Elm Architecture verwaltet den Status der App mit einem Modell. In diesem Kapitel Da zwei Zustände zu behandeln sind, die Aufgabenliste und die Cursorposition, ist die Implementierung wie folgt.

main.go


type model struct {
    cursor int
    tasks  []*Task
}

Dies allein wird Bubble Tea nicht als Modell behandeln. Um es als Modell zu behandeln, muss "model" tea.Model "implementieren.

Definition von tea.Model

// Model contains the program's state as well as it's core functions.
type Model interface {
	// Init is the first function that will be called. It returns an optional
	// initial command. To not perform an initial command return nil.
	Init() Cmd

	// Update is called when a message is received. Use it to inspect messages
	// and, in response, update the model and/or send a command.
	Update(Msg) (Model, Cmd)

	// View renders the program's UI, which is just a string. The view is
	// rendered after every Update.
	View() string
}

Zuerst werden wir es von der Initialisierungsfunktion Init () implementieren, aber da in dieser App kein Befehl zuerst ausgeführt werden muss, ist es in Ordnung, einfach nil zurückzugeben. (Die Initialisierung von modelstruct erfolgt separat.) (Dieser Artikel befasst sich nicht sehr mit Befehlen, machen Sie sich also keine Sorgen über Befehle. Wenn Sie interessiert sind, Elm Official Guide (japanische Übersetzung) Vielleicht möchten Sie sich auf /) beziehen.)

main.go


import (
    ...
    tea "github.com/charmbracelet/bubbletea"
)

...

func (m model) Init() tea.Cmd {
    return nil
}

Update

Update () ändert den Status von model basierend auf der Operation des Benutzers (Msg).

main.go


func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyMsg:
        switch msg.String() {
        case "j":
            if m.cursor < len(m.tasks) {
                m.cursor++
            }
        case "k":
            if m.cursor > 1 {
                m.cursor--
            }
        case "q":
            return m, tea.Quit
        }
    }

    return m, nil
}

Umgang mit tea.KeyMsg (Benutzertastenbedienung), Wenn "j" gedrückt wird, wird der Wert des Cursors um 1 erhöht. Verringern Sie den Wert des Cursors um 1, wenn "k" gedrückt wird. Es definiert, dass beim Drücken von "q" der Befehl "tea.Quit" zurückgegeben wird, um die App zu beenden.

Da es ein Problem ist, wenn der Cursor unendlich auf und ab geht, werden Bedingungen wie "m.cursor> 1" hinzugefügt. Übrigens, wenn Sie "case" j "," down "odercase" k "," up "` verwenden, können Sie den Cursor mit den Pfeiltasten auf und ab bewegen. Verwenden Sie ihn also bitte nach Belieben.

View

View () generiert den zu zeichnenden Text basierend auf model. Ich werde es mit string schreiben.

main.go


func (m model) View() string {
    s := "--YOUR TASKS--\n\n"

    for i, v := range m.tasks {
        cursor := " "
        if i == m.cursor-1 {
            cursor = ">"
        }

        timeLayout := "2006-01-02 15:04"
        s += fmt.Sprintf("%s #%d %s (%s)\n", cursor, v.ID, v.Name, v.CreatedAt.Format(timeLayout))
    }

    s += "\nPress 'q' to quit\n"

    return s
}

Da der Cursor nicht durch die Indexnummer gezählt wird, wird durch Vergleichen der Indizes "i" und "m.cursor-1" beurteilt, ob der Cursor auf die Aufgabe zeigt.

Sie haben jetzt genug Material, um Ihre Aufgaben anzuzeigen! Definieren wir die Hauptfunktion, damit die App gestartet werden kann!

main

Die main Funktion initialisiert die model Struktur und startet die App.

main.go


func main() {
    m := model{
        cursor: 1,
        tasks:  []*Task{
            {
                ID:        1,
                Name:      "First task!",
                CreatedAt: time.Now(),
            },
            {
                ID:        2,
                Name:      "Write an article about bubbletea",
                CreatedAt: time.Now(),
            },
        }
    }

    p := tea.NewProgram(m)
    if err := p.Start(); err != nil {
        fmt.Printf("app-name: %s", err.Error())
        os.Exit(1)
    }
}

Normalerweise werden Aufgaben aus Dateien usw. gelesen, aber die Implementierung dauert einige Zeit. Daher werden wir sie dieses Mal hart codieren. Generieren Sie ein Programm mit tea.NewProgram () und starten Sie es mit p.Start ().

Lassen Sie es uns sofort mit dem Befehl go run ausführen! Sie sollten in der Lage sein, die Aufgabenliste zu sehen und den Cursor mit den Tasten "j" und "k" auf und ab zu bewegen!

Aufgabe hinzufügen

Nun, ich konnte die Liste der Aufgaben anzeigen, aber damit ist es unwahrscheinlich, dass die ToDo-App benannt wird. In diesem Abschnitt implementieren wir eine der wichtigsten Funktionen der ToDo-App und fügen Aufgaben hinzu.

Model

Bisher war es in Ordnung, nur die Cursorposition und die Aufgabenliste zu halten. Wenn Sie jedoch das Hinzufügen einer Aufgabe implementieren, müssen Sie ein Feld bereitstellen, das Eingaben vom Benutzer empfängt und diese enthält.

bubbleteaでテキスト入力を実装するにはgithub.com/charmbracelet/bubbles/textinputというパッケージを利用します。

main.go


import (
    ...
    input "github.com/charmbracelet/bubbles/textinput"
)

type model struct {
    ...
    newTaskNameInput input.Model
}

Da es allein dadurch nicht möglich ist, zwischen dem Aufgabenlisten-Anzeigemodus (im Folgenden als normaler Modus bezeichnet) und dem Aufgabenadditionsmodus (im Folgenden als zusätzlicher Modus bezeichnet) zu unterscheiden, wird auch ein Feld hinzugefügt, das als "Modus" bezeichnet wird.

main.go


type model struct {
    mode int
    ...
    newTaskNameInput input.Model
}

Definieren wir auch die "Modus" -Kennung.

main.go


const (
    normalMode = iota
    additionalMode
)

Update

Ich möchte sofort mit der Änderung der Funktion Update () beginnen, aber beim Hinzufügen einer Aufgabe fehlt ein Element. Diese ToDo-App möchte Aufgaben-IDs nacheinander verwalten, daher müssen Sie die neuesten Aufgaben-IDs beibehalten.

~~ Als globale Variable deklarieren, mit main () initialisieren, mit Update () inkrementieren und so weiter. (Ich dachte beim Schreiben, aber es kann als "Modell" -Feld verwaltet werden.) ~~ Twitter wies @ababupdownba schließlich auf "model" hin Es ist besser, es zu einem Feld zu machen, also werde ich es dort implementieren.

main.go


type model struct {
    ...
    latestTaskID int
}

func main() {
    ...
    //Dies ist ein Festival für harte Codierung. Wenn Sie es jedoch ordnungsgemäß implementieren möchten, geben Sie beim Lesen aus einer Datei usw. den Anfangswert ein.
    m.latestTaskID = 2
    ...
}

Das Hauptthema ist übrigens "Update ()", aber im Gegensatz zum normalen Modus werden alle Zeichentasten im zusätzlichen Modus als Eingabe behandelt.

Definieren Sie daher model.addingTaskUpdate () getrennt vonmodel.Update ()und verarbeiten Sie es dort, wenn model.mode == AdditionalMode.

Im normalen Modus wechselt es in den zusätzlichen Modus, wenn "a" gedrückt wird. Stellen Sie im Add-Modus sicher, dass die Aufgabe hinzugefügt wird, wenn "Enter" gedrückt wird, damit sie in den normalen Modus zurückkehrt, wenn "Strg + Q" gedrückt wird.

main.go


func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    if m.mode == additionalMode {
        return m.addingTaskUpdate(msg)
    }

    switch msg := msg.(type) {
    case tea.KeyMsg:
        switch msg.String() {
        ...
        case "a":
            m.mode = additionalMode
        ...
    }

    return m, nil
}

func (m model) addingTaskUpdate(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyMsg:
        switch msg.String() {
        case "ctrl+q":
            m.mode = normalMode
            m.newTaskNameInput.Reset()
            return m, nil
        case "enter":
            m.latestTaskID++
            m.tasks = append(m.tasks, &Task{
                ID  :      m.latestTaskID,
                Name:      m.newTaskNameInput.Value(),
                CreatedAt: time.Now(),
            })

            m.mode = normalMode
            m.newTaskNameInput.Reset()

            return m, nil
        }
    }

    var cmd tea.Cmd
    m.newTaskNameInput, cmd = input.Update(msg, m.newTaskNameInput)

    return m, cmd
}

Der mit "m.newTaskNameInput.Value ()" eingegebene Wert wird abgerufen und mit "m.newTaskNameInput.Reset ()" zurückgesetzt. Ich aktualisiere m.newTaskNameInput, indem ich Tastenanschläge mit input.Update () behandle.

View

Es ist notwendig, die gleiche Verarbeitung wie "Update ()" von "View ()" zu trennen. Dies kann aber mit nur 6 Zeilenwechseln umgesetzt werden!

main.go


func (m model) View() string {
    if m.mode == additionalMode {
        return m.addingTaskView()
    }
    ...
}

func (m model) addingTaskView() string {
    return fmt.Sprintf("Additional Mode\n\nInput a new task name\n\n%s", input.View(m.newTaskNameInput))
}

input.View () ist eine Variable, die input.Model fürView ()in string konvertiert.

main

Fügen wir die Initialisierung der neuen Felder "mode" und "newTaskNameInput" hinzu.

main.go


func main() {
    newTaskNameInput := input.NewModel()
    newTaskNameInput.Placeholder = "New task name..."
    newTaskNameInput.Focus()

    m := model{
        mode: normalMode,
        ...
        newTaskNameInput: newTaskNameInput,
    }
    ...
}

"Platzhalter" ist eine Zeichenfolge, die angezeigt wird, wenn nichts eingegeben wird.

Das Aufrufen von "Focus ()" konzentriert sich auf dieses "input.Model". Dies scheint verwendet zu werden, wenn mehrere Eingaben auf einem Bildschirm verarbeitet werden. Dieses Mal lassen wir Sie nicht mehr als eine eingeben, daher denke ich, dass es in Ordnung ist, wenn Sie es als Magie betrachten.

Jetzt haben Sie das Hinzufügen von Aufgaben implementiert! Führen wir es wie zuvor mit dem Befehl go run usw. aus!

Nachwort

In diesem Artikel habe ich erklärt, wie man mit bubbletea eine leicht reichhaltige ToDo-App erstellt. Die Implementierung war einfacher als erwartet und ich wollte ein komplexes CLI-Tool wie tig erstellen, aber für mich, der den ersten Schritt nicht machen konnte. War ein sehr attraktiver Rahmen.

bubbletea bietet viele weitere Funktionen zum Erstellen umfangreicher TUI-Anwendungen. Schauen Sie sich also unbedingt das [Repository] an (https://github.com/charmbracelet/bubbletea)!

Recommended Posts

[Go language] Erstellen Sie eine TUI-App mit Elm Architecture. Erstellen Sie eine leicht reichhaltige ToDo-App mit bubbletea
Lassen Sie uns eine Todo-App mit dem Django REST-Framework erstellen
Todo-App mit Django erstellen ③ Aufgabenlistenseite erstellen
Todo-App mit Django erstellen ⑤ Funktion zum Bearbeiten von Aufgaben erstellen
Erstellen einer Todo-App mit Django ① Erstellen Sie eine Umgebung mit Docker
Todo-App mit Django erstellen ④ Ordner- und Aufgabenerstellungsfunktion implementieren
Erstellen Sie eine GUI-App mit Tkinter of Python
Erstellen Sie eine einfache Web-App mit Flasche
Verfahren zur Erstellung plattformübergreifender Apps mit kivy
[Übung] Erstellen Sie eine Watson-App mit Python! # 1 [Sprachdiskriminierung]