gristlabs_grist-core/app/common/CustomWidget.ts
Paul Fitzpatrick 07bb90b5a6
allow bundled widgets to be hidden from dropdown, and nested (#714)
This makes a few refinements to bundling widgets:

  * A widget with `published: false` is not shown in the
    custom widget dropdown in the UI. This is so widgets
    can be bundled with the app for "native" use (like the
    calendar widget) without immediately resulting in an
    extra listing in the UI. (There are improvements we'd
    like to make to the UI to better communicate widget
    provenance and quality eventually, which would be a
    helpful alternative to just a binary flag.)

  * A relative path to the custom widget manifest is
    respected. This will make the bundling process marginally
    neater.
2023-10-30 21:13:21 -04:00

95 lines
2.2 KiB
TypeScript

import sortBy = require('lodash/sortBy');
/**
* Custom widget manifest definition.
*/
export interface ICustomWidget {
/**
* Widget friendly name, used on the UI.
*/
name: string;
/**
* Widget unique id, probably in npm package format @gristlabs/custom-widget-name.
*
* There could be multiple versions of the same widget with the
* same id, e.g. a bundled version and an external version.
*/
widgetId: string;
/**
* Custom widget main page URL.
*/
url: string;
/**
* Optional desired access level.
*/
accessLevel?: AccessLevel;
/**
* If set, Grist will render the widget after `grist.ready()`.
*
* This is used to defer showing a widget on initial load until it has finished
* applying the Grist theme.
*/
renderAfterReady?: boolean;
/**
* If set to false, do not offer to user in UI.
*/
published?: boolean;
/**
* If the widget came from a plugin, we track that here.
*/
source?: {
pluginId: string;
name: string;
};
}
/**
* Widget access level.
*/
export enum AccessLevel {
/**
* Default, no access to Grist.
*/
none = "none",
/**
* Read only access to table the widget is based on.
*/
read_table = "read table",
/**
* Full access to document on user's behalf.
*/
full = "full",
}
export function isSatisfied(current: AccessLevel, minimum: AccessLevel) {
function ordered(level: AccessLevel) {
switch(level) {
case AccessLevel.none: return 0;
case AccessLevel.read_table: return 1;
case AccessLevel.full: return 2;
default: throw new Error(`Unrecognized access level ${level}`);
}
}
return ordered(current) >= ordered(minimum);
}
/**
* Find the best match for a widgetId/pluginId combination among the
* given widgets. An exact widgetId match is required. A pluginId match
* is preferred but not required.
*/
export function matchWidget(widgets: ICustomWidget[], options: {
widgetId: string,
pluginId?: string,
}): ICustomWidget|undefined {
const prefs = sortBy(widgets, (w) => {
return [w.widgetId !== options.widgetId,
(w.source?.pluginId||'') !== options.pluginId];
});
if (prefs.length === 0) { return; }
if (prefs[0].widgetId !== options.widgetId) { return; }
return prefs[0];
}