🧩
Seanime Extensions
🧩
Seanime Extensions
  • Seanime
    • Getting started
    • Core APIs
    • Changelog
  • Content Providers
    • Write, test, share
    • Anime torrent provider
    • Manga provider
    • Online streaming provider
  • Plugins
    • Introduction
    • Write, test, share
    • APIs
      • Helpers
      • Store
      • Storage
      • Database
      • AniList
      • System
        • Permissions
        • OS
        • Filepath
        • Commands
        • Buffers, I/O
        • MIME
    • UI
      • Basics
      • User Interface
        • Tray
        • Toast
        • Screen
        • Command Palette
        • Action
        • DOM
      • Anime/Library
        • Anime
        • Playback
        • Continuity
        • Auto Downloader
        • Auto Scanner
        • Filler Manager
        • External Player Link
      • Downloading
        • Downloader
        • Torrent Client
      • Other
        • Manga
        • Discord
        • MPV
    • Hooks
    • Example
  • Frequently asked
    • Feature requests
Powered by GitBook
On this page
  • Create a tray icon
  • Add a badge
  • Rendering content
  • Tray events
  • Event handlers
  • Layout components
  • Fields/Forms
  • Select, RadioGroup
  • Checkbox, Switch
  1. Plugins
  2. UI
  3. User Interface

Tray

PreviousUser InterfaceNextToast

Last updated 21 days ago

Create a tray icon

You can only create one tray icon in your plugin

const tray = ctx.newTray({
    tooltipText: "My plugin",
    iconUrl: "https://seanime.rahim.app/logo_2.png",
    withContent: true,
})

Add a badge

tray.updateBadge({ number: 1, intent: "alert" })
// Remove the badge
tray.updateBadge({ number: 0 })
  • intent: "alert" | "info" | "warning" | "success"

Rendering content

withContent should be set to 'true'.

The state is a mechanism to keep track of variable data over time, enabling components to re-render automatically when the data changes.

The render() function is used to define how content should be presented in the tray popover. It takes a callback function that returns a tree of components.

Components, like tray.text and tray.button, are reusable building blocks of the UI, each responsible for rendering a piece of the interface according to the current state.

The tray will be re-rendered anytime there is a state change even if the state isn't in the render function.

const count = ctx.state(0);

ctx.setInterval(() => {
    count.set(c => c + 1);
}, 1000);

tray.render(() => {
    return tray.stack({
        items: [
            tray.text(`Count: ${count.get()}`),
            // Show the button when the count reaches 5
            count.get() >= 5
                ? tray.button("Click me", { onClick: "button-clicked" })
                : trat.text("Nothing here")
        ],
    })
})

Tray events

tray.onOpen(() => {
    // User opened the tray content
})

tray.onClose(() => {
    // User closed the tray content
})

tray.onClick(() => {
    // User clicked the tray icon
})

// Open the tray
// This will not work on the first page load or if the plugin is not pinned
tray.open()

// Close the tray
tray.close()

// Force the tray content to update
// Not useful is most cases because the tray updates when states change
tray.update()

Event handlers

You can register functions to specific event triggers like onClick using ctx.registerEventHandler() in order to define custom behaviors based on user action.

//..

ctx.registerEventHandler("reset-counter", () => {
    count.set(0)
})

tray.render(() => {
    return tray.stack({
        items: [
            tray.text(`Count: ${count.get()}`),
            tray.button("Reset counter", { onClick: "reset-counter" }),
        ],
    })
})

Tips

You can register inline event handlers. Make sure the first argument is unique to that element.

tray.stack(allItems.map((item) => {
    return tray.flex([
        tray.text(key),
        tray.button({ 
            label: "Open", 
            size: "sm", 
            // It takes a unique ID key as first argument!
            onClick: ctx.eventHandler(item.id, () => {
                // Do something...
            }),
            intent: "gray-subtle"
         }),
    ], { gap: 1, style: { alignItems: "center" } })
}))

Layout components

tray.div([], { style: {} })
tray.stack([], { style: {} })
tray.flex([], { style: {} })

Fields/Forms

Field components:

  • input

  • button

  • select

  • radioGroup

  • checkbox

  • switch

Use ctx.fieldRef to get and set a field's value synchronously.

const textInputRef = ctx.fieldRef<string>("Default value")

// When the form is submitted
ctx.registerEventHandler("submit-form", () => {
    // We can get the value of the text input
    console.log(textInputRef.current)
    
    // We can change the value of the text input
    textInputRef.setValue("")
})

tray.render(() => tray.stack([
    text.input("A text field", { fieldRef: textInputRef }),
    tray.button("Submit", { onClick: "submit-form" }),
]))

Select, RadioGroup

You can create forms easily with ctx.fieldRef and the available field components.

const selectRef = ctx.fieldRef<string>()
const radioGroupRef = ctx.fieldRef<string>()

tray.render(() => tray.stack([
    tray.select("Label", { 
        placeholder: "Select...",
        options: [
            { label: "One Piece", value: "21" },
            { label: "Sakamoto Days", value: "177709" },
        ],
        fieldRef: selectRef,
    }),
    tray.radioGroup("Label", { 
        options: [
            { label: "One Piece", value: "21" },
            { label: "Sakamoto Days", value: "177709" },
        ],
        fieldRef: radioGroupRef,
    }),
]))

Checkbox, Switch

const checkboxRef = ctx.fieldRef<boolean>()
const switchRef = ctx.fieldRef<boolean>()

tray.render(() => tray.stack([
    tray.checkbox("Do something", { 
        fieldRef: checkboxRef
    }),
    tray.switch("Do something else", { 
        fieldRef: switchRef
    }),
]))

Example