Как создать плагин «Действие в галерее»
Слот attachment.viewer.action позволяет встроить плагин в галерею просмотра вложений. Плагин в этом слоте получает бинарные данные вложения и его метаданные. Он показывает свой UI, например редактор изображений, и позволяет вернуть файлы в Трекер.
1. Создание проекта через CLI
Чтобы создать новый плагин для этого слота, используйте команду создания приложения и в диалоге выберите нужный шаблон:
weavix create
В ответ на вопрос Select a template: выберите attachment.viewer.action.
Скорее всего, вам понадобится разрешение tracker:attachments:write.
Остальные шаги заполните по запросу. В результате будет сгенерирован проект с уже настроенным манифестом под слот attachment.viewer.action и заготовкой кода.
2. Что такое слот attachment.viewer.action
Слот attachment.viewer.action — это точка интеграции в галерее просмотра вложений в Трекере. Плагин в этом слоте:
- добавляет новое действие в виде кнопки в галерее со всеми доступными плагинами;
- при выборе плагина он открывается в модальном окне;
- получает от Трекера содержимое файла в формате blob и метаданные вложения через
slotContext.
При закрытии плагина можно передать payload с массивом вложений. Трекер обработает его и добавит каждый файл к текущей сущности — для каждого вложения из массива будет вызвана соответствующая функция.
Например, если плагин открыт из описания тикета и внутри вызван метод close с payload, Трекер добавит переданные файлы к этому тикету.
3. Манифест плагина
В манифесте нужно объявить слот attachment.viewer.action.
Пример структуры manifest.json:
{
"$schema": "./manifest.schema.json",
"slug": "attachment-viewer-action",
"version": "0.1.0",
"permissions": {
"data": ["tracker:attachments:read", "tracker:attachments:write"]
},
"slots": {
"tracker": {
"attachment.viewer.action": [
{
"entrypoint": "index.html",
"title": {
"ru": "Название плагина",
"en": "Plugin name"
},
"description": {
"ru": "Описание плагина",
"en": "Description of the plugin"
}
}
]
}
}
}
Важно
- Значение
titleотображается как подпись кнопки действия в галерее — выбирайте короткое и понятное название. - entrypoint — точка входа плагина, обычно
index.html.
4. Контекст слота
Плагин получает данные вложения через slotContext. Тип контекста:
export type AttachmentViewerActionSlotContext = {
attachmentBlob: Blob;
meta: {
id: string;
url: string;
date: string;
size: number;
mimetype: string;
};
};
Поля:
| Поле | Тип | Описание |
|---|---|---|
attachmentBlob |
Blob |
Бинарное содержимое файла |
meta.id |
string |
Идентификатор вложения |
meta.url |
string |
URL файла |
meta.date |
string |
Дата загрузки |
meta.size |
number |
Размер файла в байтах |
meta.mimetype |
string |
MIME-тип файла, например image/png |
Получить контекст в коде:
const { slotContext } = useTrackerPluginContext();
const { attachmentBlob, meta } = slotContext;
5. Возврат данных в Трекер
Чтобы вернуть результат работы плагина, нужно:
- Загрузить измененный файл в Трекер через
trackerApi. - Закрыть плагин через
hostApi.close, передав созданные вложения.
Трекер обработает payload и добавит каждый файл из массива attachments к текущей сущности. Например, если плагин открыт из описания тикета, файлы будут прикреплены к этому тикету.
Замена оригинального вложения
Если передать флаг replace: true в payload, оригинальное вложение будет заменено на новое вместо добавления дополнительного файла.
Пример:
hostApi.close({ attachments: [response.data], replace: true });
5.1. Хук useAttachmentSave
Удобно вынести логику сохранения в отдельный хук:
import { hostApi, trackerApi } from "@weavix/tracker-plugin-sdk-react";
import { useState, useCallback } from "react";
type UseAttachmentSaveReturn = {
save: (blob: Blob, filename: string) => Promise<void>;
loading: boolean;
error: Error | null;
success: boolean;
reset: () => void;
};
export function useAttachmentSave(): UseAttachmentSaveReturn {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [success, setSuccess] = useState(false);
const save = useCallback(async (blob: Blob, filename: string) => {
try {
setLoading(true);
setError(null);
setSuccess(false);
const file = new File([blob], filename, {
type: blob.type || "image/png",
});
const response = await trackerApi.v3.post["/attachments"]({
bodyParams: { filename },
file,
});
hostApi.close({ attachments: [response.data], replace: true });
setSuccess(true);
} catch (e) {
setError(e as Error);
} finally {
setLoading(false);
}
}, []);
const reset = useCallback(() => {
setLoading(false);
setError(null);
setSuccess(false);
}, []);
return { save, loading, error, success, reset };
}
Здесь:
trackerApi.v3.post['/attachments']— загружает файл в Трекер и возвращает данные созданного вложения.hostApi.close({ attachments: [...] })— закрывает модальное окно плагина и передает Трекеру массив вложений для прикрепления к сущности.
6. Минимальная структура приложения
- Точка входа, например
main.tsx: рендер в корень DOM и обертка в TrackerPluginProvider. - Корневой компонент, например
App.tsx:- использует useTrackerPluginContext и получает
theme,slotContext; - читает
attachmentBlobиmetaизslotContext; - при необходимости использует
useAttachmentSaveдля загрузки файла и закрытия плагина черезhostApi.close; - рендерит UI (редактор, просмотрщик и т.д.).
- использует useTrackerPluginContext и получает
Обертка провайдером обязательна, иначе плагин не сможет получить контекст и взаимодействовать с Трекером:
import { TrackerPluginProvider } from "@weavix/tracker-plugin-sdk-react";
root.render(
<TrackerPluginProvider>
<App />
</TrackerPluginProvider>,
);
7. Не забывайте про отладку
8. Чеклист перед публикацией
- В манифесте указан слот attachment.viewer.action с понятным
title. - В корневом компоненте читается
slotContext— доступныattachmentBlobиmeta. - Если плагин возвращает измененный файл — используется
trackerApiдля загрузки иhostApi.closeдля передачи результата Трекеру.