desktop.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. // package desktop -- консольный клиент для вар-танка
  2. package desktop
  3. import (
  4. "fmt"
  5. "os"
  6. "time"
  7. "github.com/charmbracelet/bubbles/textinput"
  8. tea "github.com/charmbracelet/bubbletea"
  9. "wartank/desktop/client_serv"
  10. )
  11. // Desktop -- консольный клиент для вар-танка
  12. type Desktop struct {
  13. TeaModel
  14. p *tea.Program // Объект приложения
  15. client *client_serv.ClientServ // Клиент подключния к бото-серверу
  16. isConnect bool // Признак подключения к бот-серверу
  17. isLogin bool // Признак логина на сервер
  18. }
  19. var desk *Desktop
  20. func NewDesktop() *Desktop {
  21. sf := &Desktop{
  22. client: client_serv.NewClientServ(),
  23. }
  24. sf.TeaModel = NewModel()
  25. go sf.tick()
  26. sf.p = tea.NewProgram(sf, tea.WithAltScreen(), tea.WithANSICompressor())
  27. desk = sf
  28. if _, err := sf.p.Run(); err != nil {
  29. fmt.Printf("Опа! здесь какая-то ошибочка: %v", err)
  30. os.Exit(1)
  31. }
  32. return sf
  33. }
  34. // AMode -- режим обработки клавиш
  35. type AMode int
  36. const (
  37. ModeDefault AMode = iota
  38. ModeConfig // Режим конфигурирования
  39. ModeConfigServer // Режим конфигуррования сервера
  40. ModeInputHostServer // Режим ввода хоста сервера
  41. ModeInputLoginServer // Режим ввода логина на сервер
  42. ModeInputPassServer // Режим ввода пароля входа на сервер
  43. ModeConnectServer // Режим подключения к серверу
  44. ModeLoginServer // Режим логина на сервер
  45. ModeServerMakeAdmin // Создать админа для бот-сервера
  46. )
  47. type TeaModel struct {
  48. choices []string // выбранный пункт в списке
  49. cursor int // позиция курсора в списке
  50. selected map[int]struct{} // что выбрано из списка
  51. mode AMode // Режим показа на экране
  52. textInput textinput.Model
  53. host string // Имя хоста сервера (с портом)
  54. login string // Логин для сервера
  55. pass string // Пароль для сервера
  56. }
  57. func NewModel() TeaModel {
  58. ti := textinput.New()
  59. ti.Placeholder = "localhost"
  60. ti.Focus()
  61. ti.CharLimit = 156
  62. ti.Width = 20
  63. m := TeaModel{
  64. // Список дел
  65. choices: []string{"Купить картошку", "Оплатить мобилку", "Заплатить за кредит"},
  66. selected: make(map[int]struct{}),
  67. textInput: ti,
  68. }
  69. return m
  70. }
  71. func (sf *Desktop) tick() {
  72. for {
  73. time.Sleep(time.Second * 1)
  74. msg := tea.MouseEvent{}
  75. // m.Update(msg)
  76. sf.p.Send(msg)
  77. }
  78. }
  79. // Run -- запускает в работу консольный клиент
  80. func (sf *Desktop) Run() error {
  81. return nil
  82. }
  83. func (sf Desktop) Init() tea.Cmd {
  84. // Просто вернуть `nil`, т.е. никаких команд
  85. return textinput.Blink
  86. }
  87. func (sf Desktop) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  88. var cmd tea.Cmd
  89. switch msg := msg.(type) {
  90. // Может клавиша нажата
  91. case tea.KeyMsg:
  92. switch msg.String() { // Да, нажато, а что именно?
  93. case "ctrl+q": // Надо выйти из программы
  94. return sf, tea.Quit
  95. }
  96. }
  97. switch desk.mode {
  98. case ModeDefault:
  99. switch msg := msg.(type) {
  100. // Может клавиша нажата
  101. case tea.KeyMsg:
  102. switch msg.String() { // Да, нажато, а что именно?
  103. case "up", "k": // Если "up" или "k" перемещение курсора вверх
  104. if desk.cursor > 0 {
  105. desk.cursor--
  106. }
  107. case "down", "j": // Если "down" или "j" перемещение курсора вниз
  108. if desk.cursor < len(desk.choices)-1 {
  109. desk.cursor++
  110. }
  111. case "enter", " ": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  112. _, ok := desk.selected[desk.cursor]
  113. if ok {
  114. delete(desk.selected, desk.cursor)
  115. } else {
  116. desk.selected[desk.cursor] = struct{}{}
  117. }
  118. case "w": // Настройки графического клиента
  119. desk.mode = ModeConfig
  120. }
  121. }
  122. case ModeConfig:
  123. switch msg := msg.(type) {
  124. // Может клавиша нажата
  125. case tea.KeyMsg:
  126. switch msg.String() { // Да, нажато, а что именно?
  127. case "esc": // Вернуться на шаг назад
  128. desk.mode = ModeDefault
  129. case "1": // Выбор один
  130. desk.mode = ModeConfigServer
  131. case "2": // Вход на сервер
  132. if !desk.isConnect {
  133. desk.mode = ModeConnectServer
  134. }
  135. case "3": // Логин на сервер
  136. if !desk.isLogin {
  137. desk.mode = ModeLoginServer
  138. }
  139. case "4": // Создать админа
  140. desk.mode = ModeServerMakeAdmin
  141. }
  142. }
  143. case ModeConfigServer:
  144. switch msg := msg.(type) {
  145. // Может клавиша нажата
  146. case tea.KeyMsg:
  147. switch msg.String() { // Да, нажато, а что именно?
  148. case "esc": // Вернуться на шаг назад
  149. desk.mode = ModeConfig
  150. case "1": // Выбор хоста сервера
  151. desk.mode = ModeInputHostServer
  152. desk.textInput.Reset()
  153. desk.textInput.Placeholder = "localhost:18050"
  154. case "2": // Выбор логина для входа
  155. desk.mode = ModeInputLoginServer
  156. desk.textInput.Reset()
  157. desk.textInput.Placeholder = "login"
  158. case "3": // Выбор пароля для входа
  159. desk.mode = ModeInputPassServer
  160. desk.textInput.Reset()
  161. desk.textInput.Placeholder = "pass"
  162. }
  163. }
  164. case ModeInputHostServer:
  165. switch msg := msg.(type) {
  166. // Может клавиша нажата
  167. case tea.KeyMsg:
  168. switch msg.String() { // Да, нажато, а что именно?
  169. case "esc": // Вернуться на шаг назад
  170. desk.mode = ModeConfigServer
  171. case "enter": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  172. desk.host = desk.textInput.Value()
  173. desk.mode = ModeConfigServer
  174. default:
  175. desk.textInput, cmd = desk.textInput.Update(msg)
  176. }
  177. }
  178. case ModeInputLoginServer:
  179. switch msg := msg.(type) {
  180. // Может клавиша нажата
  181. case tea.KeyMsg:
  182. switch msg.String() { // Да, нажато, а что именно?
  183. case "esc": // Вернуться на шаг назад
  184. desk.mode = ModeConfigServer
  185. case "enter": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  186. desk.login = desk.textInput.Value()
  187. desk.mode = ModeConfigServer
  188. default:
  189. desk.textInput, cmd = desk.textInput.Update(msg)
  190. }
  191. }
  192. case ModeInputPassServer:
  193. switch msg := msg.(type) {
  194. // Может клавиша нажата
  195. case tea.KeyMsg:
  196. switch msg.String() { // Да, нажато, а что именно?
  197. case "esc": // Вернуться на шаг назад
  198. desk.mode = ModeConfigServer
  199. case "enter": // Нажатие "enter" или "пробел" переключает выбранный элемент списка
  200. desk.pass = desk.textInput.Value()
  201. desk.mode = ModeConfigServer
  202. default:
  203. desk.textInput, cmd = desk.textInput.Update(msg)
  204. }
  205. }
  206. case ModeConnectServer:
  207. switch msg := msg.(type) {
  208. // Может клавиша нажата
  209. case tea.KeyMsg:
  210. switch msg.String() { // Да, нажато, а что именно?
  211. case "esc": // Нажатие "esc" прекращает подключение к серверу
  212. desk.mode = ModeConfigServer
  213. }
  214. }
  215. case ModeLoginServer:
  216. switch msg := msg.(type) {
  217. // Может клавиша нажата
  218. case tea.KeyMsg:
  219. switch msg.String() { // Да, нажато, а что именно?
  220. case "esc": // Нажатие "esc" прекращает логин на сервер
  221. desk.mode = ModeConfig
  222. }
  223. }
  224. case ModeServerMakeAdmin:
  225. switch msg := msg.(type) {
  226. // Может клавиша нажата
  227. case tea.KeyMsg:
  228. switch msg.String() { // Да, нажато, а что именно?
  229. case "esc": // Нажатие "esc" прекращает логин на сервер
  230. desk.mode = ModeConfig
  231. }
  232. }
  233. }
  234. // Возвращает обновлённую модель дляe Bubble Tea runtime для обработки.
  235. //Здесь команду возвращать надо.
  236. return desk, cmd
  237. }
  238. func (sf Desktop) View() string {
  239. s := time.Now().Local().Format("[ WarTank ] = 2006-01-02 15:04:05.000")
  240. s += fmt.Sprintf("\tХост=%q\tЛогин=%q\tПароль=%q\tПодключено=%v\tЛогин=%v\n\n", desk.host, desk.login, desk.pass, desk.isConnect, desk.isLogin)
  241. switch desk.mode {
  242. case ModeDefault: // Исходное содержимое
  243. s += desk.winDefault()
  244. case ModeConfig: // Показать настройки клиента
  245. s += desk.winConfig()
  246. case ModeConfigServer: // Конфигурация сервера
  247. s += desk.winConfigServer()
  248. case ModeInputHostServer: // Ввод хоста для сервера
  249. s += desk.winHostServer()
  250. case ModeInputLoginServer: // Ввод логина для входа на сервер
  251. s += desk.winLoginServer()
  252. case ModeInputPassServer: // ввод пароля
  253. s += desk.winPassServer()
  254. case ModeConnectServer: // Режим подключения к серверу
  255. s += desk.winConnectServer()
  256. case ModeLoginServer: // Режим логина на сервер
  257. s += desk.winServerLogin()
  258. case ModeServerMakeAdmin: // Режим содания админа на бот-сервере
  259. s += desk.winServerMakeAdmin()
  260. }
  261. return s
  262. }
  263. func (sf *Desktop) winServerMakeAdmin() string {
  264. s := "=Создание админа на бот-сервере вар-танк=\n\n"
  265. err := desk.client.MakeAdmin(desk.pass)
  266. if err != nil {
  267. s += fmt.Sprintf("Desktop.winServerMakeAdmin(): err=\n\t%v", err)
  268. s += "\n\n[ctrl+q]-выход [esc]-отмена\n"
  269. return s
  270. }
  271. s += "Desktop.winServerMakeAdmin(): ok\n"
  272. desk.isLogin = true
  273. s += "\n\n[ctrl+q]-выход [esc]-отмена\n"
  274. return s
  275. }
  276. func (sf *Desktop) winServerLogin() string {
  277. s := "=Логин на бот-сервер вар-танк=\n\n"
  278. err := desk.client.Login(desk.login, desk.pass)
  279. if err != nil {
  280. s += fmt.Sprintf("Desktop.winServerLogin(): err=\n\t%v", err)
  281. s += "\n\n[ctrl+q]-выход [esc]-отмена\n"
  282. return s
  283. }
  284. s += "Desktop.winServerLogin(): ok\n"
  285. desk.isLogin = true
  286. s += "\n\n[ctrl+q]-выход [esc]-отмена\n"
  287. return s
  288. }
  289. func (sf *Desktop) winConnectServer() string {
  290. s := "=Подключение к бот-серверу вар-танк=\n\n"
  291. if !desk.isConnect {
  292. err := desk.client.Connect(desk.host)
  293. if err != nil {
  294. s += fmt.Sprintf("Desktop.winConnectServer(): err=\n\t%v", err)
  295. s += "\n\n[ctrl+q]-выход [esc]-отмена\n"
  296. return s
  297. }
  298. }
  299. s += "Desktop.winConnectServer(): ok\n"
  300. desk.isConnect = true
  301. s += "\n\n[ctrl+q]-выход [esc]-отмена\n"
  302. return s
  303. }
  304. func (sf *Desktop) winPassServer() string {
  305. s := "=Ввод пароля для бот-сервера вар-танк=\n\n"
  306. s += fmt.Sprintf(">%v\n", desk.textInput.View())
  307. s += "\n\n[ctrl+q]-выход [esc]-назад\n"
  308. return s
  309. }
  310. func (sf *Desktop) winLoginServer() string {
  311. s := "=Ввод логина для сервера вар-танк=\n\n"
  312. s += fmt.Sprintf(">%v\n", desk.textInput.View())
  313. s += "\n\n[ctrl+q]-выход [esc]-назад\n"
  314. return s
  315. }
  316. func (sf *Desktop) winHostServer() string {
  317. s := "=Ввод хоста для сервера вар-танк=\n\n"
  318. s += fmt.Sprintf(">%v\n", desk.textInput.View())
  319. s += "\n\n[ctrl+q]-выход [esc]-назад\n"
  320. return s
  321. }
  322. func (sf *Desktop) winConfigServer() string {
  323. s := "Настройки сервера вар-танк\n\n"
  324. s += "[1]-Настройки хоста сервера\n"
  325. s += "[2]-Логин для входа\n"
  326. s += "[3]-Пароль для входа\n"
  327. s += "\n\n[ctrl+q]-выход [esc]-назад\n"
  328. return s
  329. }
  330. func (sf *Desktop) winConfig() string {
  331. s := "Настройки клиента\n\n"
  332. s += "[1]-Настройки сервера\n"
  333. s += "[2]-Вход на сервер\n"
  334. s += "[3]-Логин на сервер\n"
  335. s += "[4]-Сделать админа на бот-сервере\n"
  336. s += "\n\n[ctrl+q]-выход [esc]-назад\n"
  337. return s
  338. }
  339. func (sf *Desktop) winDefault() string {
  340. // Заголовок
  341. s := "Что нам нужно сделать?\n\n"
  342. // Перебор по списку выбора
  343. for i, choice := range desk.choices {
  344. // Курсор указывает на этот выбор?
  345. cursor := " " // нет курсора
  346. if desk.cursor == i {
  347. cursor = ">" // есть курсор
  348. }
  349. // Этот элемент выбран?
  350. checked := " " // не выбран
  351. if _, ok := desk.selected[i]; ok {
  352. checked = "+" // выбран!
  353. }
  354. // отрендерить заново
  355. s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
  356. }
  357. // Подвал
  358. s += "\n\n[ctrl+q]-выход [w]-настройки\n"
  359. // Вернуть строку экрана для отрисовки
  360. return s
  361. }