GoSetup-bubble/setup/list.go

177 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package setup
import (
"bubble/setup/ui"
"bubble/types"
"fmt"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/sirupsen/logrus"
)
// ListApplicationItem структура для описывающая один элемент списка
type ListApplicationItem struct {
Application types.PackageApplication // Приложения для настройки
}
// Title метод для получения имени пункта списка
func (i ListApplicationItem) Title() string {
return fmt.Sprintf("%s, v%s", i.Application.Name, i.Application.Version)
}
// Description метод для получения описания пункта списка
func (i ListApplicationItem) Description() string {
return fmt.Sprintf("%s", i.Application.Description)
}
// FilterValue метод для получения строки, по которой будет происходить фильтрация
func (i ListApplicationItem) FilterValue() string {
return i.Application.Name
}
// ListApplicationsModel структура описывающая модель отрисовки списка приложений
type ListApplicationsModel struct {
List list.Model //Готовый компонент модели списка
width int //Ширина окна
height int //Высота окна
}
// NewListApplicationsModel создает новую модель списка на основе переданного массива приложений
func NewListApplicationsModel(applications []types.PackageApplication, width, height int) *ListApplicationsModel {
//Заполняем массив приложений
items := make([]list.Item, len(applications))
for i, app := range applications {
items[i] = ListApplicationItem{app}
}
//delegate нужен для функционала работы с элементом списка
delegate := list.NewDefaultDelegate()
delegate.Styles.SelectedTitle = ui.ListSelectedStyle
delegate.Styles.SelectedDesc = ui.ListSelectedStyle
delegate.ShowDescription = false
//Формируем новый список
l := list.New(items, delegate, width, height)
l.Title = "Приложения"
l.Styles.Title = ui.ListTitleStyle
l.SetShowHelp(false)
return &ListApplicationsModel{
List: l,
width: width,
height: height,
}
}
func (m *ListApplicationsModel) Init() tea.Cmd {
return nil
}
// Update отслеживает действия со списком приложений
func (m *ListApplicationsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
//Отслеживаем и записываем новые размеры окна
m.width = msg.Width
m.height = msg.Height
return m, nil
case tea.KeyMsg:
//Отслеживаем нажатия клавиш списка
switch {
case key.Matches(msg, types.MainKeys.Confirm):
//Обработчик клавиши подтверждения приложения, нужен для подтверждения элемента списка.
//Получаем информацию о выбранном приложении,
//возвращаем команду по возврату сообщения о показе модального окна подтверждения настройки приложения.
if len(m.List.Items()) == 0 {
return m, nil
}
selected := m.List.SelectedItem().(ListApplicationItem)
return m, func() tea.Msg {
return types.ShowModalMsg{
Type: types.ModalConfirm,
Title: "Начать установку",
Message: fmt.Sprintf("Вы хотите настроить приложение:\n\n%s\n\nПродолжить?", selected.Title()),
ModalButtons: []types.ModalButton{
{Text: "Да", Value: true},
{Text: "Нет", Value: false},
},
OnResult: func(result any) tea.Cmd {
if result == true {
return func() tea.Msg {
return types.SelectedAppMsg{App: selected.Application}
}
}
return nil
},
}
}
}
if msg.String() == "q" || msg.String() == "esc" {
return m, nil
}
}
//Если ранее не было возвращено команд, то передаем сообщение напрямую в список
m.List, cmd = m.List.Update(msg)
return m, cmd
}
// View формирует строку для вывода списка приложений
func (m *ListApplicationsModel) View() string {
//Строка отступа между списком и описанием
gapStroke := " "
//Данные о размерах контейнера и контента внутри
containerWidth, containerHeight, contentWidth, contentHeight := ui.ContentSizes(m.width, m.height)
//Ширина списка = пропорция от ширины контента - границы для контейнера списка
listWidth := int(float64(contentWidth)*ui.ListWithRatio) - 2*1
//Высота списка = высота контента - границы для контейнера списка
listHeight := contentHeight - 2*1
//Ширина описания = обратная пропорция списка от ширины контента - границы для контейнера описания - отступ от контейнера списка
descWidth := int(float64(contentWidth)*(1-ui.ListWithRatio)) - 2*1 - len(gapStroke)
//Высота описания = высота контента - границы для контейнера описания
descHeight := contentHeight - 2*1
//Установка размеров списка
m.List.SetSize(listWidth, listHeight)
//logs.go logrus.DebugLevel set
logrus.Debugf(
"window: width %d, height %d\ncontainer: width %d, height %d\ncontent: width %d, height %d\nlist: width %d, height %d\ndesc: width %d, height %d\n\n",
m.width, m.height,
containerWidth, containerHeight,
contentWidth, contentHeight,
listWidth, listHeight,
descWidth, descHeight,
)
//Формируем строку списка
listView := ui.ListContainerStyle.
Width(listWidth).
Height(listHeight).
Render(m.List.View())
//Получаем описание
var desc string
if sel := m.List.SelectedItem(); sel != nil {
desc = sel.(ListApplicationItem).Description()
}
//Формируем строку с описанием
descView := ui.ListDescriptionContainerStyle.
Width(descWidth).
Height(descHeight).
Render(desc)
//Объединяем список и описание в один контент с отступом
body := lipgloss.JoinHorizontal(lipgloss.Center, listView, gapStroke, descView)
//Формируем главный контейнер
container := ui.AppContainer.
Width(containerWidth).
Height(containerHeight).
Render(body)
return lipgloss.Place(
m.width, m.height,
lipgloss.Center, lipgloss.Center,
container,
lipgloss.WithWhitespaceChars("░"),
lipgloss.WithWhitespaceForeground(ui.ColorWarning),
)
}