diff --git a/app/App.svelte b/app/App.svelte index 8941be9..8b33ecb 100644 --- a/app/App.svelte +++ b/app/App.svelte @@ -1,20 +1,22 @@ @@ -60,5 +62,4 @@ .flex-expand { flex: 1 1 auto; } - diff --git a/app/commons/ipc/ipcChannelInterface.ts b/app/commons/ipc/ipcChannelInterface.ts index 8b5b68a..f73cd97 100644 --- a/app/commons/ipc/ipcChannelInterface.ts +++ b/app/commons/ipc/ipcChannelInterface.ts @@ -1,9 +1,11 @@ -import type { IpcMainEvent , IpcRendererEvent } from "electron"; +import type { IpcMainEvent, IpcRendererEvent } from "electron"; export enum IpcChannel { startServer = 'setProtoFiles', setProtoImportPaths = 'setProtoImportPaths', - onRequest= 'onRequest' + onRequest = 'onRequest', + onAppCloseRequest = 'onAppCloseRequest', + closeElectronApp = 'closeElectronApp' } export interface IpcRequest { diff --git a/app/commons/utils/util.ts b/app/commons/utils/util.ts index f765b8b..22bbb2c 100644 --- a/app/commons/utils/util.ts +++ b/app/commons/utils/util.ts @@ -3,18 +3,17 @@ import type { OpenDialogReturnValue } from "electron/main"; import { loadProtos, RpcProtoInfo } from "../../renderer/behaviour"; import { protoFilesStore, servicesStore } from "../../stores"; import faker, { random } from 'faker'; -import { appConfigStore } from "../../stores/tabStore"; import { get } from "svelte/store"; import type { IncomingRequest, TabConfigModel } from "../../renderer/components/types/types"; import { Metadata } from "@grpc/grpc-js"; import type { MethodPayload } from "bloomrpc-mock-js"; -import { randomInt } from "crypto"; +import { appConfigStore } from "../../stores/appConfigStore"; export class ProtoUtil { static async getMethodRpc(serviceName: string, methodName: string): Promise { const services = await servicesStore.getValue() return new Promise((res, rej) => { - const filteredServices = services.filter((service, index) => service.serviceName === serviceName) + const filteredServices = services.filter((service, index) => service.fullServiceName === serviceName) if (filteredServices.length == 0) { rej('could not find the service ' + serviceName) return @@ -62,7 +61,7 @@ export class TabUtil { const appConfig = get(appConfigStore) return appConfig.tabs.find((tabModel, index, allTabs) => { const rpc = tabModel.selectedRpc - return rpc?.serviceName == rpcProtoInfo.serviceName && rpc.methodName == rpcProtoInfo.methodName + return rpc?.fullServiceName == rpcProtoInfo.fullServiceName && rpc.methodName == rpcProtoInfo.methodName }) } } diff --git a/app/disk_storage/appDataDiskStorage.ts b/app/disk_storage/appDataDiskStorage.ts new file mode 100644 index 0000000..01584a7 --- /dev/null +++ b/app/disk_storage/appDataDiskStorage.ts @@ -0,0 +1,127 @@ +// @ts-ignore +import * as Store from "electron-store"; +import { get } from "svelte/store"; +import type { Certificate, RpcProtoInfo } from "../renderer/behaviour"; +import { EditorEventEmitter } from "../renderer/behaviour/responseStateController"; +import { AppConfigModel, EditorDataFlowMode, MockRpcEditorModel, MonitorConnectionStatus, RpcOperationMode, TabConfigModel } from "../renderer/components/types/types"; +import { protoFilesStore } from "../stores"; + + +export function getDefaultTabConfig(): TabConfigModel { + return ({ + id: '0', + selectedRpc: undefined, + targetGrpcServerUrl: '', + rpcOperationMode: RpcOperationMode.monitor, + monitorRequestEditorState: { + connectionStatus: MonitorConnectionStatus.waiting, + eventEmitter: new EditorEventEmitter(), dataFlowMode: EditorDataFlowMode.passThrough + }, + clientRequestEditorState: { text: '{}', metadata: '' }, + monitorResponseEditorState: { + connectionStatus: MonitorConnectionStatus.waiting, + eventEmitter: new EditorEventEmitter(), dataFlowMode: EditorDataFlowMode.passThrough + }, + clientResponseEditorState: { text: '', metadata: '' }, + mockRpcEditorState: { responseText: '{}' } + }); +} + +const appDataDiskStore = new Store({ + name: "appDataDiskStore", +}); + +const KEYS = { + tabs: 'tabs', + activeTabIndex: 'activeTabIndex', + defaultTargetServerUrl: 'defaultTargetServerUrl' +}; + +export abstract class AppDataDiskStore { + static storeAppData(appConfigModel: AppConfigModel) { + const serializedTabs = appConfigModel.tabs.map(tabModel => TabModelConverter.serializeModel(tabModel)) + console.log('seraizlied tabs = ', serializedTabs) + appDataDiskStore.set(KEYS.tabs, serializedTabs) + appDataDiskStore.set(KEYS.activeTabIndex, appConfigModel.activeTabIndex) + appDataDiskStore.set(KEYS.defaultTargetServerUrl, appConfigModel.defaultTargetServerUrl) + } + + static fetchAppData(): AppConfigModel { + const defaultTabConfig = getDefaultTabConfig(); + const tabsSavedData: SerializedTabConfigModel[] | undefined = appDataDiskStore.get(KEYS.tabs) + console.log('saved tabs = ', tabsSavedData) + return { + tabs: tabsSavedData !== undefined ? tabsSavedData.map(t => TabModelConverter.deserializeToModel(t)) : [defaultTabConfig], + activeTabIndex: appDataDiskStore.get(KEYS.activeTabIndex, 0), + defaultTargetServerUrl: appDataDiskStore.get(KEYS.defaultTargetServerUrl, defaultTabConfig.targetGrpcServerUrl) + } + } + + static clear() { + return appDataDiskStore.clear() + } +} + +interface SerializedTabConfigModel { + selectedProto: ({ + protoFileName?: string; + fullServiceName?: string; + methodName?: string; + }), + id: string; + targetGrpcServerUrl: string; + rpcOperationMode: RpcOperationMode; + // monitorRequestEditorState: MonitorRequestEditorModel; + // monitorResponseEditorState: MonitorResponseEditorModel; + tlsCertificate?: Certificate, + clientRequestEditorState: { + text: string; + metadata: string; + }; + clientResponseEditorState: { + text: string; + metadata: string; + }; + mockRpcEditorState: MockRpcEditorModel; +} + +abstract class TabModelConverter { + static serializeModel(tabConfigModel: TabConfigModel): SerializedTabConfigModel { + return { + id: tabConfigModel.id, + mockRpcEditorState: tabConfigModel.mockRpcEditorState, + rpcOperationMode: tabConfigModel.rpcOperationMode, + targetGrpcServerUrl: tabConfigModel.targetGrpcServerUrl, + tlsCertificate: tabConfigModel.tlsCertificate, + selectedProto: { + methodName: tabConfigModel.selectedRpc?.methodName, + fullServiceName: tabConfigModel.selectedRpc?.fullServiceName, + protoFileName: tabConfigModel.selectedRpc?.protoFileName, + }, + clientRequestEditorState: { ...tabConfigModel.clientRequestEditorState }, + clientResponseEditorState: { ...tabConfigModel.clientResponseEditorState }, + } + } + + static deserializeToModel(serializedTabConfig: SerializedTabConfigModel): TabConfigModel { + const selectedRpcInfo = serializedTabConfig.selectedProto + const allProtoFiles = get(protoFilesStore) + let services = allProtoFiles.find((file) => file.fileName === selectedRpcInfo.protoFileName)?.services + let selectedRpc: RpcProtoInfo | undefined + if (services !== undefined) { + selectedRpc = Object.values(services).find(s => s.fullServiceName === selectedRpcInfo.fullServiceName)?.methods[selectedRpcInfo.methodName!] + } + const defaultTabConfig = getDefaultTabConfig() + return { + ...defaultTabConfig, + selectedRpc, + clientRequestEditorState: serializedTabConfig.clientRequestEditorState, + clientResponseEditorState: serializedTabConfig.clientResponseEditorState, + id: serializedTabConfig.id, + mockRpcEditorState: serializedTabConfig.mockRpcEditorState, + rpcOperationMode: serializedTabConfig.rpcOperationMode, + targetGrpcServerUrl: serializedTabConfig.targetGrpcServerUrl, + tlsCertificate: serializedTabConfig.tlsCertificate, + } + } +} \ No newline at end of file diff --git a/app/disk_storage/index.ts b/app/disk_storage/index.ts index e3b150d..899dd2d 100644 --- a/app/disk_storage/index.ts +++ b/app/disk_storage/index.ts @@ -1,11 +1,14 @@ import { ProtoPathDiskStore } from "./importPaths"; import { ProtoFilesDiskStore } from "./protos"; import { TlsCertDiskStore } from "./certificates"; +import { AppDataDiskStore } from "./appDataDiskStorage"; export class ElectronDiskStorage { clearAllDiskStores() { - ProtoPathDiskStore.clear(); - ProtoFilesDiskStore.clear(); - TlsCertDiskStore.clear(); + ProtoPathDiskStore.clear() + ProtoFilesDiskStore.clear() + TlsCertDiskStore.clear() + TlsCertDiskStore.clear() + AppDataDiskStore.clear() } } \ No newline at end of file diff --git a/app/index.ts b/app/index.ts index 82dc6b1..fb9c506 100644 --- a/app/index.ts +++ b/app/index.ts @@ -1,6 +1,6 @@ import { ipcRenderer } from 'electron'; import App from './App.svelte'; -import { RequestHandlerChannel } from './renderer/ipc/ipcRendererChannels'; +import { AppQuitHandlerChannel, RequestHandlerChannel } from './renderer/ipc/ipcRendererChannels'; import type { IpcRendererChannelInterface } from './commons/ipc/ipcChannelInterface'; @@ -11,6 +11,6 @@ const app = new App({ function registerIpcChannels(ipcChannels: IpcRendererChannelInterface[]) { ipcChannels.forEach(channel => ipcRenderer.on(channel.getName(), (event, request) => channel.handle(event, request))); } -registerIpcChannels([new RequestHandlerChannel()]) +registerIpcChannels([new RequestHandlerChannel(), new AppQuitHandlerChannel()]) export default app; \ No newline at end of file diff --git a/app/main.dev.ts b/app/main.dev.ts index 4f3a8bc..ab09878 100644 --- a/app/main.dev.ts +++ b/app/main.dev.ts @@ -1,7 +1,7 @@ import { BrowserWindow } from "electron"; -import type { IpcMainChannelInterface } from "./commons/ipc/ipcChannelInterface"; +import { IpcChannel, IpcMainChannelInterface } from "./commons/ipc/ipcChannelInterface"; import { NetworkUtil } from "./commons/utils"; -import { StartServerChannel, SetProtoImportPathsChannel } from "./main_process/ipc/ipcMainChannels"; +import { StartServerChannel, SetProtoImportPathsChannel, CloseElectronAppChannel } from "./main_process/ipc/ipcMainChannels"; /** * This module executes inside of electron's main process. You can start @@ -17,93 +17,110 @@ const { ipcMain, } = require('electron'); -class Main { - private mainWindow: BrowserWindow | null = null; +const gotSingleInstanceLock = app.requestSingleInstanceLock() +let appReadyToQuit = false +if (!gotSingleInstanceLock) { + app.quit() +} + +let mainWindow: BrowserWindow | null = null; + - public getMainWindow(): BrowserWindow { - return this.mainWindow! +function init() { + if (process.env.NODE_ENV === 'production') { + const sourceMapSupport = require('source-map-support'); + sourceMapSupport.install(); } - public init(ipcChannels: IpcMainChannelInterface[]) { - if (process.env.NODE_ENV === 'production') { - const sourceMapSupport = require('source-map-support'); - sourceMapSupport.install(); - } + if ( + process.env.NODE_ENV === 'development' || + process.env.DEBUG_PROD === 'true' + ) { + require('electron-debug')(); + const path = require('path'); + const p = path.join(__dirname, '..', 'app', 'node_modules'); + require('module').globalPaths.push(p); + installExtensions(); + } - if ( - process.env.NODE_ENV === 'development' || - process.env.DEBUG_PROD === 'true' - ) { - require('electron-debug')(); - const path = require('path'); - const p = path.join(__dirname, '..', 'app', 'node_modules'); - require('module').globalPaths.push(p); - this.installExtensions(); + app.on('window-all-closed', onWindowAllClosed); + app.on('ready', createWindow); + app.on('before-quit', event => notifyRendererToCleanUp(event)) + app.on('second-instance', (event, commandLine, workingDirectory) => { + // Someone tried to run a second instance, we should focus our window. + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore() + mainWindow.focus() } + }) +} - app.on('window-all-closed', this.onWindowAllClosed); - app.on('ready', this.createWindow); - this.registerIpcChannels(ipcChannels) - } +function installExtensions(): Promise { + const installer = require('electron-devtools-installer'); + const forceDownload = !!process.env.UPGRADE_EXTENSIONS; + const extensions: any[] = []; + + return Promise.all( + extensions.map(name => installer.default(installer[name], forceDownload)) + ).catch(console.log); +} - private installExtensions(): Promise { - const installer = require('electron-devtools-installer'); - const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - const extensions: any[] = []; - return Promise.all( - extensions.map(name => installer.default(installer[name], forceDownload)) - ).catch(console.log); +function onWindowAllClosed() { + // Respect the OSX convention of having the application in memory even + // after all windows have been closed + if (process.platform !== 'darwin') { + app.quit(); } +} - private onWindowAllClosed() { - // Respect the OSX convention of having the application in memory even - // after all windows have been closed - if (process.platform !== 'darwin') { - app.quit(); +function createWindow() { + mainWindow = new BrowserWindow({ + show: false, + width: 1324, + height: 800, + backgroundColor: "#f0f2f5", + //@ts-ignore : webpack defined constant + title: `${__APP_DISPLAY_NAME__} @ ${NetworkUtil.getLocalIp()}:50051`, + webPreferences: { + nodeIntegration: true, + enableRemoteModule: true } - } + }); - private async createWindow() { - this.mainWindow = new BrowserWindow({ - show: false, - width: 1324, - height: 800, - backgroundColor: "#f0f2f5", - //@ts-ignore : webpack defined constant - title: `${__APP_DISPLAY_NAME__} @ ${NetworkUtil.getLocalIp()}:50051`, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true - } - }); - - this.mainWindow.loadURL(`file://${__dirname}/app.html`); - - // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event - this.mainWindow.once('ready-to-show', () => { - if (!this.mainWindow) { - throw new Error('"mainWindow" is not defined'); - } - - setTimeout(() => { - this.mainWindow?.show(); - this.mainWindow?.focus(); - }, 150); - }); - - this.mainWindow.on('closed', () => { - this.mainWindow = null; - }); + mainWindow.loadURL(`file://${__dirname}/app.html`); - } + // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event + mainWindow.once('ready-to-show', () => { + if (!mainWindow) { + throw new Error('"mainWindow" is not defined'); + } - private registerIpcChannels(ipcChannels: IpcMainChannelInterface[]) { - ipcChannels.forEach(channel => ipcMain.on(channel.getName(), (event, request) => channel.handle(event, request))); - } + setTimeout(() => { + mainWindow?.show(); + mainWindow?.focus(); + }, 150); + }); + + mainWindow.on('close', (event) => notifyRendererToCleanUp(event)) + mainWindow.on('closed', () => { + mainWindow = null; + }); + registerIpcChannels([new SetProtoImportPathsChannel(), new StartServerChannel(), new CloseElectronAppChannel()]) +} + +function registerIpcChannels(ipcChannels: IpcMainChannelInterface[]) { + ipcChannels.forEach(channel => ipcMain.on(channel.getName(), (event, request) => channel.handle(event, request))); +} + +function notifyRendererToCleanUp(event: Electron.Event) { + if (appReadyToQuit) return; + event.preventDefault() + mainWindow?.webContents.send(IpcChannel.onAppCloseRequest) + appReadyToQuit = true } -new Main().init([new SetProtoImportPathsChannel(), new StartServerChannel()]) \ No newline at end of file +init() \ No newline at end of file diff --git a/app/main_process/grpcServer.ts b/app/main_process/grpcServer.ts index 8a7c7ce..fc19d78 100644 --- a/app/main_process/grpcServer.ts +++ b/app/main_process/grpcServer.ts @@ -1,8 +1,6 @@ import * as grpc from '@grpc/grpc-js' -import { MainProcessAppConfigModel, mainProcessAppConfigStore } from '../stores'; -import type { ProtoService, ResponseError } from '../renderer/behaviour'; -import { responseInterceptor } from '../renderer/behaviour'; -import { ipcRenderer } from 'electron'; +import { mainProcessAppConfigStore } from '../stores'; +import type { ProtoService } from '../renderer/behaviour'; import { RendererProcessInterface } from './ipc/ipcRendererProcessInterface'; import { get } from 'svelte/store'; @@ -17,7 +15,7 @@ function addGrpcServices(server: grpc.Server | null, serviceProtos: ProtoService Object.entries(serviceProto.methods).forEach(([methodName, methodRpcInfo]) => { serviceImplementation[methodName] = (clientCall: any, callback: grpc.sendUnaryData) => { RendererProcessInterface.onRequest(clientCall.request, clientCall.metadata, - serviceProto.serviceName, methodRpcInfo.methodName).then((response) => { + serviceProto.fullServiceName, methodRpcInfo.methodName).then((response) => { console.log('reponse from renderer process : ') console.dir(response, { depth: null }) if (response.error === undefined) { diff --git a/app/main_process/ipc/ipcMainChannels.ts b/app/main_process/ipc/ipcMainChannels.ts index 1cf4b4d..ed302bd 100644 --- a/app/main_process/ipc/ipcMainChannels.ts +++ b/app/main_process/ipc/ipcMainChannels.ts @@ -4,6 +4,7 @@ import { IpcChannel, IpcMainChannelInterface, IpcRequest } from "../../commons/i import { protoFilesStore, protoImportPathsStore } from "../../stores"; import { startProxyGrpcServer } from "../grpcServer"; import { get } from "svelte/store"; +const { app } = require('electron'); export class StartServerChannel implements IpcMainChannelInterface { getName(): string { @@ -38,4 +39,18 @@ export class SetProtoImportPathsChannel implements IpcMainChannelInterface { protoImportPathsStore.setProtoImportPaths(folderPaths) event.sender.send(request.responseChannel, {}); } +} + + +export class CloseElectronAppChannel implements IpcMainChannelInterface { + getName(): string { + return IpcChannel.closeElectronApp + } + async handle(event: IpcMainEvent, request: IpcRequest): Promise { + if (!request.responseChannel) { + request.responseChannel = `${this.getName()}_response`; + } + event.sender.send(request.responseChannel, {}); + app.quit() + } } \ No newline at end of file diff --git a/app/renderer/behaviour/models/models.ts b/app/renderer/behaviour/models/models.ts index e6492f5..453d4b4 100644 --- a/app/renderer/behaviour/models/models.ts +++ b/app/renderer/behaviour/models/models.ts @@ -15,7 +15,7 @@ export interface ProtoServiceList { export interface ProtoService { proto: Proto, - serviceName: string, + fullServiceName: string, serviceDefinition: ServiceDefinition, requestMocks: ServiceMethodsPayload, responseMocks: ServiceMethodsPayload, diff --git a/app/renderer/behaviour/models/protoInfo.ts b/app/renderer/behaviour/models/protoInfo.ts index 1064c80..6751b95 100644 --- a/app/renderer/behaviour/models/protoInfo.ts +++ b/app/renderer/behaviour/models/protoInfo.ts @@ -15,11 +15,14 @@ export class RpcProtoInfo { mockResponsePayloadString: string; mockResponseTemplate: MethodPayload; client: any; - serviceName: string; + fullServiceName: string; + shortServiceName: string; + protoFileName: string; private serviceDef: Service; - constructor(service: ProtoService, methodName: string) { + constructor(protoFileName: string, service: ProtoService, methodName: string) { this.methodName = methodName; + this.protoFileName = protoFileName this.mockRequestTemplate = service.requestMocks[this.methodName]() this.mockResponseTemplate = service.responseMocks[this.methodName]() @@ -31,9 +34,10 @@ export class RpcProtoInfo { this.mockResponsePayload = { message: new Message(responsePayload), plain: responsePayload } this.mockResponsePayloadString = ProtoUtil.stringify(this.mockResponsePayload.plain) - this.client = lodashGet(service.proto.ast, service.serviceName); - this.serviceDef = service.proto.root.lookupService(service.serviceName); - this.serviceName = this.serviceDef.name + this.client = lodashGet(service.proto.ast, service.fullServiceName); + this.serviceDef = service.proto.root.lookupService(service.fullServiceName); + this.fullServiceName = service.fullServiceName + this.shortServiceName = this.serviceDef.name } methodDef() { diff --git a/app/renderer/behaviour/protoImporter.ts b/app/renderer/behaviour/protoImporter.ts index 5a1098b..e26b818 100644 --- a/app/renderer/behaviour/protoImporter.ts +++ b/app/renderer/behaviour/protoImporter.ts @@ -1,4 +1,3 @@ -import { remote } from 'electron'; import { fromFileName, mockRequestMethods, mockResponseMethods, Proto, walkServices } from 'bloomrpc-mock-js'; import * as path from "path"; import type { ProtoFile, ProtoService } from './models/models'; @@ -72,7 +71,7 @@ function parseServices(proto: Proto) { const serviceDefinition = serviceClientImpl.service const serviceObject: ProtoService = { - serviceName: serviceName, + fullServiceName: serviceName, proto, serviceDefinition: serviceDefinition, requestMocks: requestMocks, @@ -82,7 +81,7 @@ function parseServices(proto: Proto) { const serviceMethods: { [key: string]: RpcProtoInfo } = {} Object.keys(requestMocks) - .forEach(methodName => serviceMethods[methodName] = new RpcProtoInfo(serviceObject, methodName)) + .forEach(methodName => serviceMethods[methodName] = new RpcProtoInfo(proto.fileName, serviceObject, methodName)) serviceObject.methods = serviceMethods services[serviceName] = serviceObject }); diff --git a/app/renderer/components/TapRpc.svelte b/app/renderer/components/TapRpc.svelte index 64ab45c..4d0fed5 100644 --- a/app/renderer/components/TapRpc.svelte +++ b/app/renderer/components/TapRpc.svelte @@ -11,13 +11,10 @@ Button, Icon, } from 'svelte-materialify/src' - import { - activeTabConfigStore, - appConfigStore, - } from '../../stores/tabStore' - import { onMount, tick } from 'svelte' + import { onMount} from 'svelte' import TabCloseButton from '../pages/tabPage/components/TabCloseButton.svelte' import type { TabConfigModel } from './types/types'; + import { appConfigStore } from '../../stores/appConfigStore'; $: tabs = $appConfigStore.tabs @@ -80,5 +77,4 @@ .new-tab-button { transform: translate(10px, 10px); } - diff --git a/app/renderer/components/rpcSelector/RpcSelector.svelte b/app/renderer/components/rpcSelector/RpcSelector.svelte index 79ff13a..583b183 100644 --- a/app/renderer/components/rpcSelector/RpcSelector.svelte +++ b/app/renderer/components/rpcSelector/RpcSelector.svelte @@ -26,7 +26,7 @@ let results: RpcSelectorFileType[] = [] services.forEach((service) => { results.push({ - name: service.serviceName, + name: service.fullServiceName, type: 'folder', files: getServiceMethods(service), }) @@ -50,8 +50,10 @@ return results } - const onRpcClick = (protoInfo: RpcProtoInfo) => { - activeTabConfigStore.setSelectedRpc(protoInfo) + const onRpcClick = (selectedRpc: RpcSelectorFileType) => { + if(selectedRpc.protoInfo!==undefined){ + activeTabConfigStore.setSelectedRpc(selectedRpc.protoInfo) + } } diff --git a/app/renderer/components/rpcSelector/components/File.svelte b/app/renderer/components/rpcSelector/components/File.svelte index 38a51ae..6d6bf8e 100644 --- a/app/renderer/components/rpcSelector/components/File.svelte +++ b/app/renderer/components/rpcSelector/components/File.svelte @@ -1,6 +1,5 @@ () + const dispatch = createEventDispatcher<{ fileClick: RpcSelectorFileType}>() {name} @@ -26,7 +26,7 @@ {:else} dispatch("fileClick", file.protoInfo)} + on:click={e => dispatch("fileClick", file)} /> {/if} diff --git a/app/renderer/components/rpcSelector/components/ServicePanels.svelte b/app/renderer/components/rpcSelector/components/ServicePanels.svelte deleted file mode 100644 index 109f5ea..0000000 --- a/app/renderer/components/rpcSelector/components/ServicePanels.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - - - {#each protoServices as protoService (protoService['serviceName'])} - - - - - {protoService['serviceName']} - - - - {#each getServiceMethods(protoService) as rpcMethod (rpcMethod)} - onRpcClick(protoService, rpcMethod)}> - {rpcMethod} - - {/each} - - {/each} - diff --git a/app/renderer/ipc/ipcMainProcessInterface.ts b/app/renderer/ipc/ipcMainProcessInterface.ts index c72ad34..1c132b0 100644 --- a/app/renderer/ipc/ipcMainProcessInterface.ts +++ b/app/renderer/ipc/ipcMainProcessInterface.ts @@ -17,4 +17,8 @@ export class MainProcessInterface { } }) } + + static closeElectronApp(): Promise { + return IpcMainService.send(IpcChannel.closeElectronApp, {}) + } } diff --git a/app/renderer/ipc/ipcRendererChannels.ts b/app/renderer/ipc/ipcRendererChannels.ts index 36a1a93..00f4532 100644 --- a/app/renderer/ipc/ipcRendererChannels.ts +++ b/app/renderer/ipc/ipcRendererChannels.ts @@ -8,7 +8,9 @@ import { activeTabConfigStore } from "../../stores"; import { GrpcClientManager } from "../behaviour/grpcClientManager"; import { get } from "svelte/store"; import { IncomingRequest, MonitorConnectionStatus, RpcOperationMode } from "../components/types/types"; -import { appConfigStore } from "../../stores/tabStore"; +import { appConfigStore } from "../../stores/appConfigStore"; +import { AppDataDiskStore } from "../../disk_storage/appDataDiskStorage"; +import { MainProcessInterface } from "./ipcMainProcessInterface"; export class RequestHandlerChannel implements IpcRendererChannelInterface { getName(): string { @@ -114,3 +116,14 @@ export class RequestHandlerChannel implements IpcRendererChannelInterface { } } + +export class AppQuitHandlerChannel implements IpcRendererChannelInterface { + getName(): string { + return IpcChannel.onAppCloseRequest + } + + handle(event: Electron.IpcRendererEvent, request: IpcRequest): void { + AppDataDiskStore.storeAppData(get(appConfigStore)) + MainProcessInterface.closeElectronApp() + } +} \ No newline at end of file diff --git a/app/renderer/pages/tabPage/MainTabContent.svelte b/app/renderer/pages/tabPage/MainTabContent.svelte new file mode 100644 index 0000000..e5ef22e --- /dev/null +++ b/app/renderer/pages/tabPage/MainTabContent.svelte @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/app/renderer/pages/tabPage/MainTabPage.svelte b/app/renderer/pages/tabPage/MainTabPage.svelte index a72ebdc..a66871a 100644 --- a/app/renderer/pages/tabPage/MainTabPage.svelte +++ b/app/renderer/pages/tabPage/MainTabPage.svelte @@ -8,6 +8,9 @@ import MockRpcModePage from './mockRpcModePage/MockRpcModePage.svelte' import { Tabs, TabContent, Tab } from 'svelte-materialify/src' import { RpcOperationMode } from '../../components/types/types'; + import { onMount } from 'svelte'; + import { appConfigStore } from '../../../stores/appConfigStore'; +import MainTabContent from './MainTabContent.svelte' const allModes = Object.values(RpcOperationMode) console.log( @@ -19,6 +22,7 @@ console.log('new mode = ', newMode) activeTabConfigStore.setRpcOperationMode(newMode) } + {#if $activeTabConfigStore.selectedRpc} @@ -37,15 +41,7 @@ {/each}
- - - - - - - - - +
diff --git a/app/renderer/pages/tabPage/initializerPage/TabInitializerPage.svelte b/app/renderer/pages/tabPage/initializerPage/TabInitializerPage.svelte index 0eda820..ea52d08 100644 --- a/app/renderer/pages/tabPage/initializerPage/TabInitializerPage.svelte +++ b/app/renderer/pages/tabPage/initializerPage/TabInitializerPage.svelte @@ -1,11 +1,7 @@ + onTargetServerChanged(e.target)} diff --git a/app/renderer/pages/tabPage/mockRpcModePage/MockRpcModePage.svelte b/app/renderer/pages/tabPage/mockRpcModePage/MockRpcModePage.svelte index 6de81a4..3075222 100644 --- a/app/renderer/pages/tabPage/mockRpcModePage/MockRpcModePage.svelte +++ b/app/renderer/pages/tabPage/mockRpcModePage/MockRpcModePage.svelte @@ -1,12 +1,9 @@
@@ -27,5 +24,4 @@ display: flex; flex-flow: column; } - diff --git a/app/renderer/pages/tabPage/monitorModePage/MonitorModePage.svelte b/app/renderer/pages/tabPage/monitorModePage/MonitorModePage.svelte index 2c11809..c88880c 100644 --- a/app/renderer/pages/tabPage/monitorModePage/MonitorModePage.svelte +++ b/app/renderer/pages/tabPage/monitorModePage/MonitorModePage.svelte @@ -1,10 +1,8 @@
@@ -26,5 +24,4 @@ display: flex; flex-flow: column; } - diff --git a/app/stores/appConfigStore.ts b/app/stores/appConfigStore.ts index 6c2f72d..1b44816 100644 --- a/app/stores/appConfigStore.ts +++ b/app/stores/appConfigStore.ts @@ -1,37 +1,128 @@ -import type { Server } from "@grpc/grpc-js"; -import { writable } from "svelte/store"; -export interface MainProcessAppConfigModel { - proxyGrpcServerUrl: string; - proxyGrpcServer: Server | null; - testGrpcServer: Server | null; - testGrpcServerUrl: string; -} +import { derived, writable } from "svelte/store"; +import type { Certificate, RpcProtoInfo } from "../renderer/behaviour"; +import immer from "immer"; +import { AppDataDiskStore, getDefaultTabConfig } from "../disk_storage/appDataDiskStorage"; +import type { AppConfigModel, TabConfigModel, RpcOperationMode, MonitorRequestEditorModel, MonitorResponseEditorModel, ClientEditorModel, MockRpcEditorModel } from "../renderer/components/types/types"; -export interface RequestResponseEditorModel { - requestText: string; - responseText: string; -} function createAppConfigStore() { - const { set, subscribe, update } = writable({ - proxyGrpcServerUrl: '0.0.0.0:50051', - proxyGrpcServer: null, - testGrpcServer: null, - testGrpcServerUrl: '0.0.0.0:9090', - }); + const { set, subscribe, update } = writable(AppDataDiskStore.fetchAppData()); + return { + subscribe, + setActiveTab: (index: number) => update((store) => ({ ...store, activeTabIndex: index })), + setValue: async (appConfigModel: AppConfigModel) => set(appConfigModel), + setDefaultTargetServerUrl: (targetServer: string) => update(store => { + return ({ ...store, defaultTargetServerUrl: targetServer }); + }), + setTabValue: (newTabConfig: TabConfigModel, tabId: string) => update((store) => { + return immer(store, (draftStore) => { + for (let [index, tabConfig] of draftStore.tabs.entries()) { + if (tabConfig.id === tabId) { + draftStore.tabs[index] = newTabConfig; + } + } + }) + }), + setActiveTabSelectedRpc: (rpcInfo: RpcProtoInfo) => update((config) => { + const activeTab = config.tabs[config.activeTabIndex] + const allTabs = Array.from(config.tabs) + allTabs[config.activeTabIndex] = { ...activeTab, selectedRpc: rpcInfo } + return { ...config, tabs: allTabs } + }), + setActiveTabTargetGrpcServerUrl: (url: string) => update((config) => { + const activeTab = config.tabs[config.activeTabIndex] + const allTabs = Array.from(config.tabs) + allTabs[config.activeTabIndex] = { ...activeTab, targetGrpcServerUrl: url } + return { ...config, tabs: allTabs } + }), + setActiveTabRpcOperationMode: (mode: RpcOperationMode) => update(config => { + const activeTab = config.tabs[config.activeTabIndex] + const allTabs = Array.from(config.tabs) + allTabs[config.activeTabIndex] = { ...activeTab, rpcOperationMode: mode } + return { ...config, tabs: allTabs } + }), + setActiveTabMonitorRequestEditorState: (requestEditorModel: MonitorRequestEditorModel) => update((config) => { + const activeTab = config.tabs[config.activeTabIndex] + const allTabs = Array.from(config.tabs) + allTabs[config.activeTabIndex] = { ...activeTab, monitorRequestEditorState: requestEditorModel } + return { ...config, tabs: allTabs } + }), + setActiveTabMonitorResponseEditorState: (responseEditorModel: MonitorResponseEditorModel) => update((config) => { + const activeTab = config.tabs[config.activeTabIndex] + const allTabs = Array.from(config.tabs) + allTabs[config.activeTabIndex] = { ...activeTab, monitorResponseEditorState: responseEditorModel } + return { ...config, tabs: allTabs } + }), + setActiveTabClientRequestEditorState: (requestEditorModel: ClientEditorModel) => update((config) => { + const activeTab = config.tabs[config.activeTabIndex] + const allTabs = Array.from(config.tabs) + allTabs[config.activeTabIndex] = { ...activeTab, clientRequestEditorState: requestEditorModel } + return { ...config, tabs: allTabs } + }), + setActiveTabClientResponseEditorState: (responseEditorModel: ClientEditorModel) => update((config) => { + const activeTab = config.tabs[config.activeTabIndex] + const allTabs = Array.from(config.tabs) + allTabs[config.activeTabIndex] = { ...activeTab, clientResponseEditorState: responseEditorModel } + return { ...config, tabs: allTabs } + }), + setActiveTabMockRpcEditorModel: (editorModel: MockRpcEditorModel) => update((config) => { + const activeTab = config.tabs[config.activeTabIndex] + const allTabs = Array.from(config.tabs) + allTabs[config.activeTabIndex] = { ...activeTab, mockRpcEditorState: editorModel } + return { ...config, tabs: allTabs } + }), + addNewTab: () => update((config) => { + const allTabs = Array.from(config.tabs) + const newTabConfig = getDefaultTabConfig() + if (allTabs.length > 0) { + newTabConfig.clientRequestEditorState.metadata = allTabs[0].clientRequestEditorState.metadata + } + const newTab = { ...newTabConfig, id: allTabs.length.toString() } + allTabs.push(newTab) + return { ...config, tabs: allTabs, activeTabIndex: allTabs.length - 1 } + }), + setTlsCertificate: (tlsCertificate: Certificate | undefined) => update((config) => { + const allTabs = Array.from(config.tabs) + const activeTab = config.tabs[config.activeTabIndex] + allTabs[config.activeTabIndex] = { ...activeTab, tlsCertificate } + return { ...config, tabs: allTabs } + }), + removeTab: (index: number) => update((config) => { + const allTabs = Array.from(config.tabs) + let newActiveTab = config.activeTabIndex + if (allTabs.length == 1) return config + allTabs.splice(index, 1) + if (config.activeTabIndex >= allTabs.length) newActiveTab = config.activeTabIndex--; + return { ...config, tabs: allTabs } + }) + }; +} + +export const appConfigStore = createAppConfigStore() + +function createActiveTabConfigStore() { + const { subscribe } = derived(appConfigStore, (configStore) => { + return configStore.tabs[configStore.activeTabIndex] + }) - return { - subscribe, - setConfig: (config: MainProcessAppConfigModel) => set(config), - setProxyGrpcServerUrl: (url: string) => update((config) => ({ ...config, proxyGrpcServerUrl: url })), - setProxyGrpcServer: (server: Server) => update((config) => { - return ({ ...config, proxyGrpcServer: server }); - }), - setTestGrpcServer: (server: Server) => update((config) => { - return ({ ...config, testGrpcServer: server }); - }), - }; + return { + subscribe, + setSelectedRpc: (rpcInfo: RpcProtoInfo) => { + console.log("Seleted RPC : ", rpcInfo.methodName) + return appConfigStore.setActiveTabSelectedRpc(rpcInfo); + }, + setTargetGrpcServerUrl: (url: string) => appConfigStore.setActiveTabTargetGrpcServerUrl(url), + setRpcOperationMode: async (mode: RpcOperationMode) => { + appConfigStore.setActiveTabRpcOperationMode(mode); + }, + setMonitorRequestEditorState: (editorModel: MonitorRequestEditorModel) => appConfigStore.setActiveTabMonitorRequestEditorState(editorModel), + setTlsCertificate: (tlsCertificate: Certificate | undefined) => appConfigStore.setTlsCertificate(tlsCertificate), + setMonitorResponseEditorState: (editorModel: MonitorResponseEditorModel) => appConfigStore.setActiveTabMonitorResponseEditorState(editorModel), + setClientRequestEditorState: (editorModel: ClientEditorModel) => appConfigStore.setActiveTabClientRequestEditorState(editorModel), + setClientResponseEditorState: (editorModel: ClientEditorModel) => appConfigStore.setActiveTabClientResponseEditorState(editorModel), + setMockRpcEditorState: (editorModel: MockRpcEditorModel) => appConfigStore.setActiveTabMockRpcEditorModel(editorModel), + }; } -export const mainProcessAppConfigStore = createAppConfigStore(); +export const activeTabConfigStore = createActiveTabConfigStore(); \ No newline at end of file diff --git a/app/stores/index.ts b/app/stores/index.ts index b9deaea..4cdbb97 100644 --- a/app/stores/index.ts +++ b/app/stores/index.ts @@ -1,6 +1,6 @@ -export { mainProcessAppConfigStore } from "./appConfigStore"; -export type { MainProcessAppConfigModel, RequestResponseEditorModel } from "./appConfigStore"; +export { mainProcessAppConfigStore } from "./mainProcessAppConfigStore"; +export type { MainProcessAppConfigModel, RequestResponseEditorModel } from "./mainProcessAppConfigStore"; export { protoFilesStore, protoImportPathsStore } from "./protoFiles"; -export { activeTabConfigStore } from "./tabStore"; +export { activeTabConfigStore ,appConfigStore} from "./appConfigStore"; export { rpcProtoInfosStore, servicesStore } from "./protoInfo"; diff --git a/app/stores/mainProcessAppConfigStore.ts b/app/stores/mainProcessAppConfigStore.ts new file mode 100644 index 0000000..733bba6 --- /dev/null +++ b/app/stores/mainProcessAppConfigStore.ts @@ -0,0 +1,37 @@ +import type { Server } from "@grpc/grpc-js"; +import { writable } from "svelte/store"; +export interface MainProcessAppConfigModel { + proxyGrpcServerUrl: string; + proxyGrpcServer: Server | null; + testGrpcServer: Server | null; + testGrpcServerUrl: string; +} + +export interface RequestResponseEditorModel { + requestText: string; + responseText: string; +} + +function createMainProcessAppConfigStore() { + const { set, subscribe, update } = writable({ + proxyGrpcServerUrl: '0.0.0.0:50051', + proxyGrpcServer: null, + testGrpcServer: null, + testGrpcServerUrl: '0.0.0.0:9090', + }); + + return { + subscribe, + setConfig: (config: MainProcessAppConfigModel) => set(config), + setProxyGrpcServerUrl: (url: string) => update((config) => ({ ...config, proxyGrpcServerUrl: url })), + setProxyGrpcServer: (server: Server) => update((config) => { + return ({ ...config, proxyGrpcServer: server }); + }), + setTestGrpcServer: (server: Server) => update((config) => { + return ({ ...config, testGrpcServer: server }); + }), + }; +} + + +export const mainProcessAppConfigStore = createMainProcessAppConfigStore(); diff --git a/app/stores/tabStore.ts b/app/stores/tabStore.ts deleted file mode 100644 index be82d42..0000000 --- a/app/stores/tabStore.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { derived, writable } from "svelte/store"; -import type { Certificate, RpcProtoInfo } from "../renderer/behaviour"; -import { EditorEventEmitter } from "../renderer/behaviour/responseStateController"; -import { ClientEditorModel, EditorDataFlowMode, MonitorConnectionStatus, MonitorRequestEditorModel, MonitorResponseEditorModel, RpcOperationMode, TabConfigModel, AppConfigModel, MockRpcEditorModel, } from "../renderer/components/types/types"; -import immer from "immer"; - - -function getDefaultTabConfig(): TabConfigModel { - return ({ - id: '0', - selectedRpc: undefined, - targetGrpcServerUrl: 'localhost:9090', - rpcOperationMode: RpcOperationMode.monitor, - monitorRequestEditorState: { - connectionStatus: MonitorConnectionStatus.waiting, - eventEmitter: new EditorEventEmitter(), dataFlowMode: EditorDataFlowMode.passThrough - }, - clientRequestEditorState: { text: '{}', metadata: '' }, - monitorResponseEditorState: { - connectionStatus: MonitorConnectionStatus.waiting, - eventEmitter: new EditorEventEmitter(), dataFlowMode: EditorDataFlowMode.passThrough - }, - clientResponseEditorState: { text: '', metadata: '' }, - mockRpcEditorState: { responseText: '{}' } - }); -} - -function createAppConfigStore() { - const defaultTabConfig = getDefaultTabConfig(); - const { set, subscribe, update } = writable({ - activeTabIndex: 0, - tabs: [defaultTabConfig], - defaultTargetServerUrl: defaultTabConfig.targetGrpcServerUrl, - }); - return { - subscribe, - setActiveTab: (index: number) => update((store) => ({ ...store, activeTabIndex: index })), - setValue: async (tabConfigListModel: AppConfigModel) => set(tabConfigListModel), - setDefaultTargetServerUrl: (targetServer: string) => update(store => { - const tabs = store.tabs - if (tabs.length == 1) { - tabs[0].targetGrpcServerUrl = targetServer - } - return ({ ...store, defaultTargetServerUrl: targetServer }); - }), - setTabValue: (newTabConfig: TabConfigModel, tabId: string) => update((store) => { - return immer(store, (draftStore) => { - for (let [index, tabConfig] of draftStore.tabs.entries()) { - if (tabConfig.id === tabId) { - draftStore.tabs[index] = newTabConfig; - } - } - }) - }), - setActiveTabSelectedRpc: (rpcInfo: RpcProtoInfo) => update((config) => { - const activeTab = config.tabs[config.activeTabIndex] - const allTabs = Array.from(config.tabs) - allTabs[config.activeTabIndex] = { ...activeTab, selectedRpc: rpcInfo } - return { ...config, tabs: allTabs } - }), - setActiveTabTargetGrpcServerUrl: (url: string) => update((config) => { - const activeTab = config.tabs[config.activeTabIndex] - const allTabs = Array.from(config.tabs) - allTabs[config.activeTabIndex] = { ...activeTab, targetGrpcServerUrl: url } - return { ...config, tabs: allTabs } - }), - setActiveTabRpcOperationMode: (mode: RpcOperationMode) => update(config => { - const activeTab = config.tabs[config.activeTabIndex] - const allTabs = Array.from(config.tabs) - allTabs[config.activeTabIndex] = { ...activeTab, rpcOperationMode: mode } - return { ...config, tabs: allTabs } - }), - setActiveTabMonitorRequestEditorState: (requestEditorModel: MonitorRequestEditorModel) => update((config) => { - const activeTab = config.tabs[config.activeTabIndex] - const allTabs = Array.from(config.tabs) - allTabs[config.activeTabIndex] = { ...activeTab, monitorRequestEditorState: requestEditorModel } - return { ...config, tabs: allTabs } - }), - setActiveTabMonitorResponseEditorState: (responseEditorModel: MonitorResponseEditorModel) => update((config) => { - const activeTab = config.tabs[config.activeTabIndex] - const allTabs = Array.from(config.tabs) - allTabs[config.activeTabIndex] = { ...activeTab, monitorResponseEditorState: responseEditorModel } - return { ...config, tabs: allTabs } - }), - setActiveTabClientRequestEditorState: (requestEditorModel: ClientEditorModel) => update((config) => { - const activeTab = config.tabs[config.activeTabIndex] - const allTabs = Array.from(config.tabs) - allTabs[config.activeTabIndex] = { ...activeTab, clientRequestEditorState: requestEditorModel } - return { ...config, tabs: allTabs } - }), - setActiveTabClientResponseEditorState: (responseEditorModel: ClientEditorModel) => update((config) => { - const activeTab = config.tabs[config.activeTabIndex] - const allTabs = Array.from(config.tabs) - allTabs[config.activeTabIndex] = { ...activeTab, clientResponseEditorState: responseEditorModel } - return { ...config, tabs: allTabs } - }), - setActiveTabMockRpcEditorModel: (editorModel: MockRpcEditorModel) => update((config) => { - const activeTab = config.tabs[config.activeTabIndex] - const allTabs = Array.from(config.tabs) - allTabs[config.activeTabIndex] = { ...activeTab, mockRpcEditorState: editorModel } - return { ...config, tabs: allTabs } - }), - addNewTab: () => update((config) => { - const allTabs = Array.from(config.tabs) - const newTabConfig = getDefaultTabConfig() - newTabConfig.targetGrpcServerUrl = config.defaultTargetServerUrl - if (allTabs.length > 0) { - newTabConfig.clientRequestEditorState.metadata = allTabs[0].clientRequestEditorState.metadata - } - const newTab = { ...newTabConfig, id: allTabs.length.toString() } - allTabs.push(newTab) - return { ...config, tabs: allTabs, activeTabIndex: allTabs.length - 1 } - }), - setTlsCertificate: (tlsCertificate: Certificate | undefined) => update((config) => { - const allTabs = Array.from(config.tabs) - const activeTab = config.tabs[config.activeTabIndex] - allTabs[config.activeTabIndex] = { ...activeTab, tlsCertificate } - return { ...config, tabs: allTabs } - }), - removeTab: (index: number) => update((config) => { - const allTabs = Array.from(config.tabs) - let newActiveTab = config.activeTabIndex - if (allTabs.length == 1) return config - allTabs.splice(index, 1) - if (config.activeTabIndex >= allTabs.length) newActiveTab = config.activeTabIndex--; - return { ...config, tabs: allTabs } - }) - }; -} - -export const appConfigStore = createAppConfigStore() - -function createActiveTabConfigStore() { - const { subscribe } = derived(appConfigStore, (configStore) => { - return configStore.tabs[configStore.activeTabIndex] - }) - - return { - subscribe, - setSelectedRpc: (rpcInfo: RpcProtoInfo) => { - console.log("Seleted RPC : ", rpcInfo.methodName) - return appConfigStore.setActiveTabSelectedRpc(rpcInfo); - }, - setTargetGrpcServerUrl: (url: string) => appConfigStore.setActiveTabTargetGrpcServerUrl(url), - setRpcOperationMode: async (mode: RpcOperationMode) => { - appConfigStore.setActiveTabRpcOperationMode(mode); - }, - setMonitorRequestEditorState: (editorModel: MonitorRequestEditorModel) => appConfigStore.setActiveTabMonitorRequestEditorState(editorModel), - setTlsCertificate: (tlsCertificate: Certificate | undefined) => appConfigStore.setTlsCertificate(tlsCertificate), - setMonitorResponseEditorState: (editorModel: MonitorResponseEditorModel) => appConfigStore.setActiveTabMonitorResponseEditorState(editorModel), - setClientRequestEditorState: (editorModel: ClientEditorModel) => appConfigStore.setActiveTabClientRequestEditorState(editorModel), - setClientResponseEditorState: (editorModel: ClientEditorModel) => appConfigStore.setActiveTabClientResponseEditorState(editorModel), - setMockRpcEditorState: (editorModel: MockRpcEditorModel) => appConfigStore.setActiveTabMockRpcEditorModel(editorModel), - }; -} - - -export const activeTabConfigStore = createActiveTabConfigStore(); \ No newline at end of file