package setup import ( "bubble/setup/ui" "bubble/types" "fmt" "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 msg.String() { case "enter": //Обработчик enter нужен для подтверждения элемента списка. //Получаем информацию о выбранном приложении, //возвращаем команду по возврату сообщения о показе модального окна подтверждения настройки приложения. 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 }, } } } } //Если ранее не было возвращено команд, то передаем сообщение напрямую в список 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), ) }