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

Добавить кнопку и собственное окно

Добавить полезное окно с действием, выбрав правильное поведение закрытия.

Сделаем небольшую панель: пользователь задаёт максимальное число строк, нажимает «Обновить список» и видит имена выделенных объектов. Панель не изменяет проект. Она показывает, как хранить состояние между открытиями и освобождать обработчики при закрытии.

Нужно уметь установить минимальный плагин. Пример рассчитан на текущий Release BDEngine и использует editorAPI и стандартный DOM.

Задача интерфейса

Скачать полный плагин window.js. Установите файл и перезапустите редактор. В меню дока появится «Панель выделения».

Параметр «Максимум строк» принимает целое число от 1 до 50. Это ограничение примера, оно не ограничивает количество объектов в проекте. При пустом выделении панель покажет ноль объектов. Имена выводятся как текст: название объекта не выполняется как HTML.

Список обновляется при открытии окна и по кнопке. Изменение выделения при открытой панели само по себе не запускает обновление. Значение параметра сохраняется в переменной плагина до перезагрузки редактора, а не записывается в файл проекта.

Где разместить кнопку

Для панели, которую открывают по необходимости, подходит меню дока:

editorAPI.addButtonDockMenu(
'icon-list',
'Панель выделения',
openPanel
);

openPanel - функция плагина. Метод возвращает DOM-элемент ссылки; если потребуется убрать именно эту кнопку, сохраните результат и вызовите у него remove(). Удаление кнопки не закрывает уже созданное окно.

Для часто используемого действия есть addButtonTool(icon, handler, right, sound). Он размещает кнопку в полосе инструментов. Пункты импорта и экспорта добавляются отдельными методами. Параметры и возвращаемые значения: выбрать кнопку.

Кнопка регистрируется один раз после bde:started. Обработчик события ставится при выполнении файла плагина:

window.addEventListener('bde:started', () => {
// Создание кнопки и начального состояния плагина.
}, {once: true});

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

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

Нашей панели подходит обычное окно: пользователь может менять выделение и возвращаться к списку. createWindow сразу создаёт и открывает окно:

const panel = editorAPI.createWindow(
'Панель выделения',
'bde.docs.window.panel',
'icon-list',
460, 340,
300, 240,
cleanup
);

После иконки передаются желаемые ширина и высота, минимальные ширина и высота, затем обработчик закрытия крестиком. Размеры задаются в пикселях. Используйте собственный постоянный name_id: редактор связывает с ним сохранённую геометрию окна.

В panel.container находится DOM-контейнер содержимого. focus() выводит уже открытое окно на передний план и восстанавливает его, если оно было свёрнуто.

createModalWindow(title) подходит для временного выбора, который перекрывает работу с редактором. Модальное окно создаётся скрытым: его открывают через showModal(), закрывают через hideModal(), а содержимое добавляют в container. Скрытие сохраняет DOM и обработчики. Если выберете такой вариант, создайте модальное окно один раз и переиспользуйте его, вместо создания нового при каждом нажатии.

Содержимое и обработчики

Панель использует обычные элементы form, input, button и ul. Ключевая часть обновления списка:

const selected = editorAPI.getSelectedObjects();
list.replaceChildren();
for (const object of selected.slice(0, maxRows)) {
const row = document.createElement('li');
row.textContent = object.name || 'Объект без имени';
list.append(row);
}

getSelectedObjects() возвращает текущие объекты сцены, а не копии их данных. Здесь мы только читаем name. Перед вызовом проверяем параметр через Number.isInteger и границы диапазона; одного input.min недостаточно для проверки значений в коде.

При отправке формы вызываем event.preventDefault(), чтобы браузер не перезагружал страницу. События keydown и keyup внутри формы останавливаем через stopPropagation(), чтобы ввод в поле не передавался обработчикам клавиатуры редактора.

Обработчики формы привязаны с общим AbortController:

const events = new AbortController();
form.addEventListener('submit', handler, {signal: events.signal});
// При закрытии:
events.abort();

Не вставляйте введённые пользователем значения в innerHTML. Для текста используйте textContent, для значения поля - value.

Повторное открытие и очистка

Плагин хранит одну ссылку на окно. Пока она существует, повторное нажатие вызывает focus(). При закрытии обработчики отключаются, а ссылка сбрасывается.

Есть существенное различие между двумя способами закрытия обычного окна:

  • Крестик окна вызывает переданный closeFunc, затем close().
  • Прямой вызов panel.close() сам по себе не вызывает closeFunc.

Поэтому своя кнопка закрытия выполняет оба действия:

cleanup();
current.close();

close() удаляет DOM окна после анимации. Закрытое окно повторно не открывают: при следующем нажатии создаётся новое. Параметр maxRows остаётся снаружи окна, поэтому сохраняется между открытиями.

Если в инструменте появятся setInterval, подписки на window или другие внешние ресурсы, их тоже нужно освобождать в собственном cleanup. Сам факт удаления DOM этого не делает. closeFunc не является обработчиком выгрузки всего плагина. Подробнее: проверить жизненный цикл.

Проверить действие и состояние

  1. Откройте панель без выделения. Должны появиться ноль объектов и пустой список.
  2. Выделите несколько объектов и нажмите «Обновить список». Проверьте имена и счётчик.
  3. Укажите максимум 1. В списке останется одна строка, общий счётчик выделения не изменится.
  4. Попробуйте пустое поле, 0, 51 и дробное число. Некорректное значение не должно примениться.
  5. Не закрывая окно, нажмите его пункт в доке ещё раз. Должно активироваться то же окно.
  6. Закройте его крестиком, затем откройте снова. Повторите со своей кнопкой «Закрыть». В обоих случаях значение параметра должно сохраниться, а одно нажатие формы - дать одно обновление.

Это проверки в самом редакторе. Сверка кода и проверка JavaScript не заменяют проверку фокуса, сворачивания и размеров окна на нужном устройстве.