2025-12-26 17:45:37 +05:00
|
|
|
|
package setup
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"bubble/setup/ui"
|
|
|
|
|
|
"bubble/types"
|
2025-12-29 17:46:48 +05:00
|
|
|
|
"fmt"
|
2026-01-23 17:52:47 +05:00
|
|
|
|
"github.com/charmbracelet/bubbles/key"
|
2025-12-29 17:46:48 +05:00
|
|
|
|
"github.com/charmbracelet/bubbles/list"
|
2025-12-26 17:45:37 +05:00
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
2026-01-14 18:08:43 +05:00
|
|
|
|
"github.com/sirupsen/logrus"
|
2025-12-26 17:45:37 +05:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
// ListApplicationItem структура для описывающая один элемент списка
|
2025-12-29 17:46:48 +05:00
|
|
|
|
type ListApplicationItem struct {
|
2026-01-15 17:55:52 +05:00
|
|
|
|
Application types.PackageApplication // Приложения для настройки
|
2025-12-26 17:45:37 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
// Title метод для получения имени пункта списка
|
2025-12-29 17:46:48 +05:00
|
|
|
|
func (i ListApplicationItem) Title() string {
|
|
|
|
|
|
return fmt.Sprintf("%s, v%s", i.Application.Name, i.Application.Version)
|
|
|
|
|
|
}
|
2026-01-15 17:55:52 +05:00
|
|
|
|
|
|
|
|
|
|
// Description метод для получения описания пункта списка
|
2025-12-29 17:46:48 +05:00
|
|
|
|
func (i ListApplicationItem) Description() string {
|
|
|
|
|
|
return fmt.Sprintf("%s", i.Application.Description)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
// FilterValue метод для получения строки, по которой будет происходить фильтрация
|
2025-12-29 17:46:48 +05:00
|
|
|
|
func (i ListApplicationItem) FilterValue() string {
|
|
|
|
|
|
return i.Application.Name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
// ListApplicationsModel структура описывающая модель отрисовки списка приложений
|
2025-12-29 17:46:48 +05:00
|
|
|
|
type ListApplicationsModel struct {
|
2026-01-15 17:55:52 +05:00
|
|
|
|
List list.Model //Готовый компонент модели списка
|
|
|
|
|
|
width int //Ширина окна
|
|
|
|
|
|
height int //Высота окна
|
2025-12-29 17:46:48 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
// NewListApplicationsModel создает новую модель списка на основе переданного массива приложений
|
2025-12-29 17:46:48 +05:00
|
|
|
|
func NewListApplicationsModel(applications []types.PackageApplication, width, height int) *ListApplicationsModel {
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Заполняем массив приложений
|
2025-12-29 17:46:48 +05:00
|
|
|
|
items := make([]list.Item, len(applications))
|
2025-12-26 17:45:37 +05:00
|
|
|
|
for i, app := range applications {
|
2025-12-29 17:46:48 +05:00
|
|
|
|
items[i] = ListApplicationItem{app}
|
2025-12-26 17:45:37 +05:00
|
|
|
|
}
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//delegate нужен для функционала работы с элементом списка
|
2025-12-29 17:46:48 +05:00
|
|
|
|
delegate := list.NewDefaultDelegate()
|
|
|
|
|
|
delegate.Styles.SelectedTitle = ui.ListSelectedStyle
|
|
|
|
|
|
delegate.Styles.SelectedDesc = ui.ListSelectedStyle
|
|
|
|
|
|
delegate.ShowDescription = false
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Формируем новый список
|
2025-12-29 17:46:48 +05:00
|
|
|
|
l := list.New(items, delegate, width, height)
|
2026-01-14 18:08:43 +05:00
|
|
|
|
l.Title = "Приложения"
|
2025-12-29 17:46:48 +05:00
|
|
|
|
l.Styles.Title = ui.ListTitleStyle
|
2026-01-14 18:08:43 +05:00
|
|
|
|
l.SetShowHelp(false)
|
2025-12-29 17:46:48 +05:00
|
|
|
|
return &ListApplicationsModel{
|
|
|
|
|
|
List: l,
|
|
|
|
|
|
width: width,
|
|
|
|
|
|
height: height,
|
2025-12-26 17:45:37 +05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-29 17:46:48 +05:00
|
|
|
|
func (m *ListApplicationsModel) Init() tea.Cmd {
|
2025-12-26 17:45:37 +05:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
// Update отслеживает действия со списком приложений
|
2025-12-29 17:46:48 +05:00
|
|
|
|
func (m *ListApplicationsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
2025-12-26 17:45:37 +05:00
|
|
|
|
var cmd tea.Cmd
|
|
|
|
|
|
switch msg := msg.(type) {
|
2025-12-29 17:46:48 +05:00
|
|
|
|
case tea.WindowSizeMsg:
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Отслеживаем и записываем новые размеры окна
|
2025-12-29 17:46:48 +05:00
|
|
|
|
m.width = msg.Width
|
|
|
|
|
|
m.height = msg.Height
|
|
|
|
|
|
return m, nil
|
2025-12-26 17:45:37 +05:00
|
|
|
|
case tea.KeyMsg:
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Отслеживаем нажатия клавиш списка
|
2026-01-23 17:52:47 +05:00
|
|
|
|
switch {
|
|
|
|
|
|
case key.Matches(msg, keys.Confirm):
|
|
|
|
|
|
//Обработчик выбора нужен для подтверждения элемента списка.
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Получаем информацию о выбранном приложении,
|
|
|
|
|
|
//возвращаем команду по возврату сообщения о показе модального окна подтверждения настройки приложения.
|
2025-12-29 17:46:48 +05:00
|
|
|
|
selected := m.List.SelectedItem().(ListApplicationItem)
|
2025-12-26 17:45:37 +05:00
|
|
|
|
return m, func() tea.Msg {
|
2025-12-29 17:46:48 +05:00
|
|
|
|
return types.ShowModalMsg{
|
|
|
|
|
|
Type: types.ModalConfirm,
|
|
|
|
|
|
Title: "Начать установку",
|
2026-01-14 18:08:43 +05:00
|
|
|
|
Message: fmt.Sprintf("Вы хотите настроить приложение:\n\n%s\n\nПродолжить?", selected.Title()),
|
2025-12-29 17:46:48 +05:00
|
|
|
|
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
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
2025-12-26 17:45:37 +05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-23 17:52:47 +05:00
|
|
|
|
|
2025-12-26 17:45:37 +05:00
|
|
|
|
}
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Если ранее не было возвращено команд, то передаем сообщение напрямую в список
|
2025-12-29 17:46:48 +05:00
|
|
|
|
m.List, cmd = m.List.Update(msg)
|
2025-12-26 17:45:37 +05:00
|
|
|
|
return m, cmd
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
// View формирует строку для вывода списка приложений
|
2025-12-29 17:46:48 +05:00
|
|
|
|
func (m *ListApplicationsModel) View() string {
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Строка отступа между списком и описанием
|
2026-01-14 18:08:43 +05:00
|
|
|
|
gapStroke := " "
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Данные о размерах контейнера и контента внутри
|
2026-01-14 18:08:43 +05:00
|
|
|
|
containerWidth, containerHeight, contentWidth, contentHeight := ui.ContentSizes(m.width, m.height)
|
|
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Ширина списка = пропорция от ширины контента - границы для контейнера списка
|
2026-01-14 18:08:43 +05:00
|
|
|
|
listWidth := int(float64(contentWidth)*ui.ListWithRatio) - 2*1
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Высота списка = высота контента - границы для контейнера списка
|
2026-01-14 18:08:43 +05:00
|
|
|
|
listHeight := contentHeight - 2*1
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Ширина описания = обратная пропорция списка от ширины контента - границы для контейнера описания - отступ от контейнера списка
|
2026-01-14 18:08:43 +05:00
|
|
|
|
descWidth := int(float64(contentWidth)*(1-ui.ListWithRatio)) - 2*1 - len(gapStroke)
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Высота описания = высота контента - границы для контейнера описания
|
2026-01-14 18:08:43 +05:00
|
|
|
|
descHeight := contentHeight - 2*1
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Установка размеров списка
|
2026-01-14 18:08:43 +05:00
|
|
|
|
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,
|
|
|
|
|
|
)
|
2025-12-29 17:46:48 +05:00
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Формируем строку списка
|
|
|
|
|
|
listView := ui.ListContainerStyle.
|
2026-01-14 18:08:43 +05:00
|
|
|
|
Width(listWidth).
|
|
|
|
|
|
Height(listHeight).
|
|
|
|
|
|
Render(m.List.View())
|
2025-12-29 17:46:48 +05:00
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Получаем описание
|
2025-12-29 17:46:48 +05:00
|
|
|
|
var desc string
|
|
|
|
|
|
if sel := m.List.SelectedItem(); sel != nil {
|
|
|
|
|
|
desc = sel.(ListApplicationItem).Description()
|
2025-12-26 17:45:37 +05:00
|
|
|
|
}
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Формируем строку с описанием
|
|
|
|
|
|
descView := ui.ListDescriptionContainerStyle.
|
2026-01-14 18:08:43 +05:00
|
|
|
|
Width(descWidth).
|
|
|
|
|
|
Height(descHeight).
|
2026-01-15 17:55:52 +05:00
|
|
|
|
Render(desc)
|
2026-01-14 18:08:43 +05:00
|
|
|
|
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Объединяем список и описание в один контент с отступом
|
2026-01-14 18:08:43 +05:00
|
|
|
|
body := lipgloss.JoinHorizontal(lipgloss.Center, listView, gapStroke, descView)
|
2026-01-15 17:55:52 +05:00
|
|
|
|
//Формируем главный контейнер
|
2026-01-14 18:08:43 +05:00
|
|
|
|
container := ui.AppContainer.
|
|
|
|
|
|
Width(containerWidth).
|
|
|
|
|
|
Height(containerHeight).
|
|
|
|
|
|
Render(body)
|
|
|
|
|
|
return lipgloss.Place(
|
|
|
|
|
|
m.width, m.height,
|
2025-12-29 17:46:48 +05:00
|
|
|
|
lipgloss.Center, lipgloss.Center,
|
|
|
|
|
|
container,
|
|
|
|
|
|
lipgloss.WithWhitespaceChars("░"),
|
2026-01-14 18:08:43 +05:00
|
|
|
|
lipgloss.WithWhitespaceForeground(ui.ColorWarning),
|
2025-12-29 17:46:48 +05:00
|
|
|
|
)
|
2025-12-26 17:45:37 +05:00
|
|
|
|
}
|