Что такое файловый проводник TUI и зачем он вам нужен?
Файловый проводник TUI – это программа, которую вы можете использовать для изучения вашей файловой системы и работы с файлами в интерактивном режиме в терминале, без графического интерфейса пользователя (GUI) и без необходимости вводить длинные и рискованные команды оболочки (CLI).
Почему я должен использовать TUI, а не GUI проводники файлов?
Я думаю, если у вас возник этот вопрос, то, скорее всего, вам не нужно использовать TUI, поскольку все они в разной степени сложны в освоении. Однако, если вы системный администратор или разработчик, которому приходится часто использовать терминал, вы обнаружите, что большинство программ GUI не очень хорошо работают с терминальными программами. Например, вы не сможете или не захотите использовать файловый менеджер с графическим интерфейсом вместе с терминальным редактором кода, таким как Vim, Neovim или Emacs, поскольку это будет очень неэффективно, если вообще возможно.
Почему я должен использовать TUI вместо инструментов CLI?
Если вы проводите значительную часть своего времени, набирая команды оболочки и работая с файлами в терминале, то вам может понадобиться некая абстракция, то есть автоматизация команд, которые вы используете чаще всего. Использование инструмента, который хорошо интегрируется с другими терминальными программами и способен обеспечить визуальную обратную связь в реальном времени, сделает все интуитивно понятным для вас и позволит вам быть более продуктивным.
Например, если вам нужно только создать файл с текстом, достаточно cat. Если вам нужны функции редактирования, то nano или vi должны позволить вам это сделать. Если вы проводите большую часть дня за написанием кода или редактированием текста, вам нужно что-то более мощное, хорошо интегрирующееся с другими инструментами. Например, vim/neovim/emacs/vscode и т.д. Если ваша среда разработки более сложная и требует выделенной платформы (например, java/android), потребуются тяжелые IDE, такие как eclipse / android studio. То же самое относится и к файловым редакторам.
В зависимости от вашего рабочего процесса и от того, сколько времени вы тратите на оптимизацию вашей системы разработки, вы можете найти программы TUI, особенно такие файловые проводники, как xplr, очень удобными.
В заключение я хочу сказать, что TUI проводники файлов не заменяют инструменты CLI или программы GUI. Вместо этого, они предоставляют пользователям основу для полноценного использования их любимых инструментов и программ, используя интуитивно понятный и гибкий механизм взаимодействия в терминале.
Следите за xplr, чтобы получить краткие руководства по работе с командной строкой, советы и рекомендации по использованию xplr.
Создаем TUI на python
Привет, Хабр! В этой статье я расскажу про npyscreen — библиотеке для создания текстовых интерфейсов для терминальных и консольных приложений.
Установка
Пакет доступен для скачивания через PyPI.
sudo pip3 install npyscreen
Типы объектов
Npyscreen использует 3 основных типа объектов:
- Application objects — обеспечивают запуск и завершение приложения, создание форм, обработку событий.
В основном используются NPSAppManaged и StandardApp(с поддержкой событий). - Form objects — область экрана, которая содержит виджеты.
Основные формы:- FormBaseNew — пустая форма.
- Form — форма с кнопокой «ok».
- ActionForm — форма с двумя кнопками: «ok» и «cancel».
- FormWithMenus — форма, поддерживающая работу с меню.
- Textfield, PasswordEntry, MultiLineEdit, FilenameCombo* — формы для ввода данных.
- DateCombo, ComboBox, FilenameCombo — выпадающие списки.
- MultiSelect, MultiSelect, BufferPager — виджеты с возможностью выбора.
- Slider, TitleSlider — слайдеры.
Больше информации можно найти на официальном сайте с документацией.
Напишем Hello World
Формы удобно создавать, наследуя их от встроенных классов. Таким образом, можно переопределить встроенные методы для расширения функционала приложения.
Вот так выглядит простой Hello World
#!/usr/bin/env python3 import npyscreen class App(npyscreen.StandardApp): def onStart(self): self.addForm("MAIN", MainForm, name="Hello Habr!") class MainForm(npyscreen.ActionForm): # Конструктор def create(self): # Добавляем виджет TitleText на форму self.title = self.add(npyscreen.TitleText, name="TitleText", value="Hello World!") # переопределенный метод, срабатывающий при нажатии на кнопку «ok» def on_ok(self): self.parentApp.setNextForm(None) # переопределенный метод, срабатывающий при нажатии на кнопку «cancel» def on_cancel(self): self.title.value = "Hello World!" MyApp = App() MyApp.run()
Расположение элементов
По умолчанию виджеты занимают максимально возможное пространство.
Чтобы задать точные координаты, нужно задать параметры:- relx, rely — позиция виджета относительно начала координат формы.
- width, height, max_width, max_height — ограничения размеров виджета.
Пример
#!/usr/bin/env python3 import npyscreen class App(npyscreen.StandardApp): def onStart(self): self.addForm("MAIN", MainForm, name="Hello Habr!") class MainForm(npyscreen.FormBaseNew): def create(self): # Узнаем используемое формой пространство y, x = self.useable_space() self.add(npyscreen.TitleDateCombo, name="Date:", max_width=x // 2) self.add(npyscreen.TitleMultiSelect, relx=x // 2 + 1, rely=2, value=[1, 2], name="Pick Several", values=["Option1", "Option2", "Option3"], scroll_exit=True) # Можно использовать отицательные координаты self.add(npyscreen.TitleFilename, name="Filename:", rely=-5) MyApp = App() MyApp.run()
Боксы и пользовательские цвета
Сделать обертку в виде бокса просто — нужно создать класс, наследованный от BoxTitle и переопределить атрибут _contained_widget, положив туда виджет, который будет находиться внутри.
В npyscreen доступно несколько встроенных цветовых тем. При желании можно добавить свои. Установить их можно с помощью метода setTheme.
С настройкой цвета текста все немного сложнее. Мне пришлось расширить функционал библиотеки, чтобы это работало.Пример
#!/usr/bin/env python3 from src import npyscreen import random class App(npyscreen.StandardApp): def onStart(self): # Устанавливаем тему. По умолчанию используется DefaultTheme npyscreen.setTheme(npyscreen.Themes.ColorfulTheme) self.addForm("MAIN", MainForm, name="Hello Habr!") class InputBox(npyscreen.BoxTitle): # MultiLineEdit теперь будет окружен боксом _contained_widget = npyscreen.MultiLineEdit class MainForm(npyscreen.FormBaseNew): def create(self): y, x = self.useable_space() obj = self.add(npyscreen.BoxTitle, name="BoxTitle", custom_highlighting=True, values=["first line", "second line"], rely=y // 4, max_width=x // 2 - 5, max_height=y // 2) self.add(InputBox, name="Boxed MultiLineEdit", footer="footer", relx=x // 2, rely=2) color1 = self.theme_manager.findPair(self, 'GOOD') color2 = self.theme_manager.findPair(self, 'WARNING') color3 = self.theme_manager.findPair(self, 'NO_EDIT') color_list = [color1, color2, color3] first_line_colors = [random.choice(color_list) for i in range(len("first line"))] second_line_colors = [random.choice(color_list) for i in range(len("second"))] # Заполняем строки кастомными цветами obj.entry_widget.highlighting_arr_color_data = [first_line_colors, second_line_colors] MyApp = App() MyApp.run()
События и обработчики
Класс StandardApp в npyscreen поддерживает очередь событий.
В качестве обработки нажатий используется метод add_handlers.Пример
#!/usr/bin/env python3 import npyscreen import curses class App(npyscreen.StandardApp): def onStart(self): self.addForm("MAIN", MainForm, name="Hello Habr!") class InputBox1(npyscreen.BoxTitle): _contained_widget = npyscreen.MultiLineEdit def when_value_edited(self): self.parent.parentApp.queue_event(npyscreen.Event("event_value_edited")) class InputBox2(npyscreen.BoxTitle): _contained_widget = npyscreen.MultiLineEdit class MainForm(npyscreen.FormBaseNew): def create(self): self.add_event_hander("event_value_edited", self.event_value_edited) new_handlers = < # Устанавливаем ctrl+Q для выхода "^Q": self.exit_func, # Устанавливаем alt+enter для очистки inputbox curses.ascii.alt(curses.ascii.NL): self.inputbox_clear >self.add_handlers(new_handlers) y, x = self.useable_space() self.InputBox1 = self.add(InputBox1, name="Editable", max_height=y // 2) self.InputBox2 = self.add(InputBox2, footer="No editable", editable=False) def event_value_edited(self, event): self.InputBox2.value = self.InputBox1.value self.InputBox2.display() def inputbox_clear(self, _input): self.InputBox1.value = self.InputBox2.value = "" self.InputBox1.display() self.InputBox2.display() def exit_func(self, _input): exit(0) MyApp = App() MyApp.run()
Паттерн порталов в Angular: для чего нужен root-компонент в Taiga UI
Мой коллега Роман недавно объявил о выходе нашей новой библиотеки компонентов под Angular Taiga UI. В инструкциях Getting started сказано, что приложение нужно обернуть в некий tui-root . Давайте разберемся, что он делает, узнаем, как и зачем мы используем порталы и что это вообще такое.
Что такое портал?
Представьте себе компонент select . У него есть выпадашка с вариантами на выбор. Если хранить ее в том же месте в DOM, что и сам компонент, можно нарваться на ряд проблем. Нижестоящие элементы могут выскочить поверх, а контейнер — срезать содержимое:
Проблемы глубины обычно решаются через z-index , что запускает Войну Миров Z в вашем приложении. Часто можно встретить значения 100, 10000, 10001. Но даже если суметь это грамотно разрулить, от overflow: hidden все равно не убежишь. Что же делать?
Вместо размещения выпадашки рядом с хостом мы поместим ее в специальный контейнер поверх всего приложения. Тогда остальные элементы будут находиться в своем изолированном контексте и проблемы с z-index отпадут. Этот контейнер и есть «портал». Root-компонент в Taiga UI нужен как раз для создания подобных порталов. Рассмотрим его шаблон:
Общие и специализированные порталы
И tui-dialog-host , и tui-portal-host по сути своей — порталы. Но работают они по-разному. Для начала взглянем на второй. В Taiga UI он используется для показа выпадашек. Но это портал общего назначения. Он управляется очень простым сервисом:
@Injectable(< providedIn: 'root', >) export class TuiPortalService < private host: TuiPortalHostComponent; add(componentFactory: ComponentFactory, injector: Injector): ComponentRef < return this.host.addComponentChild(componentFactory, injector); >remove(: ComponentRef) < hostView.destroy(); >addTemplate(templateRef: TemplateRef, context?: C): EmbeddedViewRef < return this.host.addTemplateChild(templateRef, context); >removeTemplate(viewRef: EmbeddedViewRef) < viewRef.destroy(); >>
Сам компонент тоже незамысловат. Всё, что он делает, — выводит шаблоны и динамические компоненты поверх приложения. Это значит, что позиционирование, закрытие и вся иная логика лежит на плечах самих выводимых элементов.
Такой универсальный портал полезен, если потребуется что-то особенное. Например, кнопка «Наверх», всегда видимая над контентом.
Выпадашки
Создавая решение для выпадающих элементов, нужно задуматься над позиционированием. Тут у нас несколько вариантов:
- Позиционировать единожды и блокировать скролл. Так работает Material по умолчанию.
- Позиционировать единожды и закрывать, если произошел скролл. Так ведут себя нативные компоненты.
- Следить за положением хоста.
Мы решили пойти по третьему пути. Оказалось, он не так прост. Невозможно полностью синхронизировать продолжение двух элементов, даже через requestAnimationFrame . Поскольку запрос положения хоста вызывает рефлоу — к концу кадра, когда выпадашка поставлена в новое место хост еще немного изменит свою позицию. Это вызывает видимые скачки даже на быстрых машинах. Мы обошли это, использовав абсолютное позиционирование вместо фиксированного. Поскольку контейнер портала оборачивает все приложение, во время прокрутки положение выпадашки не меняется. Если хост сам находится в фиксированном контейнере, это не сработает. Но мы можем заметить это в момент открытия и позиционировать выпадашку также фиксировано.
Ну и есть еще это:
Если хост покидает видимую зону — нужно закрыть выпадашку. Этим занимается сервис Obscured. Он следит за хостом и закрывает выпадающий элемент при полном перекрытии.
Диалоги
Чтобы разобраться со специализированными порталами, взглянем на диалоги. Нотификации и подсказки работают так же, но на примере модальных окон можно обсудить еще пару интересных вопросов.
Вот так выглядит компонент с диалогами:
Как видите, в нем есть цикл ngFor , перебирающий конкретные элементы. Это позволяет заложить определенную логику, типа захвата фокуса или блокировки скролла. Также тут используется хитрое внедрение зависимостей, позволяющее диалогам не зависеть от модели данных и конкретного внешнего вида. Хост собирает потоки с диалогами с помощью специального мультитокена, объединяет их в один и выводит результат.
Таким образом, в одном приложении могут уживаться несколько видов диалогов. В Taiga UI заложено два дизайна всплывающих окон — базовый и имитирующий нативные алерты на мобильных устройствах. Последний выглядит по-разному на iOS и Android. Вы можете легко добавить свою реализацию. Давайте посмотрим, как это сделать.
Сервис показа модальных окон возвращает Observable . При подписке на него окно выводится, при отписке — закрывается. В этот поток диалог также может передавать данные. Для начала создадим свой компонент диалога. Всё, что тут важно, он может брать из DI-токена POLYMORPHEUS_CONTEXT . В этом объекте будет поле content с содержимым модалки и observer конкретного инстанса диалога. Его можно завершить через complete , что закроет диалог, и можно вернуть данные с помощью метода next . Кроме того, в этом объекте будут содержаться все параметры, которые вы пожелаете добавить к вашей реализации диалогов. Сервис для их показа наследуется от абстрактного класса:
const DIALOG = new PolymorpheusComponent(MyDialogComponent); const DEFAULT_OPTIONS: MyDialogOptions = < label: '', size: 's', >; @Injectable(< providedIn: 'root', >) export class MyDialogService extends AbstractTuiDialogService
Вам нужно лишь задать опции по умолчанию и ваш компонент.
Диалоги, как и все в Taiga UI, используют ng-polymorpheus для кастомизации контента. Узнать больше о том, как создавать гибкие, не зависящие от модели данных компоненты, вы можете из этой статьи.
Захват фокуса реализован директивой tuiFocusTrap . Поскольку фокус может попадать в выпадашки, стоящие позже по DOM, и мы можем иметь несколько открытых диалогов, ловушки игнорируют переход фокуса на нижестоящие элементы. Если же фокус перемещается куда-то выше по дереву — мы возвращаем его назад:
@HostListener('window:focusin.silent', ['$event.target']) onFocusIn(node: Node) < if (containsOrAfter(this.elementRef.nativeElement, node)) < return; >const focusable = getClosestKeyboardFocusable( this.elementRef.nativeElement, false, this.elementRef.nativeElement, ); if (focusable) < focusable.focus(); >>
Для блокировки скролла используется комбинация из директивы и небольшой логики внутри root-компонента. От root требуется скрыть скроллбар при открытии диалога, в то время как директива tuiOverscroll берет на себя скролл с помощью тача или колеса мыши. Существует CSS-правило overscroll-behavior. Однако его недостаточно. Оно не поможет, если диалог слишком маленький, чтобы не иметь внутреннего скролла. Поэтому мы создали специальную директиву с дополнительной логикой, блокирующей скролл, если он будет происходить в родительских контейнерах.
Бонус: что еще делает tui-root?
Мы обсудили все, что касается порталов. Давайте посмотрим, что еще заложено в root-компонент. Вы видели в шаблоне tui-scroll-controls . Это кастомный скроллбар, контролирующий глобальную прокрутку окна. Также вы могли заметить именованную проекцию контента типа . С помощью этих слотов вы можете подсунуть свое содержимое между слоями Taiga UI. Например, если вы используете другие решения для нотификаций и хотите правильно поместить их по глубине приложения.
Еще root регистрирует несколько event manager plugin`ов в DI. Подробнее об этом — в отдельной статье. Важно, чтобы TuiRootModule шел после BrowserModule , чтобы плагины были зарегистрированы в правильном порядке. Но не волнуйтесь: если вы ошибетесь — увидите сообщение в консоли.
Это все, что я хотел рассказать о порталах и root-компоненте. Taiga UI уже в open-source, и вы можете взглянуть на нее на «Гитхабе» и утянуть с npm. Вы можете погулять по демопорталу с документацией или поиграться вживую с этим StackBlitz-стартером. Не теряйтесь, в будущем мы обязательно расскажем больше про интересные фичи, которые у нас есть!
- Блог компании TINKOFF
- Веб-разработка
- Open source
- Angular
- TypeScript
При подготовке материала использовались источники:
https://procodings.ru/dev-ru/chto-takoe-faylovyy-provodnik-tui-i-zachem-on-vam-nuzhen/
https://habr.com/ru/articles/352904/
https://habr.com/ru/companies/tinkoff/articles/539302/