mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
662 lines
19 KiB
TypeScript
662 lines
19 KiB
TypeScript
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||
|
import {makeT} from 'app/client/lib/localization';
|
||
|
import {ViewSectionRec} from 'app/client/models/DocModel';
|
||
|
import {textInput} from 'app/client/ui/inputs';
|
||
|
import {shadowScroll} from 'app/client/ui/shadowScroll';
|
||
|
import {withInfoTooltip} from 'app/client/ui/tooltips';
|
||
|
import {bigBasicButton, bigPrimaryButton} from 'app/client/ui2018/buttons';
|
||
|
import {theme} from 'app/client/ui2018/cssVars';
|
||
|
import {icon} from 'app/client/ui2018/icons';
|
||
|
import {cssLink} from 'app/client/ui2018/links';
|
||
|
import {loadingSpinner} from 'app/client/ui2018/loaders';
|
||
|
import {IModalControl, modal} from 'app/client/ui2018/modals';
|
||
|
import {AccessLevel, ICustomWidget, matchWidget, WidgetAuthor} from 'app/common/CustomWidget';
|
||
|
import {commonUrls} from 'app/common/gristUrls';
|
||
|
import {bundleChanges, Computed, Disposable, dom, makeTestId, Observable, styled} from 'grainjs';
|
||
|
import escapeRegExp from 'lodash/escapeRegExp';
|
||
|
|
||
|
const testId = makeTestId('test-custom-widget-gallery-');
|
||
|
|
||
|
const t = makeT('CustomWidgetGallery');
|
||
|
|
||
|
export const CUSTOM_URL_WIDGET_ID = 'custom';
|
||
|
|
||
|
interface Options {
|
||
|
sectionRef?: number;
|
||
|
addWidget?(): Promise<{viewRef: number, sectionRef: number}>;
|
||
|
}
|
||
|
|
||
|
export function showCustomWidgetGallery(gristDoc: GristDoc, options: Options = {}) {
|
||
|
modal((ctl) => [
|
||
|
dom.create(CustomWidgetGallery, ctl, gristDoc, options),
|
||
|
cssModal.cls(''),
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
interface WidgetInfo {
|
||
|
variant: WidgetVariant;
|
||
|
id: string;
|
||
|
name: string;
|
||
|
description?: string;
|
||
|
developer?: WidgetAuthor;
|
||
|
lastUpdated?: string;
|
||
|
}
|
||
|
|
||
|
interface CustomWidgetACItem extends ICustomWidget {
|
||
|
cleanText: string;
|
||
|
}
|
||
|
|
||
|
type WidgetVariant = 'custom' | 'grist' | 'community';
|
||
|
|
||
|
class CustomWidgetGallery extends Disposable {
|
||
|
private readonly _customUrl: Observable<string>;
|
||
|
private readonly _filteredWidgets = Observable.create<ICustomWidget[] | null>(this, null);
|
||
|
private readonly _section: ViewSectionRec | null = null;
|
||
|
private readonly _searchText = Observable.create(this, '');
|
||
|
private readonly _saveDisabled: Computed<boolean>;
|
||
|
private readonly _savedWidgetId: Computed<string | null>;
|
||
|
private readonly _selectedWidgetId = Observable.create<string | null>(this, null);
|
||
|
private readonly _widgets = Observable.create<CustomWidgetACItem[] | null>(this, null);
|
||
|
|
||
|
constructor(
|
||
|
private _ctl: IModalControl,
|
||
|
private _gristDoc: GristDoc,
|
||
|
private _options: Options = {}
|
||
|
) {
|
||
|
super();
|
||
|
|
||
|
const {sectionRef} = _options;
|
||
|
if (sectionRef) {
|
||
|
const section = this._gristDoc.docModel.viewSections.getRowModel(sectionRef);
|
||
|
if (!section.id.peek()) {
|
||
|
throw new Error(`Section ${sectionRef} does not exist`);
|
||
|
}
|
||
|
|
||
|
this._section = section;
|
||
|
this.autoDispose(section._isDeleted.subscribe((isDeleted) => {
|
||
|
if (isDeleted) { this._ctl.close(); }
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
let customUrl = '';
|
||
|
if (this._section) {
|
||
|
customUrl = this._section.customDef.url() ?? '';
|
||
|
}
|
||
|
this._customUrl = Observable.create(this, customUrl);
|
||
|
|
||
|
this._savedWidgetId = Computed.create(this, (use) => {
|
||
|
if (!this._section) { return null; }
|
||
|
|
||
|
const {customDef} = this._section;
|
||
|
// May be stored in one of two places, depending on age of document.
|
||
|
const widgetId = use(customDef.widgetId) || use(customDef.widgetDef)?.widgetId;
|
||
|
if (widgetId) {
|
||
|
const pluginId = use(customDef.pluginId);
|
||
|
const widget = matchWidget(use(this._widgets) ?? [], {
|
||
|
widgetId,
|
||
|
pluginId,
|
||
|
});
|
||
|
return widget ? `${pluginId}:${widgetId}` : null;
|
||
|
} else {
|
||
|
return CUSTOM_URL_WIDGET_ID;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this._saveDisabled = Computed.create(this, use => {
|
||
|
const selectedWidgetId = use(this._selectedWidgetId);
|
||
|
if (!selectedWidgetId) { return true; }
|
||
|
if (!this._section) { return false; }
|
||
|
|
||
|
const savedWidgetId = use(this._savedWidgetId);
|
||
|
if (selectedWidgetId === CUSTOM_URL_WIDGET_ID) {
|
||
|
return (
|
||
|
use(this._savedWidgetId) === CUSTOM_URL_WIDGET_ID &&
|
||
|
use(this._customUrl) === use(this._section.customDef.url)
|
||
|
);
|
||
|
} else {
|
||
|
return selectedWidgetId === savedWidgetId;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this._initializeWidgets().catch(reportError);
|
||
|
|
||
|
this.autoDispose(this._searchText.addListener(() => {
|
||
|
this._filterWidgets();
|
||
|
this._selectedWidgetId.set(null);
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
public buildDom() {
|
||
|
return cssCustomWidgetGallery(
|
||
|
cssHeader(
|
||
|
cssTitle(t('Choose Custom Widget')),
|
||
|
cssSearchInputWrapper(
|
||
|
cssSearchIcon('Search'),
|
||
|
cssSearchInput(
|
||
|
this._searchText,
|
||
|
{placeholder: t('Search')},
|
||
|
(el) => { setTimeout(() => el.focus(), 10); },
|
||
|
testId('search'),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
shadowScroll(
|
||
|
this._buildWidgets(),
|
||
|
cssShadowScroll.cls(''),
|
||
|
),
|
||
|
cssFooter(
|
||
|
dom('div',
|
||
|
cssHelpLink(
|
||
|
{href: commonUrls.helpCustomWidgets, target: '_blank'},
|
||
|
cssHelpIcon('Question'),
|
||
|
t('Learn more about Custom Widgets'),
|
||
|
),
|
||
|
),
|
||
|
cssFooterButtons(
|
||
|
bigBasicButton(
|
||
|
t('Cancel'),
|
||
|
dom.on('click', () => this._ctl.close()),
|
||
|
testId('cancel'),
|
||
|
),
|
||
|
bigPrimaryButton(
|
||
|
this._options.addWidget ? t('Add Widget') : t('Change Widget'),
|
||
|
dom.on('click', () => this._save()),
|
||
|
dom.boolAttr('disabled', this._saveDisabled),
|
||
|
testId('save'),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
dom.onKeyDown({
|
||
|
Enter: () => this._save(),
|
||
|
Escape: () => this._deselectOrClose(),
|
||
|
}),
|
||
|
dom.on('click', (ev) => this._maybeClearSelection(ev)),
|
||
|
testId('container'),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private async _initializeWidgets() {
|
||
|
const widgets: ICustomWidget[] = [
|
||
|
{
|
||
|
widgetId: 'custom',
|
||
|
name: t('Custom URL'),
|
||
|
description: t('Add a widget from outside this gallery.'),
|
||
|
url: '',
|
||
|
},
|
||
|
];
|
||
|
try {
|
||
|
const remoteWidgets = await this._gristDoc.appModel.topAppModel.getWidgets();
|
||
|
if (this.isDisposed()) { return; }
|
||
|
|
||
|
widgets.push(...remoteWidgets
|
||
|
.filter(({published}) => published !== false)
|
||
|
.sort((a, b) => a.name.localeCompare(b.name)));
|
||
|
} catch (e) {
|
||
|
reportError(e);
|
||
|
}
|
||
|
|
||
|
this._widgets.set(widgets.map(w => ({...w, cleanText: getWidgetCleanText(w)})));
|
||
|
this._selectedWidgetId.set(this._savedWidgetId.get());
|
||
|
this._filterWidgets();
|
||
|
}
|
||
|
|
||
|
private _filterWidgets() {
|
||
|
const widgets = this._widgets.get();
|
||
|
if (!widgets) { return; }
|
||
|
|
||
|
const searchText = this._searchText.get();
|
||
|
if (!searchText) {
|
||
|
this._filteredWidgets.set(widgets);
|
||
|
} else {
|
||
|
const searchTerms = searchText.trim().split(/\s+/);
|
||
|
const searchPatterns = searchTerms.map(term =>
|
||
|
new RegExp(`\\b${escapeRegExp(term)}`, 'i'));
|
||
|
const filteredWidgets = widgets.filter(({cleanText}) =>
|
||
|
searchPatterns.some(pattern => pattern.test(cleanText))
|
||
|
);
|
||
|
this._filteredWidgets.set(filteredWidgets);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private _buildWidgets() {
|
||
|
return dom.domComputed(this._filteredWidgets, (widgets) => {
|
||
|
if (widgets === null) {
|
||
|
return cssLoadingSpinner(loadingSpinner());
|
||
|
} else if (widgets.length === 0) {
|
||
|
return cssNoMatchingWidgets(t('No matching widgets'));
|
||
|
} else {
|
||
|
return cssWidgets(
|
||
|
widgets.map(widget => {
|
||
|
const {description, authors = [], lastUpdatedAt} = widget;
|
||
|
|
||
|
return this._buildWidget({
|
||
|
variant: getWidgetVariant(widget),
|
||
|
id: getWidgetId(widget),
|
||
|
name: getWidgetName(widget),
|
||
|
description,
|
||
|
developer: authors[0],
|
||
|
lastUpdated: lastUpdatedAt,
|
||
|
});
|
||
|
}),
|
||
|
);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private _buildWidget(info: WidgetInfo) {
|
||
|
const {variant, id, name, description, developer, lastUpdated} = info;
|
||
|
|
||
|
return cssWidget(
|
||
|
dom.cls('custom-widget'),
|
||
|
cssWidgetHeader(
|
||
|
variant === 'custom' ? t('Add Your Own Widget') :
|
||
|
variant === 'grist' ? t('Grist Widget') :
|
||
|
withInfoTooltip(
|
||
|
t('Community Widget'),
|
||
|
'communityWidgets',
|
||
|
{
|
||
|
variant: 'hover',
|
||
|
iconDomArgs: [cssTooltipIcon.cls('')],
|
||
|
}
|
||
|
),
|
||
|
cssWidgetHeader.cls('-secondary', ['custom', 'community'].includes(variant)),
|
||
|
),
|
||
|
cssWidgetBody(
|
||
|
cssWidgetName(
|
||
|
name,
|
||
|
testId('widget-name'),
|
||
|
),
|
||
|
cssWidgetDescription(
|
||
|
description ?? t('(Missing info)'),
|
||
|
cssWidgetDescription.cls('-missing', !description),
|
||
|
testId('widget-description'),
|
||
|
),
|
||
|
variant === 'custom' ? null : cssWidgetMetadata(
|
||
|
variant === 'grist' ? null : cssWidgetMetadataRow(
|
||
|
cssWidgetMetadataName(t('Developer:')),
|
||
|
cssWidgetMetadataValue(
|
||
|
developer?.url
|
||
|
? cssDeveloperLink(
|
||
|
developer.name,
|
||
|
{href: developer.url, target: '_blank'},
|
||
|
dom.on('click', (ev) => ev.stopPropagation()),
|
||
|
testId('widget-developer'),
|
||
|
)
|
||
|
: dom('span',
|
||
|
developer?.name ?? t('(Missing info)'),
|
||
|
testId('widget-developer'),
|
||
|
),
|
||
|
cssWidgetMetadataValue.cls('-missing', !developer?.name),
|
||
|
testId('widget-developer'),
|
||
|
),
|
||
|
),
|
||
|
cssWidgetMetadataRow(
|
||
|
cssWidgetMetadataName(t('Last updated:')),
|
||
|
cssWidgetMetadataValue(
|
||
|
lastUpdated ?
|
||
|
new Date(lastUpdated).toLocaleDateString('default', {
|
||
|
month: 'long',
|
||
|
day: 'numeric',
|
||
|
year: 'numeric',
|
||
|
})
|
||
|
: t('(Missing info)'),
|
||
|
cssWidgetMetadataValue.cls('-missing', !lastUpdated),
|
||
|
testId('widget-last-updated'),
|
||
|
),
|
||
|
),
|
||
|
testId('widget-metadata'),
|
||
|
),
|
||
|
variant !== 'custom' ? null : cssCustomUrlInput(
|
||
|
this._customUrl,
|
||
|
{placeholder: t('Widget URL')},
|
||
|
testId('custom-url'),
|
||
|
),
|
||
|
),
|
||
|
cssWidget.cls('-selected', use => id === use(this._selectedWidgetId)),
|
||
|
dom.on('click', () => this._selectedWidgetId.set(id)),
|
||
|
testId('widget'),
|
||
|
testId(`widget-${variant}`),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private async _save() {
|
||
|
if (this._saveDisabled.get()) { return; }
|
||
|
|
||
|
await this._saveSelectedWidget();
|
||
|
this._ctl.close();
|
||
|
}
|
||
|
|
||
|
private async _deselectOrClose() {
|
||
|
if (this._selectedWidgetId.get()) {
|
||
|
this._selectedWidgetId.set(null);
|
||
|
} else {
|
||
|
this._ctl.close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private async _saveSelectedWidget() {
|
||
|
await this._gristDoc.docData.bundleActions(
|
||
|
'Save selected custom widget',
|
||
|
async () => {
|
||
|
let section = this._section;
|
||
|
if (!section) {
|
||
|
const {addWidget} = this._options;
|
||
|
if (!addWidget) {
|
||
|
throw new Error('Cannot add custom widget: missing `addWidget` implementation');
|
||
|
}
|
||
|
|
||
|
const {sectionRef} = await addWidget();
|
||
|
const newSection = this._gristDoc.docModel.viewSections.getRowModel(sectionRef);
|
||
|
if (!newSection.id.peek()) {
|
||
|
throw new Error(`Section ${sectionRef} does not exist`);
|
||
|
}
|
||
|
section = newSection;
|
||
|
}
|
||
|
const selectedWidgetId = this._selectedWidgetId.get();
|
||
|
if (selectedWidgetId === CUSTOM_URL_WIDGET_ID) {
|
||
|
return this._saveCustomUrlWidget(section);
|
||
|
} else {
|
||
|
return this._saveRemoteWidget(section);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private async _saveCustomUrlWidget(section: ViewSectionRec) {
|
||
|
bundleChanges(() => {
|
||
|
section.customDef.renderAfterReady(false);
|
||
|
section.customDef.url(this._customUrl.get());
|
||
|
section.customDef.widgetId(null);
|
||
|
section.customDef.widgetDef(null);
|
||
|
section.customDef.pluginId('');
|
||
|
section.customDef.access(AccessLevel.none);
|
||
|
section.customDef.widgetOptions(null);
|
||
|
section.hasCustomOptions(false);
|
||
|
section.customDef.columnsMapping(null);
|
||
|
section.columnsToMap(null);
|
||
|
section.desiredAccessLevel(AccessLevel.none);
|
||
|
});
|
||
|
await section.saveCustomDef();
|
||
|
}
|
||
|
|
||
|
private async _saveRemoteWidget(section: ViewSectionRec) {
|
||
|
const [pluginId, widgetId] = this._selectedWidgetId.get()!.split(':');
|
||
|
const {customDef} = section;
|
||
|
if (customDef.pluginId.peek() === pluginId && customDef.widgetId.peek() === widgetId) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const selectedWidget = matchWidget(this._widgets.get() ?? [], {widgetId, pluginId});
|
||
|
if (!selectedWidget) {
|
||
|
throw new Error(`Widget ${this._selectedWidgetId.get()} not found`);
|
||
|
}
|
||
|
|
||
|
bundleChanges(() => {
|
||
|
section.customDef.renderAfterReady(selectedWidget.renderAfterReady ?? false);
|
||
|
section.customDef.access(AccessLevel.none);
|
||
|
section.desiredAccessLevel(selectedWidget.accessLevel ?? AccessLevel.none);
|
||
|
// Keep a record of the original widget definition.
|
||
|
// Don't rely on this much, since the document could
|
||
|
// have moved installation since, and widgets could be
|
||
|
// served from elsewhere.
|
||
|
section.customDef.widgetDef(selectedWidget);
|
||
|
section.customDef.widgetId(selectedWidget.widgetId);
|
||
|
section.customDef.pluginId(selectedWidget.source?.pluginId ?? '');
|
||
|
section.customDef.url(null);
|
||
|
section.customDef.widgetOptions(null);
|
||
|
section.hasCustomOptions(false);
|
||
|
section.customDef.columnsMapping(null);
|
||
|
section.columnsToMap(null);
|
||
|
});
|
||
|
await section.saveCustomDef();
|
||
|
}
|
||
|
|
||
|
private _maybeClearSelection(event: MouseEvent) {
|
||
|
const target = event.target as HTMLElement;
|
||
|
if (
|
||
|
!target.closest('.custom-widget') &&
|
||
|
!target.closest('button') &&
|
||
|
!target.closest('a') &&
|
||
|
!target.closest('input')
|
||
|
) {
|
||
|
this._selectedWidgetId.set(null);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export function getWidgetName({name, source}: ICustomWidget) {
|
||
|
return source?.name ? `${name} (${source.name})` : name;
|
||
|
}
|
||
|
|
||
|
function getWidgetVariant({isGristLabsMaintained = false, widgetId}: ICustomWidget): WidgetVariant {
|
||
|
if (widgetId === CUSTOM_URL_WIDGET_ID) {
|
||
|
return 'custom';
|
||
|
} else if (isGristLabsMaintained) {
|
||
|
return 'grist';
|
||
|
} else {
|
||
|
return 'community';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getWidgetId({source, widgetId}: ICustomWidget) {
|
||
|
if (widgetId === CUSTOM_URL_WIDGET_ID) {
|
||
|
return CUSTOM_URL_WIDGET_ID;
|
||
|
} else {
|
||
|
return `${source?.pluginId ?? ''}:${widgetId}`;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getWidgetCleanText({name, description, authors = []}: ICustomWidget) {
|
||
|
let cleanText = name;
|
||
|
if (description) { cleanText += ` ${description}`; }
|
||
|
if (authors[0]) { cleanText += ` ${authors[0].name}`; }
|
||
|
return cleanText;
|
||
|
}
|
||
|
|
||
|
export const cssWidgetMetadata = styled('div', `
|
||
|
margin-top: auto;
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
row-gap: 4px;
|
||
|
`);
|
||
|
|
||
|
export const cssWidgetMetadataRow = styled('div', `
|
||
|
display: flex;
|
||
|
column-gap: 4px;
|
||
|
`);
|
||
|
|
||
|
export const cssWidgetMetadataName = styled('span', `
|
||
|
color: ${theme.lightText};
|
||
|
font-weight: 600;
|
||
|
`);
|
||
|
|
||
|
export const cssWidgetMetadataValue = styled('div', `
|
||
|
&-missing {
|
||
|
color: ${theme.lightText};
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
export const cssDeveloperLink = styled(cssLink, `
|
||
|
font-weight: 600;
|
||
|
`);
|
||
|
|
||
|
const cssCustomWidgetGallery = styled('div', `
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
outline: none;
|
||
|
`);
|
||
|
|
||
|
const WIDGET_WIDTH_PX = 240;
|
||
|
|
||
|
const WIDGETS_GAP_PX = 16;
|
||
|
|
||
|
const cssHeader = styled('div', `
|
||
|
display: flex;
|
||
|
column-gap: 16px;
|
||
|
row-gap: 8px;
|
||
|
flex-wrap: wrap;
|
||
|
justify-content: space-between;
|
||
|
margin: 40px 40px 16px 40px;
|
||
|
|
||
|
/* Don't go beyond the final grid column. */
|
||
|
max-width: ${(3 * WIDGET_WIDTH_PX) + (2 * WIDGETS_GAP_PX)}px;
|
||
|
`);
|
||
|
|
||
|
const cssTitle = styled('div', `
|
||
|
font-size: 24px;
|
||
|
font-weight: 500;
|
||
|
line-height: 32px;
|
||
|
`);
|
||
|
|
||
|
const cssSearchInputWrapper = styled('div', `
|
||
|
position: relative;
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
`);
|
||
|
|
||
|
const cssSearchIcon = styled(icon, `
|
||
|
margin-left: 8px;
|
||
|
position: absolute;
|
||
|
--icon-color: ${theme.accentIcon};
|
||
|
`);
|
||
|
|
||
|
const cssSearchInput = styled(textInput, `
|
||
|
height: 28px;
|
||
|
padding-left: 32px;
|
||
|
`);
|
||
|
|
||
|
const cssShadowScroll = styled('div', `
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
flex: unset;
|
||
|
flex-grow: 1;
|
||
|
padding: 16px 40px;
|
||
|
`);
|
||
|
|
||
|
const cssCenteredFlexGrow = styled('div', `
|
||
|
flex-grow: 1;
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
`);
|
||
|
|
||
|
const cssLoadingSpinner = cssCenteredFlexGrow;
|
||
|
|
||
|
const cssNoMatchingWidgets = styled(cssCenteredFlexGrow, `
|
||
|
color: ${theme.lightText};
|
||
|
`);
|
||
|
|
||
|
const cssWidgets = styled('div', `
|
||
|
display: grid;
|
||
|
grid-template-columns: repeat(auto-fill, minmax(0px, ${WIDGET_WIDTH_PX}px));
|
||
|
gap: ${WIDGETS_GAP_PX}px;
|
||
|
`);
|
||
|
|
||
|
const cssWidget = styled('div', `
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
box-shadow: 1px 1px 4px 1px ${theme.widgetGalleryShadow};
|
||
|
border-radius: 4px;
|
||
|
min-height: 183.5px;
|
||
|
cursor: pointer;
|
||
|
|
||
|
&:hover {
|
||
|
background-color: ${theme.widgetGalleryBgHover};
|
||
|
}
|
||
|
&-selected {
|
||
|
outline: 2px solid ${theme.widgetGalleryBorderSelected};
|
||
|
outline-offset: -2px;
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
const cssWidgetHeader = styled('div', `
|
||
|
flex-shrink: 0;
|
||
|
border: 2px solid ${theme.widgetGalleryBorder};
|
||
|
border-bottom: 1px solid ${theme.widgetGalleryBorder};
|
||
|
border-radius: 4px 4px 0px 0px;
|
||
|
color: ${theme.lightText};
|
||
|
font-size: 10px;
|
||
|
line-height: 16px;
|
||
|
font-weight: 500;
|
||
|
padding: 4px 18px;
|
||
|
text-transform: uppercase;
|
||
|
|
||
|
&-secondary {
|
||
|
border: 0px;
|
||
|
color: ${theme.widgetGallerySecondaryHeaderFg};
|
||
|
background-color: ${theme.widgetGallerySecondaryHeaderBg};
|
||
|
}
|
||
|
.${cssWidget.className}:hover &-secondary {
|
||
|
background-color: ${theme.widgetGallerySecondaryHeaderBgHover};
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
const cssWidgetBody = styled('div', `
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
flex-grow: 1;
|
||
|
border: 2px solid ${theme.widgetGalleryBorder};
|
||
|
border-top: 0px;
|
||
|
border-radius: 0px 0px 4px 4px;
|
||
|
padding: 16px;
|
||
|
`);
|
||
|
|
||
|
const cssWidgetName = styled('div', `
|
||
|
font-size: 15px;
|
||
|
font-weight: 600;
|
||
|
margin-bottom: 16px;
|
||
|
`);
|
||
|
|
||
|
const cssWidgetDescription = styled('div', `
|
||
|
margin-bottom: 24px;
|
||
|
|
||
|
&-missing {
|
||
|
color: ${theme.lightText};
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
const cssCustomUrlInput = styled(textInput, `
|
||
|
height: 28px;
|
||
|
`);
|
||
|
|
||
|
const cssHelpLink = styled(cssLink, `
|
||
|
display: inline-flex;
|
||
|
align-items: center;
|
||
|
column-gap: 8px;
|
||
|
`);
|
||
|
|
||
|
const cssHelpIcon = styled(icon, `
|
||
|
flex-shrink: 0;
|
||
|
`);
|
||
|
|
||
|
const cssFooter = styled('div', `
|
||
|
flex-shrink: 0;
|
||
|
display: flex;
|
||
|
flex-wrap: wrap;
|
||
|
justify-content: space-between;
|
||
|
align-items: center;
|
||
|
gap: 16px;
|
||
|
padding: 16px 40px;
|
||
|
border-top: 1px solid ${theme.widgetGalleryBorder};
|
||
|
`);
|
||
|
|
||
|
const cssFooterButtons = styled('div', `
|
||
|
display: flex;
|
||
|
column-gap: 8px;
|
||
|
`);
|
||
|
|
||
|
const cssModal = styled('div', `
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
max-width: 930px;
|
||
|
max-height: 623px;
|
||
|
padding: 0px;
|
||
|
`);
|
||
|
|
||
|
const cssTooltipIcon = styled('div', `
|
||
|
color: ${theme.widgetGallerySecondaryHeaderFg};
|
||
|
border-color: ${theme.widgetGallerySecondaryHeaderFg};
|
||
|
`);
|