Skip to content

Writing your own Widget

Ari van Houten edited this page Oct 1, 2022 · 3 revisions

This guide explains how you can write your own widget. We will be writing a simple counter widget that counts up whenever you press on it. This will form the basics of writing widgets. It's always encouraged to look at other widgets as well.

Prequisites

Fork or Install the current dev branch of myanimetab, install the dependencies and start your server:

git clone -b dev https://github.com/aridevelopment-de/myanimetab.git myanimetab-counter-widget
cd myanimetab-counter-widget
npm install

Folder structure

MyAnimeTab consists of a rather consistent folder structure. You can find all of the code written in the src/ folder:

src
├───components
│   ├───settings
│   └───widgets
└───utils

I think it should be very obvious that widgets are located in the widgets folder. Each widget owns a seperate folder. For our counter, we will create a folder called counter/. Then we create our entry point file. The naming convention follows a simple pattern: WidgetComponent<Name>.tsx e.g. WidgetComponentCounter.tsx. We will create this file as well as a counter.module.css file where all of our styles lies. The folder structure now looks like this:

widgets
├───counter
│   ├───counter.module.css
│   └───WidgetComponentCounter.tsx
└───...

Registering meta data

We prefer React functional components with hooks. A general template for the component can look like this:

import { KnownComponent } from "../../../utils/registry/types";

const Counter = (props: { blur: boolean; id: string }) => {
    return (
        <div>Hallo Welt!</div>
    )
}

export default {
	type: "counter",
	element: Counter,
	metadata: {
		name: "Counter",
		author: "Aridevelopment.de",
		defaultComponent: false,
		removeableComponent: true,
		installableComponent: true,
	},
	headerSettings: {
		name: "Counter",
		type: "counter",
		option: {
			type: "toggle",
			default: true,
		},
	},
	contentSettings: [],
} as KnownComponent;

You may notice that we do not actually define a default export for the Counter element but instead a so called KnownComponent object. This defines a prefix for the id being stored and the structure of the entry for the settings page. For our counter we'd like to have the number being displayed in the settings and have it editable. For that, we will create an entry in contentSettings:

export default {
	type: "counter",
	element: Counter,
	metadata: {...},
	headerSettings: {...},
	contentSettings: [
        {
		name: "Value",
	        key: "value",
	        type: "input",
	        tooltip: "The current counter value",
	},
    ],
} as unknown as KnownComponent;

Now, for the final step, we have to register the component somewhere so that myanimetabs knows that there is a component. Our current solution consists of a sole widgets.json file in the src/ folder. Just add an entry like the following:

{
    "name": "counter",
    "entryPoint": "counter/WidgetComponentCounter"
}

Now you can start the development server via npm start. A page will open at localhost:3000. Open the settings page, click on the plus and then on Counter. Now, a little text displaying "Hello world" should plop up at the left top corner

grafik

and if you take a look at the settings page, you actually see an entry for the counter object:

grafik

Great, you have successfully registered an example component! Now, lets move on to the actual logic of our component.

The component's logic

(More explanation needed)

Just use react as you would always use it:

import styles from './counter.module.css'

const Counter = (props: { blur: boolean; id: string }) => {
    const [count, setCount] = useState(0);

    return (
        <div className={styles.container}>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>+</button>
        </div>
    )
}

and to have a little styling:

.container {
    position: absolute;
    top: 10px;
    left: 10px;
    padding: 10px;
    color: var(--mantine-color-dark-0);
    background-color: rgba(0, 0, 0, 0.5);
    border-radius: 6px;
}

.container p {
    margin: 0;
}

Storing and retrieving our value

MyAnimeTab does provide some functions to interact with the indexeddb. One of those is the useSetting hook. You provide it a widget id and a field and it will update automatically whenver that value gets changed. For example:

import { useSetting } from "../../../utils/eventhooks";

const Counter = (props: { blur: boolean; id: string }) => {
    const [count, setCount] = useSetting(props.id, "value");

    return (
        <div className={styles.container}>
            <p>Count: {count}</p>
            <button onClick={() => setCount(parseInt(count) + 1)}>+</button>
        </div>
    )
}

MyAnimeTab still lacks a bit of proper functionality and thus the initial value has to be inserted into the settings page manually. In future updates, MyAnimeTab will support default values in inputs as well. Also, the count is currently stored as a string because we defined a normal input and not a NumberInput.