From 68db66383eb26a68c975496bb496958cbbfc1e5c Mon Sep 17 00:00:00 2001 From: Bart Date: Sat, 4 Nov 2023 16:27:44 +0100 Subject: [PATCH] Handle workspace settings update (#202) * stop fetching dependency errors once they are resolved * handle workspace update * run format * add todo --- .../DependencyErrors/DependencyErrors.tsx | 16 +++++-- .../src/services/flowser-app.service.ts | 48 ++++++++++++++----- .../src/services/workspace.service.ts | 3 ++ packages/nodejs/src/flow-config.service.ts | 8 ++-- packages/nodejs/src/flow-emulator.service.ts | 5 +- .../nodejs/src/processes/managed-process.ts | 8 ++-- .../ui/src/common/status/ErrorMessage.tsx | 4 +- .../AddressBuilder/AddressBuilder.tsx | 4 +- 8 files changed, 66 insertions(+), 30 deletions(-) diff --git a/apps/electron/src/renderer/components/DependencyErrors/DependencyErrors.tsx b/apps/electron/src/renderer/components/DependencyErrors/DependencyErrors.tsx index f7cab4a8..fdc14e14 100644 --- a/apps/electron/src/renderer/components/DependencyErrors/DependencyErrors.tsx +++ b/apps/electron/src/renderer/components/DependencyErrors/DependencyErrors.tsx @@ -1,12 +1,11 @@ -import React from 'react'; +import React, { ReactElement } from 'react'; import { ActionDialog } from '@onflowser/ui/src/common/overlays/dialogs/action/ActionDialog'; -import { ReactElement } from 'react'; +import useSWR, { SWRResponse } from 'swr'; import classes from './DependencyErrors.module.scss'; import { FlowserDependencyError, FlowserDependencyErrorType, } from '../../../services/types'; -import useSWR, { SWRResponse } from 'swr'; export function DependencyErrors(): ReactElement | null { const { data: errors } = useGetDependencyErrors(); @@ -29,8 +28,15 @@ export function DependencyErrors(): ReactElement | null { } function useGetDependencyErrors(): SWRResponse { - return useSWR(`dependency-errors`, () => - window.electron.app.listDependencyErrors(), + return useSWR( + `dependency-errors`, + () => window.electron.app.listDependencyErrors(), + { + // Let's assume that once the user resolves the errors, + // there will be no need to validate them again within the same app session. + // They will always be re-validated on the next app run. + refreshInterval: (data) => (data?.length === 0 ? 0 : 1000), + }, ); } diff --git a/apps/electron/src/services/flowser-app.service.ts b/apps/electron/src/services/flowser-app.service.ts index 31c2623a..f5f053fc 100644 --- a/apps/electron/src/services/flowser-app.service.ts +++ b/apps/electron/src/services/flowser-app.service.ts @@ -20,6 +20,7 @@ import { import path from 'path'; import crypto from 'crypto'; import { app, BrowserWindow, dialog } from 'electron'; +import { FlowserWorkspace } from '@onflowser/api'; import { WorkspaceEvent, WorkspaceService } from './workspace.service'; import { BlockchainIndexService } from './blockchain-index.service'; import { FileStorageService } from './file-storage.service'; @@ -177,6 +178,13 @@ export class FlowserAppService { 'Failed to close workspace', ).bind(this), ); + this.workspaceService.on( + WorkspaceEvent.WORKSPACE_UPDATE, + this.handleListenerError( + this.onWorkspaceUpdate.bind(this), + 'Failed to update workspace', + ).bind(this), + ); this.flowSnapshotsService.on( FlowSnapshotsEvent.ROLLBACK_TO_HEIGHT, this.handleListenerError( @@ -208,6 +216,7 @@ export class FlowserAppService { try { await listener(...args); } catch (error) { + this.logger.error(error); const result = await dialog.showMessageBox(this.window, { message: errorMessage, detail: isErrorWithMessage(error) ? error.message : undefined, @@ -241,6 +250,14 @@ export class FlowserAppService { await this.walletService.synchronizeIndex(); } + private async onWorkspaceUpdate(workspaceId: string) { + const workspace = await this.workspaceService.findByIdOrThrow(workspaceId); + + await this.flowEmulatorService.stopAndCleanup(); + + await this.startAndReindexEmulator(workspace); + } + private async onWorkspaceOpen(workspaceId: string) { const workspace = await this.workspaceService.findByIdOrThrow(workspaceId); @@ -248,29 +265,35 @@ export class FlowserAppService { workspacePath: workspace.filesystemPath, }); - this.flowGatewayService.configure({ - flowJSON: this.flowConfigService.getFlowJSON(), - restServerAddress: `http://localhost:${ - workspace.emulator?.restServerPort ?? 8888 - }`, - }); + // Separately store of each workspaces' data. + this.flowSnapshotsStorageService.setFileName( + `flowser-snapshots-${workspaceId}.json`, + ); + + this.walletStorageService.setFileName(`flowser-wallet-${workspaceId}.json`); this.processingScheduler.start(); + await this.startAndReindexEmulator(workspace); + } + private async startAndReindexEmulator(workspace: FlowserWorkspace) { if (workspace.emulator) { + // TODO: Sometimes when we restart the emulator, + // it complains that port 8080 is already taken. await this.flowEmulatorService.start({ workspacePath: workspace.filesystemPath, config: workspace.emulator, }); } - // Separately store of each workspaces' data. - this.flowSnapshotsStorageService.setFileName( - `flowser-snapshots-${workspaceId}.json`, - ); + this.flowGatewayService.configure({ + flowJSON: this.flowConfigService.getFlowJSON(), + restServerAddress: `http://localhost:${ + workspace.emulator?.restServerPort ?? 8888 + }`, + }); - this.walletStorageService.setFileName(`flowser-wallet-${workspaceId}.json`); - await this.walletService.synchronizeIndex(); + this.blockchainIndexService.clear(); if (workspace.emulator) { this.flowSnapshotsService.configure({ @@ -283,6 +306,7 @@ export class FlowserAppService { }); } + await this.walletService.synchronizeIndex(); await this.flowSnapshotsService.synchronizeIndex(); } diff --git a/apps/electron/src/services/workspace.service.ts b/apps/electron/src/services/workspace.service.ts index 9e07deed..4a11593e 100644 --- a/apps/electron/src/services/workspace.service.ts +++ b/apps/electron/src/services/workspace.service.ts @@ -7,6 +7,7 @@ import { PersistentStorage } from '@onflowser/core/src/persistent-storage'; export enum WorkspaceEvent { WORKSPACE_OPEN = 'WORKSPACE_OPEN', WORKSPACE_CLOSE = 'WORKSPACE_CLOSE', + WORKSPACE_UPDATE = 'WORKSPACE_UPDATE', } export class WorkspaceService extends EventEmitter { @@ -84,6 +85,8 @@ export class WorkspaceService extends EventEmitter { : existingWorkspace, ), ); + + this.emit(WorkspaceEvent.WORKSPACE_UPDATE, updatedWorkspace.id); } async findById(id: string): Promise { diff --git a/packages/nodejs/src/flow-config.service.ts b/packages/nodejs/src/flow-config.service.ts index d75921cd..3bb4784d 100644 --- a/packages/nodejs/src/flow-config.service.ts +++ b/packages/nodejs/src/flow-config.service.ts @@ -85,7 +85,7 @@ type FlowConfigServiceConfig = { }; export enum FlowConfigEvent { - FLOW_JSON_UPDATE = "FLOW_JSON_UPDATE" + FLOW_JSON_UPDATE = "FLOW_JSON_UPDATE", } export class FlowConfigService extends EventEmitter { @@ -206,9 +206,11 @@ export class FlowConfigService extends EventEmitter { // @ts-ignore AbortController type (because it's a polyfill) const watcher = watch(this.getConfigPath(), { signal }); for await (const event of watcher) { - this.logger.debug("Detected file change, reloading config from flow.json") + this.logger.debug( + "Detected file change, reloading config from flow.json", + ); await this.load(); - this.emit(FlowConfigEvent.FLOW_JSON_UPDATE) + this.emit(FlowConfigEvent.FLOW_JSON_UPDATE); } } catch (error) { if (isObject(error) && error["name"] !== "AbortError") { diff --git a/packages/nodejs/src/flow-emulator.service.ts b/packages/nodejs/src/flow-emulator.service.ts index 2de34a3f..0cdaed52 100644 --- a/packages/nodejs/src/flow-emulator.service.ts +++ b/packages/nodejs/src/flow-emulator.service.ts @@ -1,4 +1,7 @@ -import { ManagedProcess } from "./processes/managed-process"; +import { + ManagedProcess, + ManagedProcessOutput, +} from "./processes/managed-process"; import { FlowApiStatus, FlowGatewayService, diff --git a/packages/nodejs/src/processes/managed-process.ts b/packages/nodejs/src/processes/managed-process.ts index fb97e35d..51c5fb35 100644 --- a/packages/nodejs/src/processes/managed-process.ts +++ b/packages/nodejs/src/processes/managed-process.ts @@ -105,10 +105,13 @@ export class ManagedProcess extends EventEmitter { if (!this.childProcess) { return; } - const isKilledSuccessfully = this.childProcess.kill("SIGINT"); this.childProcess.once("error", (error) => { reject(error); }); + this.childProcess.once("exit", (exitCode) => { + resolve(exitCode); + }); + const isKilledSuccessfully = this.childProcess.kill("SIGINT"); if (!isKilledSuccessfully) { this.logger.debug( `Process ${this.name} (${this.id}) didn't shutdown given SIGINT`, @@ -116,9 +119,6 @@ export class ManagedProcess extends EventEmitter { // If the SIGINT signal doesn't work, force kill with SIGINT this.childProcess.kill("SIGKILL"); } - this.childProcess.once("exit", (exitCode) => { - resolve(exitCode); - }); // In the worst case, just inform the user that shutdown failed const rejectionTimeoutSec = 6; setTimeout(() => { diff --git a/packages/ui/src/common/status/ErrorMessage.tsx b/packages/ui/src/common/status/ErrorMessage.tsx index 9ca02226..04dfc39c 100644 --- a/packages/ui/src/common/status/ErrorMessage.tsx +++ b/packages/ui/src/common/status/ErrorMessage.tsx @@ -35,7 +35,5 @@ type TransactionErrorProps = { }; export function TransactionError(props: TransactionErrorProps): ReactElement { - return ( -
{props.errorMessage}
- ); + return
{props.errorMessage}
; } diff --git a/packages/ui/src/interactions/components/ValueBuilder/AddressBuilder/AddressBuilder.tsx b/packages/ui/src/interactions/components/ValueBuilder/AddressBuilder/AddressBuilder.tsx index 22e61083..4de4afaf 100644 --- a/packages/ui/src/interactions/components/ValueBuilder/AddressBuilder/AddressBuilder.tsx +++ b/packages/ui/src/interactions/components/ValueBuilder/AddressBuilder/AddressBuilder.tsx @@ -45,9 +45,9 @@ export function AddressBuilder(props: CadenceValueBuilder): ReactElement { function toggleOrSelect(address: string) { if (type.optional && value === address) { - setValue("") + setValue(""); } else { - setValue(address) + setValue(address); } }