UI панели
Что даёт вам эта страница: административная сторона вашего аддона — раздел настроек на странице его деталей, полноценная страница панели с собственной ссылкой в боковой панели и toast-ы для подтверждения действий. К концу администраторы могут управлять Shoutbox из панели.
Всё здесь идёт в ветку if (pano.isPanel) функции onLoad() в main.js. Если вы ещё не настроили main.js, сначала прочитайте Разработку фронтенда. Сетевые вызовы используют ApiUtil — его правила и обработка ошибок описаны в Вызове вашего API.
Раздел настроек на странице деталей вашего аддона
Когда администратор открывает ваш аддон в Панель → Аддоны, у его страницы деталей есть хук под названием panel:plugin-detail:content:<pluginId>. Регистрация компонента там — самый дешёвый способ дать вашему аддону экран настроек — большинство встроенных аддонов делают именно это. Поместите это в ветку if (pano.isPanel):
pano.ui.hook.register({
name: `panel:plugin-detail:content:${pluginId}`,
component: viewComponent(() => import('./panel/ShoutboxSettings.svelte')),
});
pano.ui.addon.onLoad(async (data, event) => {
if (data.addon.id !== pluginId) return;
const res = await ApiUtil.get({ path: '/api/panel/shoutbox/config', request: event });
if (!res.error) data.addon.config = res;
});pano.ui.addon.onLoad(callback) регистрирует функцию, которую Pano выполняет каждый раз, когда открывается страница деталей любого аддона. Ваш колбэк получает (data, event):
data.addonописывает аддон, чья страница открывается. Его известное поле —data.addon.id(ID плагина). Вы также можете прикрепить к нему свои собственные свойства — вродеdata.addon.configздесь — и они приходят в propaddonкомпонентов на этой странице. (См. записьaddonв Справочнике API фронтенда для полной формы.)event— это запрос страницы, ровно какeventвload().
Поскольку он срабатывает для каждого аддона, первая строка проверяет data.addon.id !== pluginId и выходит, если страница не ваша, до получения данных.
Здесь вам можно расширять data
load() никогда не должен мутировать объекты, которые ему передали. Колбэки onLoad — намеренное исключение: объект data предназначен для расширения, и прикрепление вашей конфигурации к data.addon.config — поддерживаемый способ передать её в ваши компоненты.
Компоненты, зарегистрированные на этом хуке, получают объект addon страницы как prop, поэтому ShoutboxSettings.svelte может прочитать конфигурацию, которую вы прикрепили:
<!-- src/panel/ShoutboxSettings.svelte -->
<script>
export let addon;
let config = addon?.config ?? { enabled: true, maxShouts: 5 };
</script>Проверка
Откройте Панель → Аддоны → Shoutbox. Ваш компонент настроек должен отрисоваться на странице деталей, с config, заполненным из API (или запасным { enabled: true, maxShouts: 5 }, если запрос провалился).
Полноценная страница с собственной ссылкой навигации
Раздел настроек живёт внутри страницы деталей аддона. Когда вашему аддону нужна собственная страница — экран управления на /shoutbox — зарегистрируйте её как страницу и добавьте ссылку в боковую панель.
Строка permission ниже следует фиксированному паттерну: pano.plugin.<pluginId>.<имя класса права доступа в dot-case>. Она должна совпадать с вашим классом права доступа Kotlin в точности — здесь ManageShoutboxPermission становится manage.shoutbox, давая pano.plugin.pano-plugin-shoutbox.manage.shoutbox.
pano.ui.page.register({
path: '/shoutbox',
component: viewComponent(() => import('./panel/ShoutboxPage.svelte')),
permission: 'pano.plugin.pano-plugin-shoutbox.manage.shoutbox',
});
pano.ui.nav.site.editNavLinks(async (links) => {
if (!links.some((l) => l.href === '/shoutbox')) {
const i = links.findIndex((l) => l.href === '/posts');
const link = {
href: '/shoutbox',
icon: 'fas fa-bullhorn',
text: `plugins.${pluginId}.nav.shoutbox`,
startsWith: true,
permission: 'pano.plugin.pano-plugin-shoutbox.manage.shoutbox',
};
i >= 0 ? links.splice(i + 1, 0, link) : links.push(link);
}
return links;
});Несколько вещей, которые стоит объяснить:
nav.site— это главная боковая панель панели. Существуют другие области навигации, перечисленные в Справочнике API фронтенда, — вот почему в пространстве имён есть дополнительное словоsite; это не опечатка.text— это ключ перевода, а не буквальный ярлык. Пока вы не добавите этот ключ в Локализации, боковая панель показывает сырую строку ключа (plugins.pano-plugin-shoutbox.nav.shoutbox). На этом этапе так и должно быть.icon— это класс Font Awesome. Панель уже поставляет Font Awesome; просмотрите доступные имена на fontawesome.com.- Вставляйте относительно якоря. Найдите существующую ссылку (здесь
/posts) и вставьте свою рядом с ней, откатываясь кpush, если якоря нет, — чтобы ваша ссылка приземлилась в разумном месте, а не всегда в конце. - Защищайтесь от дубликатов.
editNavLinksперезапускается при каждой загрузке страницы внутри долгоживущего сервера, поэтому он может выполниться много раз — проверяйтеlinks.some((l) => l.href === '/shoutbox')перед добавлением, иначе вы наложите дублирующиеся ссылки. Всегда возвращайте массив.
Держите строку права доступа синхронизированной
Строка permission выше — это набранная вручную копия узла, который выводит ваш класс Kotlin ManageShoutboxPermission (pano.plugin.pano-plugin-shoutbox.manage.shoutbox). Общей константы нет — если вы переименуете класс Kotlin, выведенный узел изменится, и этот UI-затвор молча перестанет совпадать. Меняйте оба вместе. См. правило прав доступа в Разработке бэкенда.
Если permission не выполнено, страница возвращает 404, а ссылка навигации скрыта. Для страниц есть ещё две опции: systemLayout переиспользует встроенный макет панели (аддон Comments использует systemLayout: 'PostsLayout', чтобы его страница сидела под разделом Posts), а resetLayout убирает окружающую рамку панели (боковую панель, шапку), чтобы ваша страница отрисовывалась во всю ширину. Полный список имён макетов — в Справочнике API фронтенда.
Проверка
Перезагрузите панель. Иконка рупора должна появиться в боковой панели прямо под Posts, помеченная сырым ключом plugins.pano-plugin-shoutbox.nav.shoutbox (ярлык превращается в настоящий текст, как только вы добавите этот ключ локали). Нажмите на неё, чтобы открыть свою страницу на /shoutbox.
Показ всплывающих уведомлений (toast)
Чтобы подтвердить действие администратору, покажите toast (небольшое всплывающее сообщение). Импортируйте showToast из @panomc/sdk/toasts — и обратите внимание, что он использует помощник для перевода $_ из раздела Перевод текста:
<script>
import { showToast } from '@panomc/sdk/toasts';
import { _ } from '../main.js';
async function save(config) {
const res = await ApiUtil.put({ path: '/api/panel/shoutbox/config', body: config });
showToast(res.error ? $_('toasts.save-error') : $_('toasts.save-success'));
}
</script>Проверка
Свяжите этот save с кнопкой и нажмите её. Вы должны увидеть, как выезжает toast — сообщение об успехе, когда запрос работает, сообщение об ошибке, когда он проваливается.
Что дальше
Административная сторона готова: раздел настроек, полноценная страница панели со ссылкой навигации и toast-ы.
- Локализация — добавьте строки
plugins.<pluginId>.<key>, стоящие за вашим ярлыком навигации и сообщениями toast, чтобы они показывали настоящий текст. - Справочник API фронтенда — каждое имя хука, область навигации, макет страницы и событие жизненного цикла в одном месте.
- UI темы — обращённая к посетителю сторона, включая
ApiUtilцеликом. - Разработка бэкенда — эндпоинты Kotlin и право доступа, на котором эта страница строит защиту.
- Сборка и публикация — превратите готовый аддон в релизный jar. Релизная сборка обязана включать UI, поэтому никогда не используйте для неё
-Pnoui. - Аддоны для образца — встроенные аддоны в организации PanoMC на GitHub — рабочий образец для каждого паттерна здесь: аддон Announcement (условный
invisible), аддоны FAQ и Pages (skipLoad+app.onLoad), аддон Comments (systemLayout) иpano-plugin-link-redirects(динамические страницы + очистка).