Getting Started (Addons)

From MediaMonkey Wiki
Revision as of 22:51, 4 December 2020 by Drakinite (talk | contribs) (new page owo)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

{{#invoke:For|For}}

Introduction to Making Addons in MediaMonkey 5

MediaMonkey 5 is a fully HTML-based desktop application which uses Chromium for rendering. This means that the entire UI is driven by a platform-independent HTML/CSS/JS stack, which is fully accessible to developers and skinners. JS in cooperation with native code drives the non-visual aspects, also fully controllable by developers. This gives unprecedented customization options to create beautiful skins, new or enhanced functionality and great addons in general.

Scripting in MediaMonkey 5 differs greatly from MediaMonkey 4 and below, because it has been designed from the ground-up. It is not difficult to learn, especially if you are familiar with web programming. If you are coming from writing MediaMonkey 4 addons, you will get adjusted in no time at all.

Hello World example

Here is a basic Hello World example for a MediaMonkey 5 addon.

Inside a new folder, create a file named info.json with the essential information about your addon.

{
    "title": "My First Addon",
    "id": "myFirstAddon",
    "description": "Hello world!",
    "version": "1.0.0",
    "type": "general",
    "author": "Jane Doe"
}

Next, create a file named mainwindow_add.js. This code will get added to the end of mainwindow.js, and it will run when MediaMonkey starts.

// Execute when the window is ready
window.whenReady(() => {
    uitools.toastMessage.show('Hello world!', {
        disableUndo: true
    });
});

Then, select your two files and package them into a zip. Make sure that they are in the root of your zip archive and not inside a subfolder. Rename it to myFirstAddon.mmip.

Inside MediaMonkey5, go to Tools > Addons, click Add, and select your addon. After it installs and you reload the window, you will get a popup "toast" message on the bottom of the screen.

MMIP (MediaMonkey Installer Packages)

The center of all MediaMonkey add-ons is the MMIP. There is nothing special about the file format itself; it is just a ZIP archive with a different filename. You can use any standard method to zip the files, and then just rename it to a .mmip file.

Folder Structure

Metadata (info.json)

At the root of an MMIP, there must be a file named info.json. It includes all essential information about the addon. It cannot be in a subfolder.

Here is an example info.json:

{
    "title": "My Addon Name",
    "id": "myFirstAddon",
    "description": "This is my first addon!",
    "version": "1.0.0",
    "type": "general",
    "author": "Jane Doe",
    "icon": "icon.png"
}

Here are the possible attributes info.json can contain:

Attribute Required? Information
title yes This is the name of your addon that is visible to the user.
id yes This is the unique ID of your addon. It does not have to be identical to your title, but it is recommended that your addon's id be similar to its title for organizational purposes. The id can include spaces, but it is recommended to stick with an alphanumeric string.
description yes The description of your addon. Make sure your description is brief, yet conveys the meaning and usage of your addon to the user. You can create line breaks in the description with \n.
version yes The version number of your addon. It must be in the format %d.%d.%d, which is three period-separated numbers.
type no The category of your addon. The existing categories are: general, skin, layout, sync, metadata, and visualization; but you can create your own categories. If unspecified, the type defaults to general.
author no The name of the addon's author.
icon no The filename of your addon's icon image. The image file must be in the root of the MMIP.
config no The filename of your addon's configuration script. See Adding Configurable Settings for more info.

Code

All the HTML/CSS/JS code that handles MM functionality is stored in a tree structure. As a developer, you can add new files, replace existing, or even extend functionality of the existing files. This is achieved by replication of the folder structure in Addons. For example, if a file controls\checkbox.js is present in the Addon, it completely replaces the default MM functionality of a checkbox. Similarly, an _add suffix in a filename extends functionality of an existing file. E.g. dialogs\dlgAbout_add.js can contain code that adds new controls to the layout of the About dialog.

  • Root - Contains mainly mminit.js, which has the basic MM JS routines, several other utility .js files, maincontent.html which contains the basic definition of the main window. Also important is viewHandlers.js, which defines the tree structure of MM and behaviour of the views.
    • controls - Contains all the controls used in MM UI. This starts with very basic controls, like button.js or dropdown.js, and continues with more complex things like listview.js and goes all the way to the complex UI elements, like equalizer.js, player.js or autoPlaylistEditor.js.
    • dialogs - All the dialogs reside here, e.g. dlgConvertFormat.html + dlgConvertFormat.js
      • dlgOptions - Panels for the Tools > Options menu reside here.
    • helpers - Contains miscellaneous helper scripts that do not fit into the other categories. For example: butt services, tray icon menus, docking, etc.
    • layouts - Subfolders contain individual layouts, i.e. something that can replace/modify the files in the default folder structure in order to achieve completely different layout of MM (e.g. "Touch mode" layout). Unlike skins, layouts are supposed to mainly modify dimensions, positions and types of UI elements, not their color, etc. See Layouts for more information.
    • scripts - This contains all non-skin addons that are installed to MediaMonkey, including addons that are preinstalled. They are organized by id.
    • skin - Contains basic skin definitions, mostly a set of LESS files (an extension to CSS). See http://lesscss.org for more information.
      • icon - Contains all the icons used by MM. They are in SVG format in order to scale nicely to any display resolution. As anything else, they can be easily replaced by any Addon (skin or script).
    • skins - Subfolders contain individual skins, i.e. something that can replace/modify the files in the default folder structure in order to achieve completely different looks of MM. Unlike layouts, this is supposed to mainly modify colors, fonts, icons, etc. See Skinning for more information.

Versioning

The versioning of addons must be in the format of three period-separated numbers (for example 0.0.1, 1.2.34, etc.) We recommend using semantic versioning (see https://semver.org). MediaMonkey has a built-in updater for addons. When the user clicks "Find Updates" in the addons screen, it will check online if there are any updates for addons that are installed. If any are found, the user clicks the download button that appears, then it will download and install the updated addon.

Submitting an Addon

To submit an addon, you must first have an account on the MediaMonkey forum and be signed in.

  1. Go to https://www.mediamonkey.com/addons/ and click Submit Addon.
  2. Select the most appropriate sub-category under MediaMonkey 5 that describes your addon.
  3. Click Submit New Addon.
    1. Name: Make sure it is the same as your addon's title, so that it does not confuse users after installing.
    2. Description: This does not have to be the same as the description in info.json. You can be as descriptive as you like.
    3. Support Link, Author Link, and License Type are optional.
    4. Image is not required, but highly recommended. We recommend that it be a square image, and the same image as your addon's icon.
  4. Click next. On this page, you will add the first version of your addon.
    1. Either upload your MMIP file or specify an external download link.
    2. Compatibility: Specify the MediaMonkey versions on which you have confirmed your addon works.
    3. What's New is optional, but you can specify updates here in future versions.
  5. Click Save. Review your changes, make sure everything is accurate, then click Finish.
  6. Your addon will appear in red until it is approved by a moderator. When approved, it will appear on the main addons page.

To add a new version of your addon, simply click Add New Version and follow steps 4-5. Each additional version needs to be approved by a moderator.

Important Tips

  • Minimize the amount of computation your addon does on startup. Most scripts in MM run as soon as the window loads, so make sure your addon does not take a long time doing synchronous calculations that can cause the window to take longer to load. Ways to help avoid this:
    • Use window.whenReady() when possible. Using window.whenReady() will cause your callback to only fire when all scripts are loaded, the whole DOM is processed by our parser, and all controls are initialized.
    • Use asynchronous code when possible (with either callbacks, Promises, or async/await). If you perform heavy calculations that are synchronous, then it will halt the UI. See https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await for more information.
  • Put your _add scripts into an anonymous function. To prevent potential issues of variables with the same name being used across different scripts, we recommend putting most/all of your logic into an anonymous function. You can do it with arrow notation or function notation.
    • (() => { /* Do stuff */ })();
    • (function() { /* Do stuff */ })();
  • Enable Developer Mode. Under Help > About, you can enable Developer Mode. This will prevent crash logs from being automatically sent to MediaMonkey staff.
    • Additionally, developer mode can be enabled in the code via app.enabledDeveloperMode(true) during testing. But do not keep it in your published extension.

Storing Data

JSON

When you wish to save data such as user preferences, the most effective method is to save values in persistent.json. The way you do this is through the app.setValue and app.getValue methods. Both methods require two parameters.

If retrieving a value that is primitive, set the second parameter as undefined.

app.setValue('myExtension_foo', 'bar');
    
var foo = app.getValue('myExtension_foo', undefined);

When retrieving a value that is an object, you must provide the second parameter as an object. The function will take that object and populate each value if it exists, so you can easily manage default settings this way.

// Saving settings
app.setValue('myExtension_settings', {
    option1: 0,
    option2: 'electric boogaloo'
});
// Getting settings with hardcoded defaults
var settings = app.getValue('myExtension_settings', {
    option1: 0,
    option2: 'default'
})
// Passing an empty object works too
var settings = app.getValue('myExtension_settings', {});

This data is automatically saved in persistent.json, which is saved in the user's AppData folder or the Portable subfolder; for non-portable and portable installations, respectively. Persistent.json is only deleted if the user manually deletes it or removes a portable installation. Important: Make sure the keys that you use are unique. Include the addon ID in the key, as demonstrated above, to ensure this.

Database

If you need to manage more data, you can use the database. In MM5, you can access the database through the app.db object. For more information, see: https://www.mediamonkey.com/webhelp/MM5Preview/classes/DB.html.

To execute queries that do not need return values, use app.db.executeQueryAsync:

app.db.executeQueryAsync('CREATE TABLE IF NOT EXISTS myPlugin (_id INTEGER PRIMARY KEY, value TEXT UNIQUE NOT NULL)')
.then(() => {
	app.db.executeQueryAsync('INSERT INTO myPlugin [. . .]');
})
.catch(err => {
    console.error(err)
});

To execute queries that need return values, use app.db.getQueryResultAsync:
app.db.getQueryResultAsync('SELECT * FROM Albums')
.then(result => {
    // do stuff
})
.catch(err => console.error(err));

This method returns a QueryResults object, which is a linked list. See https://www.mediamonkey.com/webhelp/MM5Preview/classes/QueryResults.html.

Adding Configurable Settings to your Addon

There are two ways to add configurable options to an addon: via the "config" option in info.json, or modifying the options dialog.

Addon Config

todo

Options Dialog

When modifying the options dialog, you can either add to / modify an existing panel or create your own panel.

Adding to an existing panel

Adding to an existing panel can be done with _add JS files. For more context, take a look at dialogs/dlgOptions.js.

For example, if you wish to add to the General Options panel:

  1. Create dialogs/dlgOptions/pnl_General_add.js
  2. Override the optionPanels.pnl_General.load and optionPanels.pnl_General.save functions to add your own code
  3. Use the divFromSimpleMenu function to automatically create styled checkboxes/radio buttons

Use app.getValue and app.setValue to retrieve and save the user settings

(() => {
    var options = [
        {
            title: 'Option 1', // The label that appears on the checkbox/radio button
            radiogroup: 'myAddon_RadioOptions', // Self-explanatory
            execute: function() {state.RadioOptions = 'option1'} // This function runs whenever the element is clicked
        },
        {
            title: 'Option 2',
            radiogroup: 'myAddon_RadioOptions',
            execute: function() {state.RadioOptions = 'option2'}
        },
        {
            title: 'My Checkbox',
            checkable: true, // Turns it into a checkbox
            execute: function() {state.Checkbox = this.checked;}
        },
    ]
    var state;
    
    optionPanels.pnl_General.load = (function(sett, pnlDiv){
        var cached_function = optionPanels.pnl_General.load;
        return function(sett, pnlDiv) {
            // Execute original pnl_General.load function
            cached_function(sett, pnlDiv);
            // Load settings
            state = app.getValue('myAddon_settings', {
                RadioOptions: 'option1',
                Checkbox: false
            });
            // Update checkbox/radiobutton state from settings
            if (state.RadioOptions == 'option1') {options[0].checked = true; options[1].checked = false;} 
            else {options[0].checked = false; options[1].checked = true}
            if (state.Checkbox == true) options[2].checked = true;
            // Create an HTML menu from the options
            divFromSimpleMenu(pnlDiv, options);
        }
    })();
    
    optionPanels.pnl_General.save = (function(sett, pnlDiv){
        var cached_function = optionPanels.pnl_General.save;
        return function(sett, pnlDiv) {
            // Execute original pnl_General.save function
            cached_function(sett, pnlDiv);
            // Save settings
            app.setValue('myAddon_settings', state);
        }
    })();
})();

Adding to an existing panel

You can define your new panel inside dialogs/dlgOptions, and add it inside dialogs/dlgOptions_add.js.

todo