Перейти к содержимому

Оболочка и команды

Эта страница переведена с английского. Читать страницу на английском

Оболочка (shell) это рамка вокруг каждого экрана: меню, выбор языка, темы и часового пояса, всплывающие уведомления (toast) и палитра команд. Приложения, работающие как настольные инструменты, добавляют строку меню, панель инструментов, инспектор свойств, строку состояния и закрепляемые панели.

Меню

Меню строится из вызовов .menu(...) у features. Пункты, которые пользователю нельзя открывать, не попадают в HTML, а feature, чьи защитные проверки (guards) отказывают пользователю, ничего не показывает. Ссылка текущего экрана имеет aria-current="page".

Оболочка сайта

Рабочие экраны открываются в оболочке приложения с боковым меню. Публичные страницы, например магазин или каталог товаров, могут вместо этого открываться в оболочке сайта: название приложения как ссылка бренда, горизонтальное меню, кнопка меню на узких экранах, необязательные действия и подвал. Её выбирает feature; экраны других features сохраняют оболочку приложения, поэтому обе живут в одном приложении.

Feature::new("shop")
    .screen(shop_home)
    .screen(shop_delivery)
    .screen(shop_returns)
    .menu(|m| {
        m.group(t!("shop.menu.home"), Icon::Home)
            .item(t!("shop.menu.home"), shop_home)
            .group(t!("shop.menu.help"), Icon::Box)
            .item(t!("shop.delivery.title"), shop_delivery)
            .item(t!("shop.returns.title"), shop_returns)
    })
    .shell(Shell::site().header(|h| h.sign_in().language().theme()).footer(|f| {
        f.column(t!("shop.menu.help"), |c| {
            c.link(t!("shop.delivery.title"), shop_delivery).url(t!("shop.footer.source"), "https://example.com")
        })
        .text(t!("shop.footer.note"))
    })
    .bottom_nav([
        BottomNavItem::new(t!("shop.menu.home"), shop_home).icon(Icon::Home),
        BottomNavItem::new(t!("shop.menu.help"), shop_delivery).icon(Icon::Box),
    ]))
  • Меню. Верхнее меню перечисляет меню каждой feature в оболочке сайта. Группа из одного пункта это обычная ссылка; большая группа это кнопка, открывающая её ссылки (шаблон W3C APG disclosure navigation). Enter, пробел или щелчок переключают группу, стрелка вниз открывает её и переходит к первой ссылке, стрелки вверх и вниз, Home и End перемещают между её ссылками, Escape закрывает её и возвращает на кнопку, а Tab из группы или щелчок снаружи закрывает её. Стрелки влево и вправо перемещают между верхними пунктами.
  • Узкие экраны. Ниже 800 px меню прячется за кнопку меню с aria-expanded; Escape закрывает его. Без скрипта меню остаётся открытым, поэтому ничего не становится недоступным.
  • Действия. sign_in() показывает посетителям ссылку входа, а вошедшим пользователям ссылку выхода, когда у приложения есть страница входа; language() и theme() показывают те же выборы, что и оболочка приложения.
  • Нижняя панель. bottom_nav([...]) добавляет до пяти ссылок, закреплённых внизу экрана на телефонах (шириной 800 px и меньше), для мест, куда люди заходят чаще всего; больше пяти не компилируется. Каждый элемент является BottomNavItem с подписью, экраном с маршрутом и необязательным значком. У ссылки показанной страницы есть aria-current="page" (его поддерживает nav.js), ссылки на экраны, которые пользователь не может открыть, опускаются, панель держится в стороне от индикатора «домой» с помощью env(safe-area-inset-bottom), а внизу страницы остаётся место, чтобы панель ничего не закрывала. Верхнее меню остаётся для остального. Панели скрипт не нужен.
  • Подвал. Колонки ссылок на экраны (link, не показываются пользователям, которым их нельзя открывать) или адреса (url: путь, https:, http: или mailto:; App::check отклоняет другие схемы и ссылки на незарегистрированные экраны) и строка текста.

Переход между экраном оболочки сайта и экраном оболочки приложения идёт через nav.js без загрузки страницы: частичный ответ сообщает, какой оболочке он принадлежит (x-zeyn-shell), и когда она отличается от текущей рамки, runtime заменяет всю рамку так же, как при смене языка, и загружает таблицу стилей и скрипт другой оболочки. Ссылки на /_zeyn/route/{screen} (например, у breadcrumb) обрабатываются так же.

Команды

Команда объявляется один раз в Rust со своей подписью, сочетанием клавиш и действием. Зарегистрированная в feature, она появляется в палитре, а её сочетание клавиш работает на каждой странице.

pub fn new_product() -> Command {
    Command::open("stock.new_product", t!("pro.cmd_new_product"), product_editor, ProductParams { id: None })
        .shortcut("Ctrl+Alt+N")
        .icon(Icon::Plus)
}

pub fn restock() -> Command {
    Command::action("stock.restock_low", t!("pro.cmd_restock"), restock_low, ()).shortcut("Ctrl+Alt+R")
}

Feature::new("pro").command(new_product()).command(restock())
КонструкторВыполняет
Command::open(id, label, screen, params)Открывает экран как окно
Command::navigate(id, label, screen, params)Переходит на экран
Command::effect(id, label, effect)Эффект в браузере, например toast
Command::action(id, label, action, input)Действие на сервере; входные данные никогда не попадают в браузер

App::check сообщает о сочетаниях клавиш, которые нельзя прочитать, которые используют две команды или которые палитра оставляет себе.

Палитра команд

Alt+Shift+P по умолчанию открывает палитру (App::palette_shortcut меняет его, в том числе нарочно на клавишу браузера, например Ctrl+Shift+P): это ARIA combobox, который ищет по командам приложения, панели инструментов и контекстному меню текущего экрана и экранам меню. Недавно использованные команды идут первыми.

Feature может добавить к ней поиск на сервере. По мере ввода палитра вызывает действие и перечисляет его результаты под командами; выбор одного открывает страницу на месте. Так этот сайт ищет по своим страницам:

#[action(public)]
pub async fn search_docs(query: String, docs: Docs) -> Result<Vec<SearchHit>> {
    Ok(docs.search(&query))
}

Feature::new("docs").search(search_docs)

Сервер выполняет каждый поиск, который пользователю разрешено выполнять, с защитными проверками feature, разрешением действия и перехватчиками (interceptors), обрезает запрос до 100 символов, отбрасывает результаты, выходящие за пределы приложения, и возвращает не более 20.

MenuBar::new(t!("pro.menubar"))
    .menu(t!("pro.menu_file"), |m| m.command(new_product()).separator().command(restock()))
    .menu(t!("pro.menu_view"), |m| m.panels())

Toolbox::new("Tools")
    .tool("select", "Select", Some(Icon::Search))
    .tool("draw", "Draw", Some(Icon::Edit))

StatusBar::new("Status").left("Ready").right("UTF-8")
Ready
UTF-8
  • MenuBar следует шаблону ARIA menubar и содержит те же команды, что и палитра.
  • Toolbox это вертикальная группа кнопок-переключателей; выбранный инструмент отправляется как событие zeyn:tool.
  • PropertyInspector::new(title, form) редактирует выбранный объект формой, разбитой на группы .section(...).
  • У StatusBar есть живая область (live region), поэтому сообщения, отправленные в неё, читают программы чтения с экрана.

Закрепляемые панели

Dock::new(t!("pro.workspace"))
    .left("explorer", t!("pro.panel_explorer"), explorer())
    .center("products", t!("pro.panel_products"), products)
    .bottom("output", t!("pro.panel_output"), output())
    .right("properties", t!("pro.properties"), shape_inspector())

Пользователи перетаскивают вкладки между областями, изменяют размер областей указателем или клавиатурой и закрывают панели; MenuList::panels в меню возвращает их. Раскладка сохраняется для каждого пользователя и экрана в хранилище представлений приложения или в браузере, если у приложения его нет.