2023-10-27 19:34:42 +00:00
|
|
|
import sortBy = require('lodash/sortBy');
|
|
|
|
|
2021-11-26 10:43:55 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
2023-10-27 19:34:42 +00:00
|
|
|
*
|
|
|
|
* There could be multiple versions of the same widget with the
|
|
|
|
* same id, e.g. a bundled version and an external version.
|
2021-11-26 10:43:55 +00:00
|
|
|
*/
|
|
|
|
widgetId: string;
|
|
|
|
/**
|
|
|
|
* Custom widget main page URL.
|
|
|
|
*/
|
|
|
|
url: string;
|
|
|
|
/**
|
|
|
|
* Optional desired access level.
|
|
|
|
*/
|
|
|
|
accessLevel?: AccessLevel;
|
2023-09-19 18:44:22 +00:00
|
|
|
/**
|
|
|
|
* If set, Grist will render the widget after `grist.ready()`.
|
|
|
|
*
|
2023-10-06 13:17:39 +00:00
|
|
|
* This is used to defer showing a widget on initial load until it has finished
|
|
|
|
* applying the Grist theme.
|
2023-09-19 18:44:22 +00:00
|
|
|
*/
|
|
|
|
renderAfterReady?: boolean;
|
2023-10-31 01:13:21 +00:00
|
|
|
/**
|
|
|
|
* If set to false, do not offer to user in UI.
|
|
|
|
*/
|
|
|
|
published?: boolean;
|
2023-10-27 19:34:42 +00:00
|
|
|
/**
|
|
|
|
* If the widget came from a plugin, we track that here.
|
|
|
|
*/
|
|
|
|
source?: {
|
|
|
|
pluginId: string;
|
|
|
|
name: string;
|
|
|
|
};
|
2024-08-13 23:21:48 +00:00
|
|
|
/**
|
|
|
|
* Widget description.
|
|
|
|
*/
|
|
|
|
description?: string;
|
|
|
|
/**
|
|
|
|
* Widget authors.
|
|
|
|
*
|
|
|
|
* The first author is the one shown in the UI.
|
|
|
|
*/
|
|
|
|
authors?: WidgetAuthor[];
|
|
|
|
/**
|
|
|
|
* Date the widget was last updated.
|
|
|
|
*/
|
|
|
|
lastUpdatedAt?: string;
|
|
|
|
/**
|
|
|
|
* If the widget is maintained by Grist Labs.
|
|
|
|
*/
|
|
|
|
isGristLabsMaintained?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface WidgetAuthor {
|
|
|
|
name: string;
|
|
|
|
url?: string;
|
2021-11-26 10:43:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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",
|
|
|
|
}
|
2022-01-12 13:30:51 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2023-10-27 19:34:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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];
|
|
|
|
}
|