Как создать плагин «Действие в галерее»

Слот 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. Возврат данных в Трекер

Чтобы вернуть результат работы плагина, нужно:

  1. Загрузить измененный файл в Трекер через trackerApi.
  2. Закрыть плагин через 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. Минимальная структура приложения

  1. Точка входа, например main.tsx: рендер в корень DOM и обертка в TrackerPluginProvider.
  2. Корневой компонент, например App.tsx:
    • использует useTrackerPluginContext и получает theme, slotContext;
    • читает attachmentBlob и meta из slotContext;
    • при необходимости использует useAttachmentSave для загрузки файла и закрытия плагина через hostApi.close;
    • рендерит UI (редактор, просмотрщик и т.д.).

Обертка провайдером обязательна, иначе плагин не сможет получить контекст и взаимодействовать с Трекером:

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 для передачи результата Трекеру.