From 548550dd1dc783ab223e882939a1fb4da7af3705 Mon Sep 17 00:00:00 2001 From: James Kerr Date: Mon, 11 Dec 2023 15:28:21 -0800 Subject: [PATCH 01/16] Copy Paste Data --- .../loads/handlers/preview-load-files.ts | 1 + apps/zui/src/domain/loads/messages.ts | 2 ++ apps/zui/src/domain/loads/operations/index.ts | 1 + apps/zui/src/domain/loads/operations/paste.ts | 14 ++++++++++++++ .../src/electron/windows/search/app-menu.ts | 19 ++++++++++++++++++- 5 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 apps/zui/src/domain/loads/operations/paste.ts diff --git a/apps/zui/src/domain/loads/handlers/preview-load-files.ts b/apps/zui/src/domain/loads/handlers/preview-load-files.ts index 00aed02002..176d46f246 100644 --- a/apps/zui/src/domain/loads/handlers/preview-load-files.ts +++ b/apps/zui/src/domain/loads/handlers/preview-load-files.ts @@ -5,6 +5,7 @@ import Pools from "src/js/state/Pools" import {quickLoadFiles} from "./quick-load-files" export const previewLoadFiles = createHandler( + "loads.previewLoadFiles", async ( {dispatch, invoke, select}, opts: {files: string[]; poolId?: string} diff --git a/apps/zui/src/domain/loads/messages.ts b/apps/zui/src/domain/loads/messages.ts index 7de843963f..4c53d42897 100644 --- a/apps/zui/src/domain/loads/messages.ts +++ b/apps/zui/src/domain/loads/messages.ts @@ -21,8 +21,10 @@ export type LoadsOperations = { "loads.getFileTypes": typeof ops.getFileTypes "loads.abortPreview": typeof ops.abortPreview "loads.abort": typeof ops.abort + "loads.paste": typeof ops.paste } export type LoadsHandlers = { "loads.chooseFiles": typeof handlers.chooseFiles + "loads.previewLoadFiles": typeof handlers.previewLoadFiles } diff --git a/apps/zui/src/domain/loads/operations/index.ts b/apps/zui/src/domain/loads/operations/index.ts index 508f809550..2aa73dcc1f 100644 --- a/apps/zui/src/domain/loads/operations/index.ts +++ b/apps/zui/src/domain/loads/operations/index.ts @@ -2,3 +2,4 @@ export * from "./get-file-types" export * from "./preview" export * from "./create" export * from "./abort" +export * from "./paste" diff --git a/apps/zui/src/domain/loads/operations/paste.ts b/apps/zui/src/domain/loads/operations/paste.ts new file mode 100644 index 0000000000..32139a29fd --- /dev/null +++ b/apps/zui/src/domain/loads/operations/paste.ts @@ -0,0 +1,14 @@ +import {clipboard} from "electron" +import {createOperation} from "src/core/operations" +import path from "path" +import os from "os" +import {writeFileSync} from "fs-extra" +import {sendToFocusedWindow} from "src/core/ipc" + +export const paste = createOperation("loads.paste", () => { + const data = clipboard.readText() + const file = path.join(os.tmpdir(), "clipboard.txt") + writeFileSync(file, data) + sendToFocusedWindow("loads.previewLoadFiles", {files: [file]}) + return path +}) diff --git a/apps/zui/src/electron/windows/search/app-menu.ts b/apps/zui/src/electron/windows/search/app-menu.ts index d03b793384..f499fafd6f 100644 --- a/apps/zui/src/electron/windows/search/app-menu.ts +++ b/apps/zui/src/electron/windows/search/app-menu.ts @@ -13,6 +13,7 @@ import {showReleaseNotesOp} from "../../ops/show-release-notes-op" import {SearchWindow} from "./search-window" import {sendToFocusedWindow} from "src/core/ipc" import {open as openUpdateWindow} from "src/domain/updates/operations" +import {paste} from "src/domain/loads/operations" export const defaultAppMenuState = () => ({ showRightPane: true, @@ -85,6 +86,12 @@ export function compileTemplate( accelerator: "CmdOrCtrl+O", } + const pasteData: MenuItemConstructorOptions = { + label: "Paste Data...", + click: paste, + accelerator: "CmdOrCtrl+Shift+V", + } + const appNameMenu: MenuItemConstructorOptions = { label: app.getName(), submenu: [ @@ -104,12 +111,22 @@ export function compileTemplate( function fileSubmenu(): MenuItemConstructorOptions[] { if (mac) { - return [newWindow, __, openFile, exportResults, __, closeTab, closeWindow] + return [ + newWindow, + __, + openFile, + pasteData, + exportResults, + __, + closeTab, + closeWindow, + ] } else { return [ newWindow, __, openFile, + pasteData, exportResults, __, settings, From e82a489a86c944656ca665ebc75ea1fd3e02d18c Mon Sep 17 00:00:00 2001 From: James Kerr Date: Thu, 14 Dec 2023 10:50:20 -0800 Subject: [PATCH 02/16] Add Integration Test --- apps/zui/src/domain/loads/operations/paste.ts | 2 +- packages/zui-player/helpers/test-app.ts | 27 +++++++++++++++++-- packages/zui-player/tests/copy-paste.spec.ts | 12 +++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 packages/zui-player/tests/copy-paste.spec.ts diff --git a/apps/zui/src/domain/loads/operations/paste.ts b/apps/zui/src/domain/loads/operations/paste.ts index 32139a29fd..61ad934244 100644 --- a/apps/zui/src/domain/loads/operations/paste.ts +++ b/apps/zui/src/domain/loads/operations/paste.ts @@ -10,5 +10,5 @@ export const paste = createOperation("loads.paste", () => { const file = path.join(os.tmpdir(), "clipboard.txt") writeFileSync(file, data) sendToFocusedWindow("loads.previewLoadFiles", {files: [file]}) - return path + return file }) diff --git a/packages/zui-player/helpers/test-app.ts b/packages/zui-player/helpers/test-app.ts index e66e848c1d..7aa030ee5f 100644 --- a/packages/zui-player/helpers/test-app.ts +++ b/packages/zui-player/helpers/test-app.ts @@ -146,8 +146,8 @@ export default class TestApp { return wins[winTitles.findIndex((wTitle) => wTitle === title)]; } - sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); + sleep(s: number) { + return new Promise((resolve) => setTimeout(resolve, s * 1000)); } get results() { @@ -189,6 +189,29 @@ export default class TestApp { return this.mainWin.getByRole(role, { name }); } } + + async invoke(name: string, ...args: any[]) { + return await this.page.evaluate( + ({ name, args }) => { + // @ts-ignore + window.zui.invoke(name, ...args); + }, + { name, args } + ); + } + + get evalMain() { + return this.zui.evaluate.bind(this.zui); + } + + get evalPage() { + return this.page.evaluate.bind(this.page); + } + + debugLogs() { + this.zui.process().stdout.on('data', console.log); + this.zui.process().stderr.on('data', console.log); + } } const getAppInfo = () => { diff --git a/packages/zui-player/tests/copy-paste.spec.ts b/packages/zui-player/tests/copy-paste.spec.ts new file mode 100644 index 0000000000..0005d2c3d2 --- /dev/null +++ b/packages/zui-player/tests/copy-paste.spec.ts @@ -0,0 +1,12 @@ +import { play } from 'zui-player'; + +play('Copy Paste Data', (app, test) => { + test('copy data from the clipboard', async () => { + await app.evalMain(({ clipboard }) => clipboard.writeText('{a: 1}')); + await app.invoke('loads.paste'); + await app.click('button', 'Load'); + await app.click('button', 'Query Pool'); + const results = await app.getViewerResults(); + test.expect(results).toEqual(['a', '1']); + }); +}); From 2e116990e8532f53d8648c1590db3eb0ec24e1d1 Mon Sep 17 00:00:00 2001 From: James Kerr Date: Thu, 14 Dec 2023 16:48:57 -0800 Subject: [PATCH 03/16] Clean Up Pasted Files --- apps/zui/src/components/dialog/dialog.tsx | 9 +++- apps/zui/src/domain/app/plugin-api.ts | 7 +++ .../loads/handlers/preview-load-files.ts | 11 +++-- apps/zui/src/domain/loads/load-context.ts | 17 +++----- apps/zui/src/domain/loads/load-model.ts | 4 +- apps/zui/src/domain/loads/load-ref.ts | 18 ++++++++ apps/zui/src/domain/loads/messages.ts | 1 + .../zui/src/domain/loads/operations/cancel.ts | 10 +++++ apps/zui/src/domain/loads/operations/index.ts | 1 + apps/zui/src/domain/loads/operations/paste.ts | 22 ++++++++-- apps/zui/src/domain/loads/plugin-api.ts | 10 ++++- apps/zui/src/domain/loads/temp-file-holder.ts | 43 +++++++++++++++++++ apps/zui/src/domain/plugin-api.ts | 2 + apps/zui/src/domain/pools/plugin-api.ts | 22 ++-------- apps/zui/src/domain/pools/utils.ts | 11 +---- apps/zui/src/util/basename.ts | 3 ++ apps/zui/src/util/get-uniq-name.ts | 9 ++++ apps/zui/src/util/typed-emitter.ts | 22 ++++++++++ apps/zui/src/views/load-pane/form.tsx | 12 ++++-- apps/zui/src/views/load-pane/index.tsx | 20 +++++---- apps/zui/src/views/load-pane/sidebar.tsx | 8 +++- packages/zui-player/helpers/test-app.ts | 8 ++-- packages/zui-player/tests/copy-paste.spec.ts | 2 + 23 files changed, 206 insertions(+), 66 deletions(-) create mode 100644 apps/zui/src/domain/app/plugin-api.ts create mode 100644 apps/zui/src/domain/loads/load-ref.ts create mode 100644 apps/zui/src/domain/loads/operations/cancel.ts create mode 100644 apps/zui/src/domain/loads/temp-file-holder.ts create mode 100644 apps/zui/src/util/basename.ts create mode 100644 apps/zui/src/util/get-uniq-name.ts create mode 100644 apps/zui/src/util/typed-emitter.ts diff --git a/apps/zui/src/components/dialog/dialog.tsx b/apps/zui/src/components/dialog/dialog.tsx index be8224bf02..5500638ba8 100644 --- a/apps/zui/src/components/dialog/dialog.tsx +++ b/apps/zui/src/components/dialog/dialog.tsx @@ -8,6 +8,7 @@ import {omit} from "lodash" export type DialogProps = { isOpen: boolean onClose: () => void + onCancel: () => void modal?: boolean onOutsideClick?: (e: globalThis.MouseEvent) => void onClick?: MouseEventHandler @@ -44,11 +45,17 @@ export function Dialog(props: DialogProps) { props.onClose() } + function onCancel(e) { + e.preventDefault() + props.onCancel() + props.onClose() + } + return ( void +} + +export class AppApi extends TypedEmitter {} diff --git a/apps/zui/src/domain/loads/handlers/preview-load-files.ts b/apps/zui/src/domain/loads/handlers/preview-load-files.ts index 176d46f246..0824b18a8b 100644 --- a/apps/zui/src/domain/loads/handlers/preview-load-files.ts +++ b/apps/zui/src/domain/loads/handlers/preview-load-files.ts @@ -18,9 +18,14 @@ export const previewLoadFiles = createHandler( if (files.length === 1 && files[0].type === "pcap") { quickLoadFiles({files: files.map((f) => f.path), poolId}) } else { - dispatch(LoadDataForm.setPoolId(poolId)) - dispatch(LoadDataForm.setFiles(opts.files)) - dispatch(LoadDataForm.setShow(true)) + if (select(LoadDataForm.getShow)) { + // The preview load is already opened + dispatch(LoadDataForm.addFiles(opts.files)) + } else { + dispatch(LoadDataForm.setFiles(opts.files)) + dispatch(LoadDataForm.setShow(true)) + dispatch(LoadDataForm.setPoolId(poolId)) + } } } ) diff --git a/apps/zui/src/domain/loads/load-context.ts b/apps/zui/src/domain/loads/load-context.ts index f9dea2cb9d..77a24e7c09 100644 --- a/apps/zui/src/domain/loads/load-context.ts +++ b/apps/zui/src/domain/loads/load-context.ts @@ -4,6 +4,8 @@ import Loads from "src/js/state/Loads" import {syncPoolOp} from "src/electron/ops/sync-pool-op" import {SearchWindow} from "src/electron/windows/search/search-window" import {MainObject} from "../../core/main/main-object" +import {createLoadRef} from "./load-ref" +import {select} from "src/core/main/select" export class LoadContext { private ctl = new AbortController() @@ -23,16 +25,7 @@ export class LoadContext { this.window.loadsInProgress++ this.main.abortables.add({id: this.id, abort: () => this.ctl.abort()}) this.main.dispatch( - Loads.create({ - id: this.id, - poolId: this.opts.poolId, - progress: 0, - files: this.opts.files, - startedAt: new Date().toISOString(), - finishedAt: null, - abortedAt: null, - errors: [], - }) + Loads.create(createLoadRef(this.id, this.opts.poolId, this.opts.files)) ) } @@ -66,6 +59,10 @@ export class LoadContext { this.ctl.abort() } + get ref() { + return select((s) => Loads.find(s, this.id)) + } + get signal() { return this.ctl.signal } diff --git a/apps/zui/src/domain/loads/load-model.ts b/apps/zui/src/domain/loads/load-model.ts index 9f028eda4f..5e5c67f372 100644 --- a/apps/zui/src/domain/loads/load-model.ts +++ b/apps/zui/src/domain/loads/load-model.ts @@ -1,4 +1,5 @@ import {LoadReference} from "src/js/state/Loads/types" +import {basename} from "src/util/basename" export class LoadModel { constructor(private ref: LoadReference) {} @@ -8,8 +9,7 @@ export class LoadModel { } get humanizeFiles() { - // basename - return this.ref.files.join(", ") + return this.ref.files.map(basename).join(", ") } get status() { diff --git a/apps/zui/src/domain/loads/load-ref.ts b/apps/zui/src/domain/loads/load-ref.ts new file mode 100644 index 0000000000..d0d27796c8 --- /dev/null +++ b/apps/zui/src/domain/loads/load-ref.ts @@ -0,0 +1,18 @@ +import {LoadReference} from "src/js/state/Loads/types" + +export function createLoadRef( + id: string, + poolId: string, + files: string[] +): LoadReference { + return { + id, + poolId, + progress: 0, + files, + startedAt: new Date().toISOString(), + finishedAt: null, + abortedAt: null, + errors: [], + } +} diff --git a/apps/zui/src/domain/loads/messages.ts b/apps/zui/src/domain/loads/messages.ts index 4c53d42897..3a705c1ce2 100644 --- a/apps/zui/src/domain/loads/messages.ts +++ b/apps/zui/src/domain/loads/messages.ts @@ -22,6 +22,7 @@ export type LoadsOperations = { "loads.abortPreview": typeof ops.abortPreview "loads.abort": typeof ops.abort "loads.paste": typeof ops.paste + "loads.cancel": typeof ops.cancel } export type LoadsHandlers = { diff --git a/apps/zui/src/domain/loads/operations/cancel.ts b/apps/zui/src/domain/loads/operations/cancel.ts new file mode 100644 index 0000000000..a010e46922 --- /dev/null +++ b/apps/zui/src/domain/loads/operations/cancel.ts @@ -0,0 +1,10 @@ +import {loads} from "src/zui" +import {createLoadRef} from "../load-ref" +import {createOperation} from "src/core/operations" + +export const cancel = createOperation( + "loads.cancel", + (ctx, poolId: string, files: string[]) => { + loads.emit("abort", createLoadRef("new", poolId, files)) + } +) diff --git a/apps/zui/src/domain/loads/operations/index.ts b/apps/zui/src/domain/loads/operations/index.ts index 2aa73dcc1f..87f1038cc0 100644 --- a/apps/zui/src/domain/loads/operations/index.ts +++ b/apps/zui/src/domain/loads/operations/index.ts @@ -3,3 +3,4 @@ export * from "./preview" export * from "./create" export * from "./abort" export * from "./paste" +export * from "./cancel" diff --git a/apps/zui/src/domain/loads/operations/paste.ts b/apps/zui/src/domain/loads/operations/paste.ts index 61ad934244..c887a60766 100644 --- a/apps/zui/src/domain/loads/operations/paste.ts +++ b/apps/zui/src/domain/loads/operations/paste.ts @@ -1,14 +1,28 @@ import {clipboard} from "electron" import {createOperation} from "src/core/operations" -import path from "path" import os from "os" -import {writeFileSync} from "fs-extra" +import * as zui from "src/zui" import {sendToFocusedWindow} from "src/core/ipc" +import {TempFileHolder} from "../temp-file-holder" + +const namespace = os.userInfo().username + "_pastes" +const pastes = new TempFileHolder(namespace) export const paste = createOperation("loads.paste", () => { const data = clipboard.readText() - const file = path.join(os.tmpdir(), "clipboard.txt") - writeFileSync(file, data) + const file = pastes.createFile("paste", data) sendToFocusedWindow("loads.previewLoadFiles", {files: [file]}) return file }) + +function removeFiles(loadFiles: string[]) { + for (let file of loadFiles) { + if (pastes.has(file)) pastes.removeFile(file) + } +} + +zui.loads.on("error", (load) => removeFiles(load.files)) +zui.loads.on("abort", (load) => removeFiles(load.files)) +zui.loads.on("success", (load) => removeFiles(load.files)) + +// TODO zui.app.on("quit", () => pastes.destroy()) diff --git a/apps/zui/src/domain/loads/plugin-api.ts b/apps/zui/src/domain/loads/plugin-api.ts index 2ff44ddb2b..7f561e04f4 100644 --- a/apps/zui/src/domain/loads/plugin-api.ts +++ b/apps/zui/src/domain/loads/plugin-api.ts @@ -3,8 +3,16 @@ import {LoadContext} from "./load-context" import {Loader} from "src/core/loader/types" import Loads from "src/js/state/Loads" import {select} from "src/core/main/select" +import {TypedEmitter} from "src/util/typed-emitter" +import {LoadReference} from "src/js/state/Loads/types" -export class LoadsApi { +type Events = { + success: (load: LoadReference) => void + abort: (load: LoadReference) => void + error: (load: LoadReference) => void +} + +export class LoadsApi extends TypedEmitter { private list: LoaderApi[] = [] // Don't use this...or rename to addLoader diff --git a/apps/zui/src/domain/loads/temp-file-holder.ts b/apps/zui/src/domain/loads/temp-file-holder.ts new file mode 100644 index 0000000000..1c299f841e --- /dev/null +++ b/apps/zui/src/domain/loads/temp-file-holder.ts @@ -0,0 +1,43 @@ +import os from "os" +import path from "path" +import * as fs from "fs-extra" +import {getUniqName} from "src/util/get-uniq-name" + +export class TempFileHolder { + files: string[] = [] + dir: string + + constructor(namespace: string) { + this.dir = path.join(os.tmpdir(), namespace) + fs.ensureDirSync(this.dir) + } + + createFile(prefix: string, data: string) { + const file = this.nextFile(prefix) + fs.writeFileSync(file, data) + this.files.push(file) + return file + } + + removeFile(filePath: string) { + fs.removeSync(filePath) + this.files = this.files.filter((f) => f !== filePath) + } + + has(filePath: string) { + return this.files.find((f) => f === filePath) + } + + destroy() { + fs.removeSync(this.dir) + } + + private nextFile(prefix: string) { + console.log(prefix, this.fileNames) + return path.join(this.dir, getUniqName(prefix, this.fileNames)) + } + + private get fileNames() { + return this.files.map((f) => path.basename(f)) + } +} diff --git a/apps/zui/src/domain/plugin-api.ts b/apps/zui/src/domain/plugin-api.ts index d4ec913fed..ea6d965a77 100644 --- a/apps/zui/src/domain/plugin-api.ts +++ b/apps/zui/src/domain/plugin-api.ts @@ -1,3 +1,4 @@ +import {AppApi} from "./app/plugin-api" import {ConfigurationsApi} from "./configurations/plugin-api" import {CorrelationsApi} from "./correlations/plugin-api" import {EnvApi} from "./env/plugin-api" @@ -19,3 +20,4 @@ export const session = new SessionApi() export const correlations = new CorrelationsApi() export const configurations = new ConfigurationsApi() export const pools = new PoolsApi() +export const app = new AppApi() diff --git a/apps/zui/src/domain/pools/plugin-api.ts b/apps/zui/src/domain/pools/plugin-api.ts index 2b54e0a7a7..f0b8241292 100644 --- a/apps/zui/src/domain/pools/plugin-api.ts +++ b/apps/zui/src/domain/pools/plugin-api.ts @@ -1,4 +1,3 @@ -import {EventEmitter} from "events" import {CreatePoolOpts, Pool} from "@brimdata/zed-js" import {updateSettings} from "./operations" import Pools from "src/js/state/Pools" @@ -9,13 +8,13 @@ import {LoadContext} from "src/domain/loads/load-context" import {syncPoolOp} from "src/electron/ops/sync-pool-op" import {LoadOptions} from "src/core/loader/types" import {getMainObject} from "src/core/main" +import {TypedEmitter} from "src/util/typed-emitter" type Events = { create: (event: {pool: Pool}) => void } -export class PoolsApi { - private emitter = new EventEmitter() +export class PoolsApi extends TypedEmitter { configure(poolId: string) { return new PoolConfiguration(poolId) } @@ -45,28 +44,15 @@ export class PoolsApi { await context.setup() await loader.run(context) await waitForPoolStats(context) + loads.emit("success", context.ref) } catch (e) { await loader.rollback(context) + loads.emit("error", context.ref) throw e } finally { context.teardown() } } - - on(name: K, handler: Events[K]) { - this.emitter.on(name, handler) - } - - emit( - name: K, - ...args: Parameters - ) { - this.emitter.emit(name, ...args) - } - - _teardown() { - this.emitter.removeAllListeners() - } } type ConfigMap = { diff --git a/apps/zui/src/domain/pools/utils.ts b/apps/zui/src/domain/pools/utils.ts index 81ca550f72..8628d56b7e 100644 --- a/apps/zui/src/domain/pools/utils.ts +++ b/apps/zui/src/domain/pools/utils.ts @@ -1,4 +1,5 @@ import * as path from "path" +import {getUniqName} from "src/util/get-uniq-name" export function deriveName(files: string[], existingNames: string[]) { let name: string @@ -22,13 +23,3 @@ function inSameDir(paths: string[]) { } return true } - -function join(name: string, num: number) { - return num === 0 ? name : [name, num.toString()].join("_") -} - -function getUniqName(proposal: string, existing: string[]) { - let i = 0 - while (existing.includes(join(proposal, i))) i++ - return join(proposal, i) -} diff --git a/apps/zui/src/util/basename.ts b/apps/zui/src/util/basename.ts new file mode 100644 index 0000000000..a0702b40c0 --- /dev/null +++ b/apps/zui/src/util/basename.ts @@ -0,0 +1,3 @@ +export function basename(fullPath: string) { + return fullPath.replace(/^.*[\\/]/, "") +} diff --git a/apps/zui/src/util/get-uniq-name.ts b/apps/zui/src/util/get-uniq-name.ts new file mode 100644 index 0000000000..3a0a7d6053 --- /dev/null +++ b/apps/zui/src/util/get-uniq-name.ts @@ -0,0 +1,9 @@ +function join(name: string, num: number) { + return num === 0 ? name : [name, num.toString()].join("_") +} + +export function getUniqName(proposal: string, existing: string[]) { + let i = 0 + while (existing.includes(join(proposal, i))) i++ + return join(proposal, i) +} diff --git a/apps/zui/src/util/typed-emitter.ts b/apps/zui/src/util/typed-emitter.ts new file mode 100644 index 0000000000..845ba22121 --- /dev/null +++ b/apps/zui/src/util/typed-emitter.ts @@ -0,0 +1,22 @@ +import EventEmitter from "events" + +type EventMap = {[name: string]: (...args: any[]) => void} + +export class TypedEmitter { + private emitter = new EventEmitter() + + on(name: K, handler: Events[K]) { + this.emitter.on(name, handler) + } + + emit( + name: K, + ...args: Parameters + ) { + this.emitter.emit(name, ...args) + } + + _teardown() { + this.emitter.removeAllListeners() + } +} diff --git a/apps/zui/src/views/load-pane/form.tsx b/apps/zui/src/views/load-pane/form.tsx index 6b7236c205..f999e9a1fc 100644 --- a/apps/zui/src/views/load-pane/form.tsx +++ b/apps/zui/src/views/load-pane/form.tsx @@ -18,7 +18,11 @@ import {LoadFormat} from "@brimdata/zed-js" import {ErrorWell} from "src/components/error-well" import {errorToString} from "src/util/error-to-string" -export function Form(props: {onClose: () => any; isValid: boolean}) { +export function Form(props: { + onClose: () => any + onCancel: () => any + isValid: boolean +}) { const dispatch = useDispatch() const select = useSelect() const pools = useSelector(Current.getPools) @@ -37,7 +41,6 @@ export function Form(props: {onClose: () => any; isValid: boolean}) { const [error, setError] = useState(null) const onSubmit = async (data) => { - console.log("on submit") const shaper = select(LoadDataForm.getShaper) // @ts-ignore const windowId = window.windowId @@ -218,7 +221,10 @@ export function Form(props: {onClose: () => any; isValid: boolean}) {