oscript-ui
oscript-ui

Слот картинки

Слот картинки — загрузка одной картинки: логотипа, значка вкладки, обложки. Вместо системного поля «Выберите файл», таблицы «что стоит сейчас» и отдельной кнопки — один слот: видно, что стоит, и одна кнопка, чтобы заменить. Выбор файла отправляет его сразу.

Когда использовать#

  • Картинка одна и заменяется целиком: логотип инсталляции, аватар проекта, значок вкладки.
  • Человеку важно видеть, что стоит сейчас, прежде чем менять.
  • У картинки есть стандартная, к которой можно вернуться.

Не берите слот для вложений к записи и для нескольких файлов сразу: там нужен список файлов, а не одна рамка. Для файла, который уходит вместе с остальной формой (договор к заявке), хватит поля словом Файл().

Слот#

Кит.СлотКартинки(Имя, Подпись) — узел рода «слот-картинки»: предпросмотр на шахматке, подпись и строка состояния, кнопка «Загрузить» или «Заменить». Имя — имя файлового поля, под ним сервер получит файл; подпись называет картинку. Слот — своя форма (multipart/form-data, тихая отправка): адрес загрузки — слово Форма(Хвост), как у секции, токен — у предков. Имя Кит.Картинка занято картинкой-<img>, поэтому узел назван слотом.

Стандартный и свой — выберите файл

Логотип

Стандартная

Логотип

logo.png · 12 КБ · 3 октября

HTML = Кит.Холст( Кит.СлотКартинки("logo", "Логотип").Адрес("/static/знак.svg") .Форма("/образец-картинки").Якорь("slot-standart"), Кит.СлотКартинки("logo", "Логотип").Адрес("/static/знак.svg") .Состояние("logo.png · 12 КБ · 3 октября").Принимает("image/png,image/svg+xml") .Форма("/образец-картинки").Сброс("/образец-картинки/сброс").Якорь("slot-svoi")) .Токен("т0кен-сессии").Рендер();

Оба слота уходят на образец с сервером: там видно, как сервер отвечает на загрузку и на сброс.

  • Адрес(ТекущаяКартинка) — что стоит сейчас. Без адреса слот пуст: одна шахматка и кнопка «Загрузить». Годится адрес http(s), путь своего приложения или встроенная картинка data:image/….
  • Состояние(Текст) — строка под подписью: имя файла, размер, дата. Без неё — «Стандартная» (ключ imgslot.default), а у слота со сбросом — «Своя картинка» (imgslot.custom).
  • Принимает(Типы) — атрибут accept, по умолчанию image/*. Это подсказка окну выбора файла, а не проверка: тип и размер присланного сверяет сервер.
  • КнопкаЗагрузки(Подпись) — своя подпись кнопки вместо «Заменить» (imgslot.replace) и «Загрузить» (imgslot.upload).

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

Отказ сервера#

Тип и размер присланного сверяет сервер, и отказ он печатает словом Ошибка(Текст) — под строкой состояния, цветом ошибки, как отказ под полем. Файловое поле описано и состоянием, и отказом (aria-describedby) и помечено негодным (aria-invalid); у отказа роль alert — скринридер скажет его сразу. Сервер отвечает на неудачную загрузку страницей со статусом 4xx, где слот напечатан с отказом: тихая отправка кладёт её на место, и отказ встаёт под тем слотом, который его получил. Без скрипта та же страница приходит обычной навигацией.

Отказ под слотом

Логотип

logo.png · 12 КБ

HTML = Кит.Холст(Кит.СлотКартинки("logo", "Логотип").Адрес("/static/знак.svg") .Состояние("logo.png · 12 КБ").Форма("/образец-картинки") .Ошибка("Файл «отчёт.pdf» — не картинка: годятся PNG, SVG и WebP.").Якорь("slot-otkaz")) .Токен("т0кен-сессии").Рендер();

На образце с сервером выберите файл не картинки — сервер ответит отказом 422 так же.

Вернуть стандартную#

Слово Сброс(Адрес) добавляет меню ⚙ с пунктом «Вернуть стандартную». Пункт отправляет отдельную форму сброса, а её отправку перехватывает подтверждение: то же окно, что Кит.Подтверждение, с вопросом о картинке и кнопкой цвета ошибки. Уходит форма кнопкой окна — с полем confirmed=1 (Подтверждение.ИмяПоляПодтверждения()).

Без скрипта форма сброса уходит без поля подтверждения. Сервер отвечает перенаправлением на страницу, где тот же слот напечатан словом Открыто(): окно сброса стоит поверх страницы, и человек подтверждает там. Слово АдресЗакрытия(Адрес) — то же, что у окна и подтверждения (дев-сборка 10): крестик и «Отмена» окна сброса — ссылки на страницу без окна, поэтому окно, напечатанное открытым, закрывается и в браузере без скрипта, а со скриптом — на месте.

Сервер спрашивает подтверждение страницей

<div class="imgslot" id="slot-sbros"><form method="post" action="/образец-картинки" enctype="multipart/form-data" class="imgslot__upload" data-quiet=""><input type="hidden" name="_csrf" value="т0кен-сессии"><div class="imgslot__preview"><img class="imgslot__image" src="/static/знак.svg" alt=""></div><div class="imgslot__text"><p class="imgslot__label" id="slot-sbros-label">Логотип</p><p class="imgslot__state" id="slot-sbros-state">logo.png · 12 КБ</p></div><div class="imgslot__actions"><label class="imgslot__pick"><span class="button button--ghost button--sm imgslot__face" id="slot-sbros-face"><span>Заменить</span></span><input type="file" name="logo" accept="image/*" class="imgslot__file" aria-labelledby="slot-sbros-face slot-sbros-label" aria-describedby="slot-sbros-state" data-autosubmit></label><button type="submit" class="button button--ghost button--sm"><span>Отправить файл</span></button></div></form><form method="post" action="/образец-картинки/сброс" class="imgslot__reset" id="slot-sbros-reset-form" data-confirm="slot-sbros-reset" data-quiet=""><input type="hidden" name="_csrf" value="т0кен-сессии"><div class="imgslot__gear"><div class="menu"><button type="button" class="button button--ghost button--sm button--icon" title="Действия: Логотип" aria-label="Действия: Логотип" popovertarget="menu-22"><svg class="icon" viewBox="0 -960 960 960" aria-hidden="true" focusable="false" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"></path></svg></button><div id="menu-22" popover class="menu__panel"><p class="menu__title">Действия: Логотип</p><button type="submit" class="button button--ghost button--sm"><svg class="icon" viewBox="0 -960 960 960" aria-hidden="true" focusable="false" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M480-120q-138 0-240.5-91.5T122-440h82q14 104 92.5 172T480-200q117 0 198.5-81.5T760-480q0-117-81.5-198.5T480-760q-69 0-129 32t-101 88h110v80H120v-240h80v94q51-64 124.5-99T480-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-480q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-120Zm112-192L440-464v-216h80v184l128 128-56 56Z"></path></svg><span>Вернуть стандартную</span></button></div></div></div></form><dialog class="modal modal--danger modal--confirm" id="slot-sbros-reset" aria-labelledby="slot-sbros-reset-title" aria-describedby="slot-sbros-reset-lead" open><div class="modal__box"><div class="modal__head"><h2 class="modal__title" id="slot-sbros-reset-title">Вернуть стандартную картинку: Логотип?</h2><a href="/образец-картинки" class="button button--link button--icon" title="Закрыть окно" aria-label="Закрыть окно" data-closes="slot-sbros-reset"><svg class="icon" viewBox="0 -960 960 960" aria-hidden="true" focusable="false" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"></path></svg></a></div><p class="modal__lead" id="slot-sbros-reset-lead">Загруженная картинка удалится, на её место встанет стандартная.</p><div class="modal__actions"><a href="/образец-картинки" class="button button--ghost" data-closes="slot-sbros-reset"><span>Отмена</span></a><button type="submit" class="button button--danger" name="confirmed" value="1" form="slot-sbros-reset-form"><span>Вернуть стандартную</span></button></div></div></dialog></div>
HTML = Кит.Холст(Кит.СлотКартинки("logo", "Логотип").Адрес("/static/знак.svg") .Состояние("logo.png · 12 КБ").Форма("/образец-картинки") .Сброс("/образец-картинки/сброс").Открыто().АдресЗакрытия("/образец-картинки").Якорь("slot-sbros")) .Токен("т0кен-сессии").Рендер();

Без скрипта#

Без скрипта слот показывает системное файловое поле и кнопку «Отправить файл» (imgslot.send) — форма уходит обычной навигацией, а сервер отвечает перенаправлением обратно на страницу. Со скриптом поле уходит из вида под кнопку «Заменить», кнопка отправки прячется, и выбор файла отправляет форму сам: на файловом поле стоит признак data-autosubmit — тот же, что у тумблера со словом СохранятьСразу.

Слот — своя форма, поэтому внутрь другой формы он не кладётся: браузер выбросил бы вложенный <form>, и файл ушёл бы не туда. Кит останавливает такую сборку. В доме настроек положите слоты в секцию без .Форма(), а поля, которые сохраняются кнопкой, — в соседнюю секцию-форму.

Что печатает сервер

<div class="imgslot" id="slot-razmetka"><form method="post" action="/образец-картинки" enctype="multipart/form-data" class="imgslot__upload" data-quiet=""><input type="hidden" name="_csrf" value="т0кен-сессии"><div class="imgslot__preview"></div><div class="imgslot__text"><p class="imgslot__label" id="slot-razmetka-label">Значок вкладки</p><p class="imgslot__state" id="slot-razmetka-state">Стандартная</p></div><div class="imgslot__actions"><label class="imgslot__pick"><span class="button button--ghost button--sm imgslot__face" id="slot-razmetka-face"><span>Загрузить</span></span><input type="file" name="icon" accept="image/*" class="imgslot__file" aria-labelledby="slot-razmetka-face slot-razmetka-label" aria-describedby="slot-razmetka-state" data-autosubmit></label><button type="submit" class="button button--ghost button--sm"><span>Отправить файл</span></button></div></form></div>
HTML = Кит.Холст(Кит.СлотКартинки("icon", "Значок вкладки") .Форма("/образец-картинки").Якорь("slot-razmetka")) .Токен("т0кен-сессии").Рендер();

Гайдлайны#

Значок вкладки

icon.png · 2 КБ

ДелайтеПодпись называет картинку, состояние — что стоит
Код
HTML = Кит.Холст(Кит.СлотКартинки("icon", "Значок вкладки").Адрес("/static/знак.svg")
		.Состояние("icon.png · 2 КБ").Форма("/образец-картинки").Якорь("delat-podpis"))
	.Токен("т0кен-сессии").Рендер();

Файл

Стандартная

Не делайте«Файл» без имени: на странице с двумя слотами не понять, какой что меняет
Код
HTML = Кит.Холст(Кит.СлотКартинки("icon", "Файл").Адрес("/static/знак.svg")
		.Форма("/образец-картинки").Якорь("ne-delat-fail"))
	.Токен("т0кен-сессии").Рендер();

Логотип

Стандартная

ДелайтеСброс — только когда стоит своя картинка
Код
HTML = Кит.Холст(Кит.СлотКартинки("logo", "Логотип").Адрес("/static/знак.svg")
		.Форма("/образец-картинки").Якорь("delat-bez-sbrosa"))
	.Токен("т0кен-сессии").Рендер();

Логотип

Стандартная

Не делайте«Вернуть стандартную» у стандартной: пункт, который ничего не меняет
Код
HTML = Кит.Холст(Кит.СлотКартинки("logo", "Логотип").Адрес("/static/знак.svg")
		.Состояние("Стандартная").Форма("/образец-картинки")
		.Сброс("/образец-картинки/сброс").Якорь("ne-delat-sbros"))
	.Токен("т0кен-сессии").Рендер();

Доступность#

  • Картинка в рамке декоративна (alt=""): что это, говорит подпись рядом, а что нарисовано на логотипе, словами не пересказать.
  • Файловое поле названо кнопкой и подписью (aria-labelledby: «Заменить Логотип»), а строка состояния — его описание (aria-describedby): скринридер слышит, что стоит сейчас. Отказ сервера — второе описание, поле при нём помечено aria-invalid.
  • Со скриптом поле уходит из вида, но не из порядка обхода: фокус с клавиатуры встаёт на него, обводку рисует кнопка над ним, Enter и пробел открывают выбор файла.
  • Меню ⚙ названо «Действия: Логотип». Окно сброса названо вопросом и описано последствиями; закрыли его — фокус возвращается на кнопку ⚙.

Модель#

Кит.СлотКартинки()src/Классы/холст/Кит.os

Слот картинки — загрузка одной картинки (логотип, значок, обложка): предпросмотр на
шахматке, подпись и строка состояния, кнопка «Загрузить / Заменить» и меню ⚙
с «Вернуть стандартную» через подтверждение. Слот — своя форма: выбор файла отправляет
её сразу, тихо (multipart); без скрипта видно файловое поле и кнопку отправки. Поэтому
внутрь другой формы слот не кладут. Адрес загрузки — слово Форма(Хвост) (адрес контекста
с хвостом, как у секции; без слова — сам адрес контекста), токен — у предков.
Имя Кит.Картинка занято картинкой-<img>, поэтому узел назван слотом.
Слова: Адрес(ТекущаяКартинка), Состояние(Текст), Принимает(Типы), КнопкаЗагрузки(Подпись),
Сброс(Адрес или хвост) (пункт ⚙ «Вернуть стандартную»), Открыто (окно подтверждения
сброса напечатано открытым — так сервер спрашивает подтверждение без скрипта), Форма,
Якорь, Токен, Ошибка(Текст) (отказ сервера под строкой состояния — тип, размер, пустая
форма; ответ 4xx тихой отправки кладёт его на место слота).

Параметры:
 Имя     - Строка - имя файлового поля: под ним сервер получит файл.
 Подпись - Строка - что это за картинка («Логотип»).

Возвращаемое значение:
 ЭлементЭкрана

ЭлементЭкрана.Состояние()src/Классы/холст/ЭлементЭкрана.os

Строка состояния слота картинки — что стоит сейчас: имя файла, размер, дата. Пусто —
из словаря: «стандартная» у слота без сброса, «своя» у слота со сбросом.

Параметры:
 Текст - Строка - состояние (экранируется при печати).

Возвращаемое значение:
 ЭлементЭкрана - этот же элемент.

ЭлементЭкрана.Принимает()src/Классы/холст/ЭлементЭкрана.os

Какие файлы слот картинки предлагает выбрать — атрибут accept файлового поля
(«image/png,image/svg+xml»); по умолчанию «image/*». Это подсказка окну выбора файла,
а не проверка: тип и размер присланного сверяет сервер.

Параметры:
 Типы - Строка - типы через запятую: MIME-типы или расширения (.ico).

Возвращаемое значение:
 ЭлементЭкрана - этот же элемент.

ЭлементЭкрана.КнопкаЗагрузки()src/Классы/холст/ЭлементЭкрана.os

Подпись кнопки выбора файла у слота картинки. По умолчанию из словаря: «Заменить», когда
картинка стоит, «Загрузить», когда слот пуст.

Параметры:
 Подпись_ - Строка - подпись кнопки.

Возвращаемое значение:
 ЭлементЭкрана - этот же элемент.

ЭлементЭкрана.Сброс()src/Классы/холст/ЭлементЭкрана.os

Сброс слота картинки к стандартной: в меню ⚙ слота встаёт пункт «Вернуть стандартную»,
и его форма уходит на этот адрес только после подтверждения. Адрес — как у слова Форма:
хвост к адресу контекста или путь от корня. Без скрипта форма уходит без поля
подтверждения (Подтверждение.ИмяПоляПодтверждения), и сервер отвечает страницей со
слотом, у которого окно сброса напечатано открытым (слово Открыто).

Параметры:
 Адрес_ - Строка - (необязательный) хвост адреса или путь от корня; пусто — адрес контекста.

Возвращаемое значение:
 ЭлементЭкрана - этот же элемент.

СлотКартинки.Отрисовать()src/Классы/холст/СлотКартинки.os

Печатает слот картинки.

Параметры:
 Модель - Структура - Ид (обязателен: от него идентификаторы подписи, состояния, окна
          сброса), Имя (обязательно: имя файлового поля), Подпись (обязательна: что это
          за картинка — «Логотип»), Адрес (адрес текущей картинки; пусто — пустая
          шахматка), Состояние (строка под подписью: имя файла, размер, дата; пусто — из
          словаря: «imgslot.default» без сброса, «imgslot.custom» со сбросом), Принимает
          (атрибут accept, по умолчанию «image/*»), КнопкаЗагрузки (подпись кнопки; пусто —
          «imgslot.replace» при картинке, «imgslot.upload» без неё), Загрузка (Структура:
          Действие — адрес формы загрузки, ПолеТокена — ДОВЕРЕННЫЙ HTML скрытого поля
          токена), Сброс (та же Структура для формы сброса и Открыто — окно подтверждения
          напечатано открытым, АдресЗакрытия — адрес страницы без окна: крестик и «Отмена»
          ссылкой туда, дев-сборка 10; Неопределено — сбрасывать нечего, меню ⚙ нет), КлассОкна
          (классы окна подтверждения; их знает холст), Ошибка (отказ сервера словами —
          тип, размер, пустая форма: печатается под строкой состояния цветом ошибки,
          файловое поле ссылается на него aria-describedby и помечено aria-invalid; пусто —
          отказа нет, разметка прежняя).

Возвращаемое значение:
 Строка - HTML <div class="imgslot">.