Skip to content
 
 

Repository files navigation

oscript-ui

Переиспользуемый UI-кит на чистом OneScript: кнопки, таблицы, плашки, поля, каркас форм, токены темы, глифы, значки и свой базовый лист стилей. Классы кита — желуди ОСени, но зависимости на неё у библиотеки нет: аннотации это синтаксис, читает их контейнер снаружи.

Кит самодостаточен: поставленный в чужой проект, он выглядит прилично без единой строки CSS от потребителя — и правила, и файлы проект при этом волен переопределить.

О предметной области приложения кит не знает ничего. Компонент принимает МОДЕЛЬ (структуру полей) и отдаёт HTML; кто собрал модель, ему неизвестно и знать не положено.

Установка

opm install oscript-ui

В packagedef приложения:

.ЗависитОт("oscript-ui", "0.1.0")

В точке входа — до сборки контейнера ОСени:

#Использовать oscript-ui

Директива обязана отработать раньше Новый Поделка(...): ОСень регистрирует желуди по ИЗВЕСТНЫМ типам, а тип, появившийся после конструктора, желудём уже не станет. Ни путей, ни сканирования каталогов не требуется.

Состав

src/Классы/
├── основа/         ЭкранированиеHTML · ЧтениеМодели · ЧтениеГлифа · НаборИконок —
│                   то, на чём стоят компоненты и что само разметки не печатает
├── компоненты/     ровно то, что отдаёт фасад НаборКомпонентов
├── холст/          Кит · ЭлементЭкрана · РазметкаЭлементов — экран деревом узлов
├── каркас/         Формы · ОбъявлениеФормы · ПечатьФормы · ПроверкаПоля · ЗначенияФормы
│                   РендерерНастроек — «объект → дерево разделов → секции → панель»
│                   КаркасСтраницы · ОбрамлениеИнсталляцииПоУмолчанию — корень страницы
├── оформление/     ТокеныТемы · ВыборОформления · РаскладкаСтатики
│                   ОтпечаткиСтатики · АктивыСтатики
│                   ОформлениеИнсталляцииПоУмолчанию · СтатикаИнсталляцииПоУмолчанию
├── тексты/         ТекстыИнтерфейса (резолвер ключей) · СловарьИнтерфейсаПоУмолчанию
├── витрина/        СборкаОбразцов — записи раздела и образца
│                   ОбразцыСтраницы · ОбразцыВвода · ОбразцыПоказа · ОбразцыХолста —
│                   каталоги ИСХОДНИКОВ образцов, по каталогу на слой словаря
├── НаборКомпонентов.os   фасад: по методу на компонент
└── ВитринаКита.os        витрина: исполняет исходники и печатает страницу
src/статика/
├── oscript-ui.css   базовый лист: правила примитивов кита, ни одного цвета
├── иконки/          глифы, по .svg на иконку, и текст разрешения на их использование
└── провайдеры/      значки чужих сервисов — отдаются файлами, а не вставляются в разметку

Имя файла глифа и есть имя иконки: замена иконки — замена файла, и рисунок, и viewBox берутся из него самого. Как проект перебивает правило базового листа и как подменяет любой из этих файлов — раздел «Переопределение».

Примеры

Компонент отдаёт готовую разметку по модели:

Кнопки = Поделка.НайтиЖелудь("Кнопка");
HTML = Кнопки.Отрисовать(Новый Структура("Подпись, Вариант, Иконка",
	"Сохранить", "основная", "сохранить"));

Фасад НаборКомпонентов даёт по методу на компонент — экрану достаточно одного желудя:

Набор = Поделка.НайтиЖелудь("НаборКомпонентов");
HTML = Набор.Карточка().Отрисовать(Новый Структура("Заголовок, Тело",
	"Пул main", Набор.Тег().Отрисовать("stable")));

Экран можно собирать не строками, а деревом узлов — тогда теги и CSS-классы печатает РазметкаЭлементов, единственное место, где узел становится тегом:

Холст = Кит.Холст();
Шапка = Холст.Добавить(Кит.Ряд("выбор"));
Шапка.Добавить(Кит.Надпись("название")).Добавить(Кит.Текст("Витрина"));
HTML = Холст.Рендер();

Роды узлов

Род — что узел такое; вид — как он выглядит, значением из закрытого набора, а не именем CSS-класса. Неизвестный вид останавливает сборку, а не даёт молча неоформленный экран.

Род Фабрика Что это
холст Кит.Холст() прозрачная группировка: своего тега нет, пустой печатается пустой строкой
область Кит.Область(Вид) блок со своим тегом — на него вешают признак и роль
ряд Кит.Ряд(Вид) элементы в строку с переносом
раскрывашка Кит.Раскрывашка(Вид) <details>; свёрнутая строка наполняется через Шапка(), Подпись() называет её скринридеру
статья Кит.Статья(Вид) <article>: самостоятельный кусок, который можно вынуть из списка
список Кит.Список(Вид) <ul>; в него кладут пункты
пункт Кит.Пункт() <li>, холст
свойства Кит.Свойства() <dl>: перечисление пар «имя — значение»
свойство Кит.Свойство(Имя) пара <dt>/<dd>: имя подписью, значение детьми
группа Кит.Группа(Подпись, Вид) <fieldset> с легендой
форма Кит.Форма(Действие, Токен, Вид) POST вошедшего; без CSRF-токена не собирается
форма-без-сессии Кит.ФормаБезСессии(Действие, Вид) POST посетителя, у которого сессии ещё нет
форма-отбора Кит.ФормаОтбора(Действие, Вид) GET: на сервере ничего не меняет
форма-без-токена Кит.ФормаБезТокена(Действие, Вид) POST, у которого сессия есть, а токена сервер не спрашивает: цена подделки — неудобство (сменить тему, выйти)
таблица Кит.Таблица(Вид) список; Пусто() — что показать вместо неё
колонка Кит.Колонка(Подпись) заголовок столбца, холст
запись Кит.Запись() строка таблицы; Разворот() — подробность под ней
ячейка Кит.Ячейка(Вид) холст внутри записи; «столбиком» — двухэтажная
заголовок Кит.Заголовок(Уровень, Вид) заголовок 1..6
надпись Кит.Надпись(Вид) кусок строки со своей ролью в ней
примечание Кит.Примечание(Текст, Вид) абзац мелким шрифтом; холст
ссылка Кит.Ссылка(Адрес, Вид) холст; адрес чужой схемы ссылкой не станет
текст Кит.Текст(Значение) экранируется по своей природе
код Кит.Код(Значение, Вид) машинное значение моноширинным; «блочный» — образец в <pre>, «приглушённый» — не спорит с соседним текстом
шаблон Кит.Шаблон(Образец) текст с местами %1…%N, которые занимают дети
картинка Кит.Картинка(Адрес, Вид) <img>; пустой адрес не даёт битой картинки, Подпись() становится alt
компонент Кит.Кнопка(…), Кит.Карточка(…) обёртка над компонентом; со слотом принимает детей
документ Кит.Документ(Язык, Тема) корень страницы: объявление типа и <html>; внутрь кладут только голову и тело
голова Кит.Голова() <head>; внутрь — заглавие, объявления, ресурсы, палитра
титул Кит.Титул(Текст) <title> — как страница названа во вкладке
мета Кит.Мета(Имя, Содержание), Кит.Кодировка(Имя) объявление в голове документа
ресурс Кит.Ресурс(Отношение, Адрес, Тип, Размеры) <link>: лист стилей, иконка сайта; без адреса не печатается
стиль Кит.Стиль(Имя, Правила) <style> инлайном; правила — ДОВЕРЕННЫЙ CSS, экранирование там не защищает
скрипт Кит.Скрипт(Адрес) <script defer>: разбор разметки не останавливает
тело Кит.Тело() <body>
шапка Кит.Шапка(Вид) <header> — верхняя полоса страницы
основное Кит.Основное(Вид) <main>: то, ради чего страницу открыли
подвал Кит.Подвал(Вид) <footer>
навигация Кит.Навигация(Вид) <nav> — ориентир для скринридера
готовое Кит.Готовое(Разметка) ⚠ костыль перехода: сырой HTML

Таблица собирается тем же деревом, а ячейка принимает элементы, а не готовую строку:

Таблица = Кит.Таблица();
Таблица.Добавить(Кит.Колонка("Имя"));
Таблица.Пусто().Добавить(Кит.Примечание("Ничего не найдено"));

Запись = Таблица.Добавить(Кит.Запись());
Запись.Добавить(Кит.Ячейка()).Добавить(Кит.Код("main"));
Запись.Разворот().Добавить(Кит.Примечание("подробности"));

Контейнерный компонент берёт содержимое детьми, а не полем модели:

Карточка = Кит.Карточка(Новый Структура("Заголовок", "Пулы"));
Карточка.Добавить(Кит.Примечание("две штуки"));

Сетка раскладывает детей сама, поэтому получает их по одному — каждый ребёнок становится своей ячейкой:

Сетка = Кит.Сетка(Новый Структура("Колонки", 2));
Сетка.Добавить(Кит.Примечание("слева"));
Сетка.Добавить(Кит.Примечание("справа"));

Плашка тоже берёт содержимое детьми, когда его надо разметить своими узлами — например подписать отдельным <span>, за который возьмётся скрипт страницы:

Плашка = Кит.Плашка(Новый Структура("Вид", "ok"));
Плашка.Добавить(Кит.Надпись().Признак("chip-caption", "")).Добавить(Кит.Текст("lts"));

Свойства узла

Помимо вида у каждого узла есть свойства, которые печатаются атрибутами: Признак(Имя, Значение) (data-*, печатается даже пустым), Роль(Значение), Подпись(Текст), Подсказка(Текст) (title), Отключить(), Скрыть(). Четыре свойства спрашиваются только своим родом и на чужом отбиваются: Высота(Пикселей) и Ширина(Пикселей) — у картинки, СФайлами() — у формы, Открыть() — у раскрывашки.

Корень страницы

КаркасСтраницы собирает документ целиком — от объявления типа до отложенного скрипта — вокруг готового содержимого:

Каркас = Поделка.НайтиЖелудь("КаркасСтраницы");
Ответ.ТелоТекст = Каркас.Страница("Каталог", Тело,
    Каркас.КонтекстСтраницы(Пользователь, КукиЗапроса));

Тело — либо элемент экрана, либо строка непереведённого экрана: строку каркас заворачивает в готовый фрагмент, и два стиля сборки сосуществуют. Документ(…) отдаёт тот же каркас элементом — Дерево() на нём печатает, из чего собрана страница.

Кто такая инсталляция, каркас не знает: знак, подпись, адреса ходов, иконки и разделы навигации приходят контрактом ОбрамлениеИнсталляции (ниже).

Обёртки над компонентами

Компонент печатает себя целиком, а в дереве экрана виден по имени — компонент[кнопка], а не безымянный кусок HTML. Обёрнуты все компоненты, кроме Таблица: её заменил раскладочный род.

Фабрика Что это Вид узла
Кит.Кнопка(Модель) кнопка формы или ссылка-кнопка кнопка
Кит.Плашка(Модель) короткий признак: состояние, тип, источник плашка
Кит.РядПлашек(Список) ряд плашек одним элементом плашки
Кит.Поле(Модель) поле ввода со своей меткой и отказом поле
Кит.ПолеИзКонфига(Модель) поле, значение которого задано конфигурацией поле-конфига
Кит.СкрытоеПоле(Имя, Значение) скрытое поле формы скрытое
Кит.БлокКоманды(Модель) копируемая команда команда
Кит.Карточка(Модель) рамка с заголовком; тело — дети карточка
Кит.ОпаснаяЗона(Модель) необратимые действия; тело — дети зона
Кит.Сетка(Модель) раскладка равнозначных блоков; ячейки — дети сетка
Кит.ШапкаОбъекта(Модель) первый экран объекта; действия — дети шапка
Кит.ЗаголовокСтраницы(Модель) заголовок страницы с пояснением заглавие
Кит.ПустоеСостояние(Модель) значок, объяснение и призыв к действию пустое
Кит.Табы(Модель) полоса вкладок, каждая своим адресом вкладки
Кит.СтрокаПоиска(Модель) готовая строка поиска по списку поиск
Кит.Пагинация(Модель) постраничная навигация страницы
Кит.СтраницаОшибки(Модель) тело страницы отказа отказ
Кит.Проводник(Модель) список шагов многошаговой задачи шаги
Кит.Иконка(Модель) значок из набора иконок иконка
Кит.РядМеты(Элементы, Вид) пары «значок + значение» в строку мета
Кит.ПолосаЗаполнения(Модель) доля занятого от предела полоса
Кит.Тумблер(Модель) поле выбора из двух состояний тумблер
Кит.Меню(Модель) кнопка-открывашка и панель пунктов меню
Кит.Плитка(Модель) сводный показатель одним числом плитка
Кит.Уведомление(Модель) сообщение о результате действия (компонент Тост) уведомление
Кит.ДеревоРазделов(Модель) навигация по дому настроек разделы
Кит.ПанельСохранения(Модель) липкая полоса с кнопкой «Сохранить» панель

Два имени рядом, которые легко перепутать: Кит.Иконка(Имя) рисует глиф из набора иконок, а Кит.Картинка(Адрес, "значок") показывает внешнюю картинку размером со значок. Кит.СтрокаПоиска — готовая форма поиска по списку, Кит.ФормаОтбора — пустая GET-форма, которую наполняют сами.

Объявленная форма знает свои поля и умеет разобрать пришедшее, поэтому она входит в дерево тем же узлом-компонентом, а не переписывается родами:

Холст.Добавить(Кит.ОбъявленнаяФорма(Объявление, Значения, "/pools", Токен, "столбик"));
Холст.Добавить(Кит.Окно(Объявление, Значения, "/pools", Токен, СвойстваОкна));

Четыре контракта, которые подставляет инсталляция

Кит объявляет каждый контракт ПРОЗВИЩЕМ и кладёт под ним пустышку. Приложение подставляет свою реализацию тем же прозвищем плюс &Верховный:

Прозвище Пустышка кита Что даёт инсталляция
СловарьИнтерфейса пустой словарь тексты по ключам
ОформлениеИнсталляции пустое оформление тему, акцент по умолчанию и имя cookie выбора
СтатикаИнсталляции ни одного файла каталог статики, её файлы и их типы
ОбрамлениеИнсталляции пустое обрамление знак, подпись подвала, язык, иконки, адреса ходов (корень, вход, кабинет, выход, переключение оформления), разделы верхней и приватной навигации и имя вошедшего
&Верховный
&Прозвище("СловарьИнтерфейса")
&Желудь
Процедура ПриСозданииОбъекта() Экспорт

Имя класса контрактом быть НЕ МОЖЕТ: ОСень ищет желудь сперва в определениях по имени и только потом по прозвищу, поэтому класс, названный СловарьИнтерфейса, не перебивается ничем — до сравнения &Верховных дело не доходит.

Ключи, которые кит спрашивает у словаря

Своих строк у кита нет, но семнадцать ключей он запрашивает сам. Ключа нет в словаре — на экране появится видимый маркер [?ключ]:

Ключ Где нужен
form.error.required · form.error.number · form.error.min · form.error.max · form.error.choice ПроверкаПоля — отказы формы по полю
settings.frame.save · settings.frame.nav.title · settings.frame.crumbs.label · settings.frame.empty РендерерНастроек — каркас дома настроек
chrome.top.nav.title · chrome.top.login · chrome.top.logout · chrome.top.office · chrome.top.theme.toggle · chrome.top.theme.to-dark · chrome.top.theme.to-light · chrome.office.nav.title КаркасСтраницы — шапка и навигация приватной части

Статика: два корня

РаскладкаСтатики знает два каталога разной природы. Собственный каталог кита она находит сама — от файла своего класса вверх по имени статика, и это работает и после установки в oscript_modules. Каталог инсталляции вместе со списком её файлов приходит контрактом СтатикаИнсталляции.

Что везёт сама библиотека:

Файл Где лежит Как отдаётся
oscript-ui.css src/статика/ базовый лист стилей — правила примитивов кита
github.svg · gitlab.svg · google.svg · key.svg src/статика/провайдеры/ значки чужих сервисов; ключи закрытым списком отдаёт КлючиЗначков()
phosphor-LICENSE.txt src/статика/иконки/ текст разрешения на набор рисунков
*.svg src/статика/иконки/ глифы; в разметку они попадают телом, а не адресом

Что даёт инсталляция — лицо конкретного приложения: знак сайта, иконка вкладки, иконка домашнего экрана, свой лист стилей.

&Верховный
&Прозвище("СтатикаИнсталляции")
&Желудь
Процедура ПриСозданииОбъекта() Экспорт
КонецПроцедуры

Функция Каталог() Экспорт
	Возврат "/путь/к/приложению/src/статика";
КонецФункции

Функция Файлы() Экспорт
	Результат = Новый Соответствие();
	Результат.Вставить("app.css", Новый Структура("Подкаталог, ТипКонтента",
		"", "text/css; charset=utf-8"));
	Результат.Вставить("mark.svg", Новый Структура("Подкаталог, ТипКонтента",
		"бренд", "image/svg+xml"));
	Возврат Результат;
КонецФункции

АктивыСтатики отдаёт по этой раскладке односегментный адрес с отпечатком содержимого: /static/app-1f2e3d4c5b6a7988.css. Правка файла без смены адреса невозможна как класс — адрес есть функция содержимого. У каждого файла свой отпечаток, и базовый лист кита — такой же файл, как остальные: АктивыСтатики.АдресБазовогоЛиста().

Переопределение: как проект перебивает кит

Кит самодостаточен: поставленный в чужой проект, он выглядит прилично сам по себе, без единой строки CSS от потребителя. Всё, что проект хочет изменить, он меняет одним из трёх способов — новых механизмов заводить не нужно.

Правило — вторым листом в каскаде

КаркасСтраницы печатает в <head> два тега <link rel="stylesheet"> в этом порядке:

  1. базовый лист библиотеки — АктивыСтатики.АдресБазовогоЛиста();
  2. лист инсталляции — поле АдресСтилей обрамления.

Правило из листа инсталляции побеждает китовое той же специфичностью: ни !important, ни лишний селектор не нужны. Пустой АдресСтилей не печатается вовсе — инсталляции, которой хватает базового листа, второй тег не достаётся.

/* лист инсталляции: кнопка приложения — прямоугольная */
.button { border-radius: 0; }

Порядок каскада стережёт КаркасСтраницы_Тесты.БазовыйЛистСтоитПередЛистомИнсталляции.

Файл — одноимённым объявлением

Раскладка кладёт свои файлы первыми, файлы инсталляции — поверх. Имя, объявленное инсталляцией, вытесняет китовое целиком: файл поедет из её каталога, с её типом и с её отпечатком.

// свой значок вместо китового, своя иконка вместо своей же — механизм один
Функция Файлы() Экспорт
	Результат = Новый Соответствие();
	Результат.Вставить("github.svg", Новый Структура("Подкаталог, ТипКонтента",
		"значки", "image/svg+xml"));
	Возврат Результат;
КонецФункции

Так же подменяется и сам базовый лист — объявлением файла oscript-ui.css, — но это крайняя мера: перебить правило вторым листом дешевле и не рвёт связь с обновлениями библиотеки.

Палитра — контрактом оформления

Цвета, интервалы и радиусы кит печатает инлайновым <style id="токены-темы"> до обоих листов. Менять их полагается не правилом, а контрактом ОформлениеИнсталляции: он отдаёт тему (тёмная/светлая) и акцент #rrggbb, из которого ТокеныТемы выводит ступень 500 акцентной рампы и контрастный цвет текста на заливке.

&Верховный
&Прозвище("ОформлениеИнсталляции")
&Желудь
Процедура ПриСозданииОбъекта() Экспорт
КонецПроцедуры

Функция Оформление() Экспорт
	Возврат Новый Структура("Тема, Акцент", "светлая", "#2f7d4c");
КонецФункции

Токен, которого контракт не покрывает, переопределяется листом инсталляции — он идёт после инлайнового блока:

:root { --layout-width: 1280px; }

Как посмотреть витрину

Витрина — для того, кто пишет экран на этой библиотеке. Под каждым образцом стоит не только картинка, но и исходник, которым эта картинка нарисована: копируете кнопкой справа от кода, вставляете к себе — получаете ровно то, что видели. Поднимается из репозитория одной командой, сервера не требует:

oscript витрина.os

Команда собирает кит и кладёт рядом витрина.html — путь печатается в консоль, файл открывается браузером. Внутри — весь словарь библиотеки: по карточке на компонент (кнопка во всех вариантах, плашки, карточки, таблицы, формы, пустые состояния, пагинация, тосты и остальные), карточка на каждую группу родов узлов холста — от корня документа до готового куска чужой разметки, — и таблица родов с тегом, признаком вложения и списком известных видов. Ни одного рода и ни одной обёртки над компонентом мимо витрины не проходит: это стережёт ВитринаКита_Тесты.ВитринаПоказываетВесьСловарьКита.

В исходниках доступны три желудя — те же, что экран приложения достаёт из ОСени:

Имя Что это
Набор фасад компонентов: Набор.Кнопка().Отрисовать(Модель)
Кит холст: экран собирается деревом узлов
Формы фабрика объявлений формы

Каждый образец заканчивается присваиванием переменной HTML — это и есть разметка, которую он отдаёт. Больше в исходнике нет ничего: он самодостаточен ровно настолько, чтобы работать у вас без правок.

Показанный код — источник образца, а не его копия. Витрина не хранит рядом с картинкой её описание: она берёт исходник и ИСПОЛНЯЕТ его, а нарисованным показывает результат. Разойтись им негде — расходиться нечему. Что это правда, проверяет ВитринаКита_Тесты.ПоказанныйКодРисуетТотЖеОбразец: он исполняет каждый исходник заново, в чужом модуле, где нет ничего кроме трёх желудей, и сверяет с показанным.

Кнопка «Копировать» — обычный Блок команды кита, тот же, что на страницах приложения: код лежит в разметке, а кнопку оживляет короткий скрипт страницы по атрибуту data-copy. Своего скрипта библиотека не везёт и в этом случае: без JavaScript кнопка молчит, а сам код остаётся видимым и выделяемым — его печатает разметка, а не скрипт.

Образцы намеренно безымянные по предмету: проекты, документы, метки — данные, которые читаются в любом приложении. Витрина есть лицо библиотеки, и подставлять в неё словарь одного конкретного продукта значило бы стереть ту самую границу, ради которой кит и живёт отдельно. Кладёте свой компонент — приводите образец на таких же нейтральных данных и назовите в разделе, что из словаря он показывает: род:<имя> либо компонент:<вид>.

Оформление у витрины своё, из коробки: базовый лист библиотеки вшивается в страницу без единого ключа — ровно тот вид, который получит чужой проект сразу после установки.

Управлять можно двумя ключами:

Ключ Что делает
--стили <путь.css> докладывает лист инсталляции ВТОРЫМ — как в каскаде живой страницы
--вывод <путь.html> кладёт страницу по указанному пути

С листом хаба поверх базового это выглядит так — видно ровно то, что проект перебивает:

oscript витрина.os --стили ../oscript_openhub/src/статика/openhub.css

Тесты

oneunit execute

Лицензия

MIT. Глифы — набор Phosphor, текст разрешения лежит рядом с ними в src/статика/иконки/phosphor-LICENSE.txt.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages