gristlabs_grist-core/app/common/CustomWidget.ts
George Gevoian e70c294e3d (core) Add custom widget gallery
Summary:
Custom widgets are now shown in a gallery.

The gallery is automatically opened when a new custom widget is
added to a page.

Descriptions, authors, and update times are pulled from the widget
manifest.

Test Plan: Browser tests.

Reviewers: jarek

Reviewed By: jarek

Subscribers: dsagal

Differential Revision: https://phab.getgrist.com/D4309
2024-08-14 16:48:36 -04:00

116 lines
2.6 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 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;
}
/**
* 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];
}