Skip to content

Extension Development

WXY edited this page Aug 30, 2021 · 13 revisions

Extensions adds functionality that enhances specific workflows i.e. stock photo management, ebook viewing. Where as the main program seeks to implement functionality common to all workflows, as well as a platform on which extensions may be built.

Extension Anatomy

An extension is comprised of an ES6 or UMD module and optional CSS stylesheets

  • All extensions are loaded via the inconel dynamic extension loading library
  • To affect changes in the UI, an extension may export its own ReactJS components
  • To store state that lives beyond that of UI components and communicate with other components, the extension is expected to create services
  • Asset loading is currently not supported, please do not attempt to make assumptions about placement for your files

Extending the User Interface

An extension may define 3 different types of ReactJS based UI components:

  • Menus - Examples of menus include the top level buttons for filtering, thumbnails, and ordering
  • Modes - Top level activities such as the gallery & stage are builtin modes
  • Extras - Specific to extensions, these are always visible overlays that are not required to conform any specific appearance requirements

Component definitions should be exported from your extension's module

export const menus = [
    MenuDefinition,
];

export const extras = [
    OverlayDefinition,
];

export const modes = [
    ActivityDefinition,
];

Note that the program exposes its React instance at the window level, so please ensure you do not try to bundle your own

All components may be stateful

  • However this state is lost when the component is detached from the DOM, which typically happens when a menu hides or the active mode changes
  • Less ephemeral state should be kept in services, which a component may declare its intention to access via their definition objects
    • These services are then bound by name to the component's props

Menus

Menus exists for one of two purposes:

  1. Displaying information about the current file or mode
  2. Editing user preferences
    • Menus are the only components exported by an extension that may change preferences
    • As a security measure, extensions may only change preferences prefixed by its own namespace

A Menu can be associated with multiple modes and are aware of the active mode

  • It is possible for a menu to exhibit Mode specific behavior
    • i.e. Batch tagging is only available in gallery mode

Example definition:

export const Definition = {
    id: "my-menu",
    icon: mdiAlphaMBox,
    label: "My Menu",
    services: ["browsing"],
    component: Component,
    selectPreferences: ({
        somePreference,
    }): PreferenceMappedProps => ({
        somePreference,
    }),
};

Modes

Commanding the majority of the user's attention, modes are top level activities

  • Only one mode is ever displayed at a time, granting it exclusive access to the entire client area
  • A mode must be defined with a unique path, which serves as its ID in navigation as well as allowing menus & extras to target it for composition
  • Navigating to a different mode can be performed by the onNavigate function passed to the mode's component via its props

Example definition:

export const Definition = {
    id: "my-activity",
    path: "/my-activity",
    services: ["tagging"],
    component: Component,
    selectPreferences: ({
        somePreference,
    }): PreferenceMappedProps => ({
        somePreference,
    }),
}

Managing Focus

WIP: Not yet available, This is still being designed

Extension components must observe the following guidelines on how to acquire and react to focus loss in order to ensure they behave intuitively when a user stops interacting with them.

Services

Services are the longest living components exported by an extension, matching that of the program itself.

  • Services may have dependencies against other services
    • Dependencies are considered construction preconditions, thus any cyclic dependency is considered a fatal runtime error
  • UI components declares their dependencies against services in their definitions
    • However it is completely normal for a service to be used solely by other services

Unlike components, service classes are directly exported from the application

export const services = [
    Service,
];

class Service {
    static readonly shortName = "service";
    static readonly dependencies = ["ipc", "reader"];

    constructor([ipc, reader]: [ipc.Service, io.Reader]) {
        ...
    }
}

See ServiceClass

The application offers several "builtin" services that allows your extension to extend the behaviors of the application

This section is WIP, expect surface level details only

  • Centralizes file ordering, filtering, and selection
  • Controls tag assignment, creation, modification, and deletion
  • Performs caching and debouncing to eliminate unnecessary disk usage
  • Enables your program to communicate with other programs
  • Adds support for multiplexed binary RPC via unix domain sockets as well as IO streams
  • Can execute external commands for operations that can't be supported in an extension
  • Being an "inter process communication" service, there is no way to establish a connection to another computer

Binary RPC

This program has aspirations to become a hybrid application. Thus resource intensive RPC frameworks such as JSON are out of the window for, what one consider obvious, performance and battery consumption reasons

  • Instead we use a very minimal, enveloped protocol
  • Every message contains at least two fields:
    • uint32 sequenceNum - The ID that would be used as a response
    • uint32 messageSize - This includes the bytes used by this envelope
  • Response messages (i.e. procedure call responses) shall be sent back with the same sequence number as the request that lead to the response
  • To avoid sequence number synchronization related race conditions, the least significant bit of the sequence number indicates which party initiated a particular interaction
    • If set (odd numbers) - the interaction is started by this program
  • For an example of using this protocol see fs-viewer-curator-extension
  • Exposes the platform specific path joining function
  • Permits implementation of line by line text file parsing via a reduction algorithm
  • Enables read access to
    • files' attributes
    • arbitrary POJO objects serialized to files
    • arbitrary text files
  • Permits the ability to set and remove attributes from files
  • Enables very efficient incremental writes to
    • arbitrary POJO objects serialized to files
    • arbitrary text files
  • An event emitter that triggers whenever application preferences changes

Good Practices

Bundling

To improve load speed and reduce bandwidth usage in distribution, it is recommended all extensions are bundled with a modern bundler such as Webpack or Rollup

CSS Namespacing

It is recommended that all styles declared by your extension be qualified by one "root" class bearing some abbreviated version of your extension's name. This minimizes the likelihood that styles between extensions will interact with each other

Preference IDs File

Dedicated constants files are considered anti-patterns in the main codebase, preference IDs in extensions is an exception as this reduces the labor of maintaining consistency between components where owner of the preference is not clear