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.csssrc/статика/базовый лист стилей — правила примитивов кита
github.svg · gitlab.svg · google.svg · key.svgsrc/статика/провайдеры/значки чужих сервисов; ключи закрытым списком отдаёт КлючиЗначков()
phosphor-LICENSE.txtsrc/статика/иконки/текст разрешения на набор рисунков
*.svgsrc/статика/иконки/глифы; в разметку они попадают телом, а не адресом

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

&Верховный&Прозвище("СтатикаИнсталляции")
&ЖелудьПроцедураПриСозданииОбъекта() ЭкспортКонецПроцедурыФункцияКаталог() ЭкспортВозврат"/путь/к/приложению/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