import ViewController, { EmitMessageEvent } from './ViewController'; import shim from '../../shim'; import { ButtonSpec, DialogResult, ViewHandle } from './api/types'; const { toSystemSlashes } = require('../../path-utils'); import PostMessageService, { MessageParticipant } from '../PostMessageService'; export enum ContainerType { Panel = 'panel', Dialog = 'dialog', } export interface Options { containerType: ContainerType; } interface CloseResponse { // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied resolve: Function; // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied reject: Function; } // TODO: Copied from: // packages/app-desktop/gui/ResizableLayout/utils/findItemByKey.ts function findItemByKey(layout: any, key: string): any { if (!layout) throw new Error('Layout cannot be null'); function recurseFind(item: any): any { if (item.key === key) return item; if (item.children) { for (const child of item.children) { const found = recurseFind(child); if (found) return found; } } return null; } return recurseFind(layout); } export default class WebviewController extends ViewController { private baseDir_: string; // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied private messageListener_: Function = null; private closeResponse_: CloseResponse = null; // True if a **panel** is shown in a modal window. private panelInModalMode_ = false; public constructor(handle: ViewHandle, pluginId: string, store: any, baseDir: string, containerType: ContainerType) { super(handle, pluginId, store); this.baseDir_ = toSystemSlashes(baseDir, 'linux'); this.store.dispatch({ type: 'PLUGIN_VIEW_ADD', pluginId: pluginId, view: { id: this.handle, type: this.type, containerType: containerType, html: '', scripts: [], opened: false, buttons: null, fitToContent: true, }, }); } public get type(): string { return 'webview'; } private setStoreProp(name: string, value: any) { this.store.dispatch({ type: 'PLUGIN_VIEW_PROP_SET', pluginId: this.pluginId, id: this.handle, name: name, value: value, }); } public get html(): string { return this.storeView.html; } public set html(html: string) { this.setStoreProp('html', html); } public get containerType(): ContainerType { return this.storeView.containerType; } public async addScript(path: string) { const fullPath = toSystemSlashes(shim.fsDriver().resolve(`${this.baseDir_}/${path}`), 'linux'); if (fullPath.indexOf(this.baseDir_) !== 0) throw new Error(`Script appears to be outside of plugin base directory: ${fullPath} (Base dir: ${this.baseDir_})`); this.store.dispatch({ type: 'PLUGIN_VIEW_PROP_PUSH', pluginId: this.pluginId, id: this.handle, name: 'scripts', value: fullPath, }); } public postMessage(message: any) { const messageId = `plugin_${Date.now()}${Math.random()}`; void PostMessageService.instance().postMessage({ pluginId: this.pluginId, viewId: this.handle, contentScriptId: null, from: MessageParticipant.Plugin, to: MessageParticipant.UserWebview, id: messageId, content: message, }); } public async emitMessage(event: EmitMessageEvent): Promise { if (!this.messageListener_) return; return this.messageListener_(event.message); } public onMessage(callback: any) { this.messageListener_ = callback; } // --------------------------------------------- // Specific to panels // --------------------------------------------- public async show(show = true): Promise { this.store.dispatch({ type: 'MAIN_LAYOUT_SET_ITEM_PROP', itemKey: this.handle, propName: 'visible', propValue: show, }); } public async hide(): Promise { return this.show(false); } // This method allows us to determine whether a panel is shown in dialog mode, // which is used on mobile. public setIsShownInModal(shown: boolean) { this.panelInModalMode_ = shown; } public get visible(): boolean { const appState = this.store.getState(); if (this.panelInModalMode_) { return true; } const mainLayout = appState.mainLayout; // Mobile: There is no appState.mainLayout if (!mainLayout) { return false; } const item = findItemByKey(mainLayout, this.handle); return item ? item.visible : false; } // --------------------------------------------- // Specific to dialogs // --------------------------------------------- public async open(): Promise { this.store.dispatch({ type: 'VISIBLE_DIALOGS_ADD', name: this.handle, }); this.setStoreProp('opened', true); // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied return new Promise((resolve: Function, reject: Function) => { this.closeResponse_ = { resolve, reject }; }); } public close() { this.store.dispatch({ type: 'VISIBLE_DIALOGS_REMOVE', name: this.handle, }); this.setStoreProp('opened', false); } public closeWithResponse(result: DialogResult) { this.close(); this.closeResponse_.resolve(result); } public get buttons(): ButtonSpec[] { return this.storeView.buttons; } public set buttons(buttons: ButtonSpec[]) { this.setStoreProp('buttons', buttons); } public get fitToContent(): boolean { return this.storeView.fitToContent; } public set fitToContent(fitToContent: boolean) { this.setStoreProp('fitToContent', fitToContent); } }