2022-10-28 16:11:08 +00:00
|
|
|
import { makeT } from 'app/client/lib/localization';
|
2021-08-05 15:12:46 +00:00
|
|
|
import { bigBasicButton } from 'app/client/ui2018/buttons';
|
|
|
|
import { testId } from 'app/client/ui2018/cssVars';
|
|
|
|
import { loadingSpinner } from 'app/client/ui2018/loaders';
|
|
|
|
import { cssModalButtons, cssModalTitle, IModalControl, modal } from 'app/client/ui2018/modals';
|
|
|
|
import { PluginInstance } from 'app/common/PluginInstance';
|
|
|
|
import { RenderTarget } from 'app/plugin/RenderOptions';
|
|
|
|
import { Disposable, dom, DomContents, Observable, styled } from 'grainjs';
|
|
|
|
|
2022-10-28 16:11:08 +00:00
|
|
|
const t = makeT('components.PluginScreen');
|
|
|
|
|
2021-11-09 20:03:12 +00:00
|
|
|
/**
|
|
|
|
* Rendering options for the PluginScreen modal.
|
|
|
|
*/
|
|
|
|
export interface RenderOptions {
|
|
|
|
// Maximizes modal to fill the viewport.
|
|
|
|
fullscreen?: boolean;
|
|
|
|
}
|
|
|
|
|
2021-08-05 15:12:46 +00:00
|
|
|
/**
|
|
|
|
* Helper for showing plugin components during imports.
|
|
|
|
*/
|
|
|
|
export class PluginScreen extends Disposable {
|
|
|
|
private _openModalCtl: IModalControl | null = null;
|
|
|
|
private _importerContent = Observable.create<DomContents>(this, null);
|
2021-11-09 20:03:12 +00:00
|
|
|
private _fullscreen = Observable.create(this, false);
|
2021-08-05 15:12:46 +00:00
|
|
|
|
|
|
|
constructor(private _title: string) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
// The importer state showing the inline element from the plugin (e.g. to enter URL in case of
|
|
|
|
// import-from-url).
|
|
|
|
public renderContent(inlineElement: HTMLElement) {
|
|
|
|
this.render([this._buildModalTitle(), inlineElement]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// registers a render target for plugin to render inline.
|
|
|
|
public renderPlugin(plugin: PluginInstance): RenderTarget {
|
|
|
|
const handle: RenderTarget = plugin.addRenderTarget((el, opt = {}) => {
|
|
|
|
el.style.width = "100%";
|
|
|
|
el.style.height = opt.height || "200px";
|
|
|
|
this.renderContent(el);
|
|
|
|
});
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
|
2021-11-09 20:03:12 +00:00
|
|
|
public render(content: DomContents, options?: RenderOptions) {
|
2021-08-05 15:12:46 +00:00
|
|
|
this.showImportDialog();
|
|
|
|
this._importerContent.set(content);
|
2021-11-09 20:03:12 +00:00
|
|
|
this._fullscreen.set(Boolean(options?.fullscreen));
|
2021-08-05 15:12:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// The importer state showing just an error.
|
|
|
|
public renderError(message: string) {
|
|
|
|
this.render([
|
|
|
|
this._buildModalTitle(),
|
2022-12-06 13:40:02 +00:00
|
|
|
cssModalBody(t("Import failed: "), message, testId('importer-error')),
|
2021-08-05 15:12:46 +00:00
|
|
|
cssModalButtons(
|
|
|
|
bigBasicButton('Close',
|
|
|
|
dom.on('click', () => this.close()),
|
|
|
|
testId('modal-cancel'))),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// The importer state showing just a spinner, when the user has to wait. We don't even let the
|
|
|
|
// user cancel it, because the cleanup can only happen properly once the wait completes.
|
|
|
|
public renderSpinner() {
|
|
|
|
this.render([this._buildModalTitle(), cssSpinner(loadingSpinner())]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public close() {
|
|
|
|
this._openModalCtl?.close();
|
|
|
|
this._openModalCtl = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public showImportDialog() {
|
|
|
|
if (this._openModalCtl) { return; }
|
|
|
|
modal((ctl) => {
|
|
|
|
this._openModalCtl = ctl;
|
|
|
|
return [
|
|
|
|
cssModalOverrides.cls(''),
|
2021-11-09 20:03:12 +00:00
|
|
|
cssModalOverrides.cls('-fullscreen', this._fullscreen),
|
2021-08-05 15:12:46 +00:00
|
|
|
dom.domComputed(this._importerContent),
|
|
|
|
testId('importer-dialog'),
|
|
|
|
];
|
|
|
|
}, {
|
|
|
|
noClickAway: true,
|
|
|
|
noEscapeKey: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private _buildModalTitle(rightElement?: DomContents) {
|
|
|
|
return cssModalHeader(cssModalTitle(this._title), rightElement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const cssModalOverrides = styled('div', `
|
|
|
|
max-height: calc(100% - 32px);
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
& > .${cssModalButtons.className} {
|
|
|
|
margin-top: 16px;
|
|
|
|
}
|
2021-11-09 20:03:12 +00:00
|
|
|
|
|
|
|
&-fullscreen {
|
|
|
|
height: 100%;
|
|
|
|
margin: 32px;
|
|
|
|
}
|
2021-08-05 15:12:46 +00:00
|
|
|
`);
|
|
|
|
|
|
|
|
const cssModalBody = styled('div', `
|
|
|
|
padding: 16px 0;
|
|
|
|
overflow-y: auto;
|
|
|
|
max-width: 470px;
|
2022-04-26 05:50:57 +00:00
|
|
|
white-space: pre-line;
|
2021-08-05 15:12:46 +00:00
|
|
|
`);
|
|
|
|
|
|
|
|
const cssModalHeader = styled('div', `
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: space-between;
|
|
|
|
margin-bottom: 16px;
|
|
|
|
& > .${cssModalTitle.className} {
|
|
|
|
margin-bottom: 0px;
|
|
|
|
}
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssSpinner = styled('div', `
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
height: 80px;
|
|
|
|
margin: auto;
|
|
|
|
`);
|