electron#IpcRendererEvent TypeScript Examples

The following examples show how to use electron#IpcRendererEvent. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: EmuToMainMessenger.ts    From kliveide with MIT License 6 votes vote down vote up
/**
   * Initializes the listener that processes responses
   */
  constructor() {
    super();
      ipcRenderer?.on(
        this.responseChannel,
        (_ev: IpcRendererEvent, response: ResponseMessage) =>
          this.processResponse(response)
      );
  }
Example #2
Source File: rendererMenuItemProxy.ts    From TidGi-Desktop with Mozilla Public License 2.0 6 votes vote down vote up
/**
 * Iterate through the object, replace each object method with a random uuid, and send this object without a callback to the main thread.
 * Register IPCRenderer.on(uuid for each object method
 * @returns unregister function
 */
export function rendererMenuItemProxy(menus: MenuItemConstructorOptions[]): [IpcSafeMenuItem[], () => void] {
  const originalCallbackIdMap: Record<string, () => void> = {};
  const ipcCallbackIdMap: Record<string, (_event: IpcRendererEvent) => void> = {};
  const unregister = (): void => {
    Object.keys(originalCallbackIdMap).forEach((id) => {
      ipcRenderer.removeListener(id, ipcCallbackIdMap[id]);
      delete originalCallbackIdMap[id];
      delete ipcCallbackIdMap[id];
    });
  };
  const newMenus: IpcSafeMenuItem[] = [];
  for (const menuItem of menus) {
    if (menuItem.click !== undefined) {
      const id = uuid();
      // store callback into map, and use id instead. And we ipc.on that id.
      originalCallbackIdMap[id] = menuItem.click as () => void;
      const ipcCallback = (_event: IpcRendererEvent): void => {
        originalCallbackIdMap[id]?.();
        unregister();
      };
      ipcCallbackIdMap[id] = ipcCallback;
      ipcRenderer.on(id, ipcCallback);
      newMenus.push({
        ...menuItem,
        click: id,
      });
    }
  }
  return [newMenus, unregister];
}
Example #3
Source File: preloadBindings.ts    From TidGi-Desktop with Mozilla Public License 2.0 6 votes vote down vote up
preloadBindings = function (ipcRenderer: IpcRenderer): {
  onLanguageChange: (callback: (language: { lng: string }) => unknown) => void;
  onReceive: (channel: I18NChannels, callback: (readWriteFileArgs: IReadWriteFileRequest) => void) => void;
  send: (channel: I18NChannels, readWriteFileArgs: IReadWriteFileRequest) => Promise<void>;
} {
  return {
    send: async (channel: I18NChannels, readWriteFileArgs: IReadWriteFileRequest): Promise<void> => {
      const validChannels = [I18NChannels.readFileRequest, I18NChannels.writeFileRequest];
      if (validChannels.includes(channel)) {
        await ipcRenderer.invoke(channel, readWriteFileArgs);
      }
    },
    onReceive: (channel: I18NChannels, callback: (readWriteFileArgs: IReadWriteFileRequest) => void) => {
      const validChannels = [I18NChannels.readFileResponse, I18NChannels.writeFileResponse];
      if (validChannels.includes(channel)) {
        // Deliberately strip event as it includes "sender"
        ipcRenderer.on(channel, (_event: IpcRendererEvent, arguments_: IReadWriteFileRequest) => callback(arguments_));
      }
    },
    onLanguageChange: (callback: (language: { lng: string }) => unknown) => {
      // Deliberately strip event as it includes "sender"
      ipcRenderer.on(I18NChannels.changeLanguageRequest, (_event: IpcRendererEvent, language: { lng: string }) => {
        callback(language);
      });
    },
  };
}
Example #4
Source File: FeedbackModal.tsx    From SeeQR with MIT License 6 votes vote down vote up
FeedbackModal = () => {
  const [isOpen, setOpen] = useState(false);
  const [message, setMessage] = useState('');
  const [severity, setSeverity] = useState<FeedbackSeverity>('info');

  useEffect(() => {
    const receiveFeedback = (evt: IpcRendererEvent, feedback: Feedback) => {
      const validTypes: FeedbackSeverity[] = ['success','error', 'info', 'warning'];
      // Ignore 'success' feedback.
      if (validTypes.includes(feedback.type)) {
        setSeverity(feedback.type);
        setMessage(feedback.message?.toString() ?? 'ERROR: Operation Failed');
        setOpen(true);
      }
    };
    ipcRenderer.on('feedback', receiveFeedback);
    return () => {
      ipcRenderer.removeListener('feedback', receiveFeedback);
    };
  });

  const handleClose = () => setOpen(false);

  return (
    <Snackbar
      open={isOpen}
      onClose={handleClose}
      autoHideDuration={readingTime(message)}
      anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
      // disable hiding on clickAway
      ClickAwayListenerProps={{ onClickAway: () => {} }}
    >
      <Alert onClose={handleClose} severity={severity}>
        {message}
      </Alert>
    </Snackbar>
  );
}
Example #5
Source File: ide-message-processor.ts    From kliveide with MIT License 6 votes vote down vote up
// --- Set up message processing
ipcRenderer.on(
  "MainToIdeRequest",
  async (_ev: IpcRendererEvent, message: RequestMessage) => {
    const response = await processIdeMessages(message);
    response.correlationId = message.correlationId;
    ipcRenderer.send("MainToIdeResponse", response);
  }
);
Example #6
Source File: IdeToEmuMessenger.ts    From kliveide with MIT License 6 votes vote down vote up
/**
   * Initializes the listener that processes responses
   */
  constructor() {
    super();
    ipcRenderer?.on(
      this.responseChannel,
      (_ev: IpcRendererEvent, response: ResponseMessage) =>
        this.processResponse(response)
    );
  }
Example #7
Source File: RendererToMainStateForwarder.ts    From kliveide with MIT License 6 votes vote down vote up
/**
   * Initializes the listener that processes responses
   */
  constructor(public readonly sourceId: MessageSource) {
    super();
    ipcRenderer?.on(
      this.responseChannel,
      (_ev: IpcRendererEvent, response: ResponseMessage) =>
        this.processResponse(response)
    );
  }
Example #8
Source File: emu-message-processor.ts    From kliveide with MIT License 6 votes vote down vote up
// --- Set up message processing
ipcRenderer.on(
  "MainToEmuRequest",
  async (_ev: IpcRendererEvent, message: RequestMessage) => {
    const response = await processEmulatorMessages(message);
    response.correlationId = message.correlationId;
    ipcRenderer.send("MainToEmuResponse", response);
  }
);
Example #9
Source File: ide-message-processor.ts    From kliveide with MIT License 6 votes vote down vote up
// --- Set up message processing
ipcRenderer.on(
  "IdeToEmuEmuRequest",
  async (_ev: IpcRendererEvent, message: RequestMessage) => {
    const response = await processIdeMessages(message);
    response.correlationId = message.correlationId;
    ipcRenderer.send("IdeToEmuEmuResponse", response);
  }
);
Example #10
Source File: preload.ts    From kliveide with MIT License 5 votes vote down vote up
contextBridge.exposeInMainWorld("ipcRenderer", <IpcRendereApi>{
  send: (channel: string, ...args: any[]) => ipcRenderer.send(channel, ...args),
  on: (
    channel: string,
    listener: (event: IpcRendererEvent, ...args: any[]) => void
  ) => ipcRenderer.on(channel, listener),
});
Example #11
Source File: ipc-renderer.ts    From electron-playground with MIT License 5 votes vote down vote up
listenerDownloadItemDone = (
  callback: (event: IpcRendererEvent, ...args: any[]) => void,
): void => ipcRendererListener('downloadItemDone', callback)
Example #12
Source File: ipc-renderer.ts    From electron-playground with MIT License 5 votes vote down vote up
listenerDownloadItemUpdate = (
  callback: (event: IpcRendererEvent, ...args: any[]) => void,
): void => ipcRendererListener('downloadItemUpdate', callback)
Example #13
Source File: ipc-renderer.ts    From electron-playground with MIT License 5 votes vote down vote up
listenerNewDownloadItem = (
  callback: (event: IpcRendererEvent, ...args: any[]) => void,
): void => ipcRendererListener('newDownloadItem', callback)
Example #14
Source File: ipc-renderer.ts    From electron-playground with MIT License 5 votes vote down vote up
ipcRendererListener = (
  eventName: IPCEventName,
  callback: (event: IpcRendererEvent, ...args: any[]) => void,
): void => {
  ipcRenderer.on(eventName, (event, ...args: any[]) => {
    callback(event, ...args)
  })
}
Example #15
Source File: DbView.tsx    From SeeQR with MIT License 5 votes vote down vote up
DbView = ({ selectedDb, show, setERView, ERView}: DbViewProps) => {
  const [dbTables, setTables] = useState<TableInfo[]>([]);
  const [selectedTable, setSelectedTable] = useState<TableInfo>();
  const [databases, setDatabases] = useState<DatabaseInfo[]>([]);
  const [open, setOpen] = useState(false);

  useEffect(() => {
    // Listen to backend for updates to list of tables on current db
    const tablesFromBackend = (evt: IpcRendererEvent, dbLists: unknown) => {
      if (isDbLists(dbLists)) {
        setDatabases(dbLists.databaseList);
        setTables(dbLists.tableList);
        setSelectedTable(selectedTable || dbLists.tableList[0]);
      }
    };
    ipcRenderer.on('db-lists', tablesFromBackend);
    requestDbListOnce();
    // return cleanup function
    return () => {
      ipcRenderer.removeListener('db-lists', tablesFromBackend);
    };
  });

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  const db = databases.find((dbN) => dbN.db_name === selectedDb);

  if (!show) return null;
  return (
    <>
      <DatabaseDetails db={db} />
      <br />
      <TablesTabs
        // setTables={setTables}
        tables={dbTables}
        selectTable={(table: TableInfo) => setSelectedTable(table)}
        selectedTable={selectedTable}
        selectedDb={selectedDb}
        setERView={setERView}
      />
      <br />
      <br />
      {(selectedTable && !ERView) ? (
        <StyledDummyButton
          variant="contained"
          color="primary"
          onClick={handleClickOpen}
        >
          Generate Dummy Data
        </StyledDummyButton>
      ) : null}
      <DummyDataModal
        open={open}
        onClose={handleClose}
        dbName={db?.db_name}
        tableName={selectedTable?.table_name}
      />
    </>
  );
}
Example #16
Source File: MessageChannel.tsx    From multi-downloader-nx with MIT License 5 votes vote down vote up
MessageChannelProvider: React.FC = ({ children }) => {

  const [store, dispatch] = useStore();

  const { ipcRenderer } = (window as any).Electron as { ipcRenderer: IpcRenderer };
  const randomEventHandler = React.useMemo(() => new RandomEventHandler(), []);

  React.useEffect(() => {
    (async () => {
      const currentService = await ipcRenderer.invoke('type');
      if (currentService !== undefined)
        return dispatch({ type: 'service', payload: currentService });
      if (store.service !== currentService) 
        ipcRenderer.invoke('setup', store.service)
    })();
  }, [store.service, dispatch, ipcRenderer])

  React.useEffect(() => {
    /* finish is a placeholder */
    const listener = (_: IpcRendererEvent, initalData: RandomEvent<'finish'>) => {
      const eventName = initalData.name as keyof RandomEvents;
      const data = initalData as unknown as RandomEvent<typeof eventName>;

      randomEventHandler.emit(data.name, data);
    }
    ipcRenderer.on('randomEvent', listener);
    return () => {
      ipcRenderer.removeListener('randomEvent', listener);
    };
  }, [ ipcRenderer ]);


  const messageHandler: FrontEndMessanges = {
    auth: async (data) => await ipcRenderer.invoke('auth', data),
    checkToken: async () => await ipcRenderer.invoke('checkToken'),
    search: async (data) => await ipcRenderer.invoke('search', data),
    handleDefault: async (data) => await ipcRenderer.invoke('default', data),
    availableDubCodes: async () => await ipcRenderer.invoke('availableDubCodes'),
    availableSubCodes: async () => await ipcRenderer.invoke('availableSubCodes'),
    resolveItems: async (data) => await ipcRenderer.invoke('resolveItems', data),
    listEpisodes: async (data) => await ipcRenderer.invoke('listEpisodes', data),
    randomEvents: randomEventHandler,
    downloadItem: (data) => ipcRenderer.invoke('downloadItem', data),
    isDownloading: () => ipcRenderer.sendSync('isDownloading'),
    writeToClipboard: async (data) => await ipcRenderer.invoke('writeToClipboard', data),
    openFolder: async (data) => await ipcRenderer.invoke('openFolder', data),
    logout: () => ipcRenderer.sendSync('changeProvider')
  }

  return <messageChannelContext.Provider value={messageHandler}>
    {children}
  </messageChannelContext.Provider>;
}
Example #17
Source File: electron.service.ts    From WowUp with GNU General Public License v3.0 5 votes vote down vote up
public on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
    window.wowup.rendererOn(channel, listener);
  }
Example #18
Source File: electron.service.ts    From WowUp with GNU General Public License v3.0 5 votes vote down vote up
public onRendererEvent(channel: MainChannels, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
    window.wowup?.onRendererEvent(channel, listener);
  }
Example #19
Source File: preload.ts    From WowUp with GNU General Public License v3.0 5 votes vote down vote up
function rendererOn(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) {
  ipcRenderer.on(channel, listener);
}
Example #20
Source File: preload.ts    From WowUp with GNU General Public License v3.0 5 votes vote down vote up
function onceRendererEvent(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) {
  ipcRenderer.once(channel, listener);
}
Example #21
Source File: preload.ts    From WowUp with GNU General Public License v3.0 5 votes vote down vote up
function onRendererEvent(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) {
  ipcRenderer.on(channel, listener);
}
Example #22
Source File: DbList.tsx    From SeeQR with MIT License 4 votes vote down vote up
DbList = ({
  selectedDb,
  setSelectedDb,
  setSelectedView,
  show,
}: DbListProps) => {  
  const [databases, setDatabases] = useState<string[]>([]);
  const [openAdd, setOpenAdd] = useState(false);
  const [openDupe, setOpenDupe] = useState(false);
  const [dbToDupe, setDbToDupe] = useState('');

  useEffect(() => {
    // Listen to backend for updates to list of available databases
    const dbListFromBackend = (evt: IpcRendererEvent, dbLists: unknown) => {
      if (isDbLists(dbLists)) {
        setDatabases(dbLists.databaseList.map((db) => db.db_name));
      }
    };
    ipcRenderer.on('db-lists', dbListFromBackend);
    requestDbListOnce();
    // return cleanup function
    return () => {
      ipcRenderer.removeListener('db-lists', dbListFromBackend);
    };
  });

  const handleClickOpenAdd = () => {
    setOpenAdd(true);
  };

  const handleCloseAdd = () => {
    setOpenAdd(false);
  };

  const handleClickOpenDupe = (dbName: string) => {
    setDbToDupe(dbName);
    setOpenDupe(true);
  };

  const handleCloseDupe = () => {
    setOpenDupe(false);
  };

  const selectHandler = (dbName: string) => {
    // setSelectedView('dbView');
    if (dbName === selectedDb) return;
    ipcRenderer
      .invoke('select-db', dbName)
      .then(() => {
        setSelectedDb(dbName);
      })
      .catch(() =>
        sendFeedback({
          type: 'error',
          message: `Failed to connect to ${dbName}`,
        })
      );
  };

  if (!show) return null;
  return (
    <>
      <Tooltip title="Import Database">
        <IconButton onClick={handleClickOpenAdd}>
          <AddIcon fontSize="large" />
        </IconButton>
      </Tooltip>
      <StyledSidebarList>
        {databases.map((dbName) => (
          <DbEntry
            key={`dbList_${dbName}`}
            db={dbName}
            isSelected={selectedDb === dbName}
            select={selectHandler}
            duplicate={() => handleClickOpenDupe(dbName)}
          />
        ))}
        {openDupe ? (
          <DuplicateDbModal
            open={openDupe}
            onClose={handleCloseDupe}
            dbCopyName={dbToDupe}
            databases={databases}
          />
        ) : null}
      </StyledSidebarList> 
      <AddNewDbModal
        open={openAdd}
        onClose={handleCloseAdd}
        databases={databases}
      />
    </>
  );
}
Example #23
Source File: window-controls.tsx    From simulator with Apache License 2.0 4 votes vote down vote up
WindowControls: React.FC<WindowControlsProps> = ({
  disableMaximize,
  disableMinimize,
  browserWindowId,
}) => {
  const [isMaximized, setIsMaximized] = useState(false);
  const remoteBrowserWindowId = useRef(browserWindowId);

  useEffect(() => {
    const onMaximimizeStateChange = (
      _event: IpcRendererEvent,
      isWindowMaximumized: boolean,
      targetBrowserWindowId: number
    ) => {
      if (targetBrowserWindowId === remoteBrowserWindowId.current) {
        setIsMaximized(isWindowMaximumized);
      }
    };

    ipcRenderer.on(
      'electron-react-titlebar/maximunize/change',
      onMaximimizeStateChange
    );

    const updateRemoteBrowserWindowId = async () => {
      remoteBrowserWindowId.current = (await ipcRenderer.invoke(
        'electron-react-titlebar/initialize',
        browserWindowId
      )) as number;
    };
    // eslint-disable-next-line promise/catch-or-return
    updateRemoteBrowserWindowId().finally(() => null);

    return () => {
      ipcRenderer.removeListener(
        'electron-react-titlebar/maximunize/change',
        onMaximimizeStateChange
      );
    };
  }, [browserWindowId]);

  const setMaximumize = useCallback(() => {
    ipcRenderer.send('electron-react-titlebar/maximumize/set', browserWindowId);
  }, [browserWindowId]);

  const setMinimumize = useCallback(() => {
    ipcRenderer.send('electron-react-titlebar/minimumize/set', browserWindowId);
  }, [browserWindowId]);

  const setClose = useCallback(() => {
    ipcRenderer.send('electron-react-titlebar/close', browserWindowId);
  }, [browserWindowId]);

  return (
    <div className="window-controls">
      {/* eslint-disable-next-line react/button-has-type */}
      <button
        aria-label="minimize"
        tabIndex={-1}
        className="window-control window-minimize"
        disabled={disableMinimize}
        onClick={setMinimumize}
      >
        <svg aria-hidden="true" version="1.1" width="10" height="10">
          <path d="M 0,5 10,5 10,6 0,6 Z" />
        </svg>
      </button>
      {/* eslint-disable-next-line react/button-has-type */}
      <button
        aria-label="maximize"
        tabIndex={-1}
        className="window-control window-maximize"
        disabled={disableMaximize}
        onClick={setMaximumize}
      >
        <svg aria-hidden="true" version="1.1" width="10" height="10">
          <path
            d={
              isMaximized
                ? 'm 2,1e-5 0,2 -2,0 0,8 8,0 0,-2 2,0 0,-8 z m 1,1 6,0 0,6 -1,0 0,-5 -5,0 z m -2,2 6,0 0,6 -6,0 z'
                : 'M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z'
            }
          />
        </svg>
      </button>
      {/* eslint-disable-next-line react/button-has-type */}
      <button
        aria-label="close"
        tabIndex={-1}
        className="window-control window-close"
        onClick={setClose}
      >
        <svg aria-hidden="true" version="1.1" width="10" height="10">
          <path d="M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z" />
        </svg>
      </button>
    </div>
  );
}
Example #24
Source File: NewSchemaView.tsx    From SeeQR with MIT License 4 votes vote down vote up
NewSchemaView = ({
    query,
    setQuery,
    createNewQuery,
    setSelectedDb,
    selectedDb,
    show,
}: NewSchemaViewProps) => {
  // additional local state properties using hooks
  const [dbTables, setTables] = useState<TableInfo[]>([]);
  const [selectedTable, setSelectedTable] = useState<TableInfo>();
  const [currentSql, setCurrentSql] = useState('');
  const [databases, setDatabases] = useState<DatabaseInfo[]>([]);
  const [open, setOpen] = useState(false);

  
  const defaultQuery: QueryData = {
    label: '', // required by QueryData interface, but not necessary for this view
    db: '', // name that user inputs in SchemaName.tsx
    sqlString: '', // sql string that user inputs in SchemaSqlInput.tsx
    group: '' // group string for sorting queries in accordians
  };

  const localQuery = { ...defaultQuery, ...query };

  useEffect(() => {
    
    // Listen to backend for updates to list of tables on current db
    const tablesFromBackend = (evt: IpcRendererEvent, dbLists: unknown) => {
      
      if (isDbLists(dbLists)) {
        setDatabases(dbLists.databaseList);
        setTables(dbLists.tableList);
        setSelectedTable(selectedTable || dbLists.tableList[0]);
      } 
    };
    ipcRenderer.on('db-lists', tablesFromBackend);
    requestDbListOnce();
    // return cleanup function
    return () => {
      ipcRenderer.removeListener('db-lists', tablesFromBackend);
    };
  });

  // handles naming of schema
  const onNameChange = (newName: string) => {
    setQuery({ ...localQuery, db: newName });
    setSelectedDb(newName);
  };
  
  // handles sql string input
  const onSqlChange = (newSql: string) => {
    // because App's workingQuery changes ref
    setCurrentSql(newSql);
    setQuery({ ...localQuery, sqlString: newSql });
  };
  
  // handle intializing new schema
  const onInitialize = () => {

    ipcRenderer.invoke(
      'initialize-db', {
        newDbName: localQuery.db,
      })
      .catch((err) => {
        sendFeedback({
          type: 'error',
          message: err ?? 'Failed to initialize db',
        });
      });
  }

  // handle exporting 
  const onExport = () => {
     ipcRenderer.invoke(
       'export-db', {
          sourceDb: selectedDb
       })
       .catch((err) => {
        sendFeedback({
          type: 'error',
          message: err ?? 'Failed to export db',
        });
      });
   }


  // onRun function to handle when user submits sql string to update schema
  const onRun = () => {
    
    setSelectedDb(localQuery.db);
    // // request backend to run query
    ipcRenderer
      .invoke('update-db', {
        sqlString: localQuery.sqlString,
        selectedDb
      })
      .then(() => {setCurrentSql('');})
      .catch((err) => {
        sendFeedback({
          type: 'error',
          message: err ?? 'Failed to Update Schema',
        });
      });
  };


if (!show) return null;
return (
  <NewSchemaViewContainer>
    <TopRow>
      <SchemaName name={selectedDb} onChange={onNameChange}/>
      <InitButton variant="contained" onClick={onInitialize}>Initialize Database</InitButton>
      <ExportButton variant="contained" onClick={onExport}>Export</ExportButton>
    </TopRow>
    <SchemaSqlInput  
      sql={currentSql}
      onChange={onSqlChange}
      runQuery={onRun}
    />
      <CenterButton>
        <RunButton variant="contained" onClick={onRun}>
          Update Database
        </RunButton>
      </CenterButton>
    <Container>
      <Typography variant="h4">{`${selectedDb}`}</Typography>
    </Container>
    <TablesTabs
      // setTables={setTables}
      tables={dbTables}
      selectTable={(table: TableInfo) => setSelectedTable(table)}
      selectedTable={selectedTable}
      selectedDb={selectedDb}
    />
  </NewSchemaViewContainer>
);
}
Example #25
Source File: QueryView.tsx    From SeeQR with MIT License 4 votes vote down vote up
QueryView = ({
  query,
  createNewQuery,
  selectedDb,
  setSelectedDb,
  setQuery,
  show,
  queries
}: QueryViewProps) => {
  const [databases, setDatabases] = useState<string[]>([]);

  const defaultQuery: QueryData = {
    label: '',
    db: selectedDb,
    sqlString: '',
    group: '',
  };

  const localQuery = { ...defaultQuery, ...query };

  // Register event listener that receives database list for db selector
  useEffect(() => {
    const receiveDbs = (evt: IpcRendererEvent, dbLists: unknown) => {
      if (isDbLists(dbLists)) {
        setDatabases(dbLists.databaseList.map((db) => db.db_name));
      }
    };
    ipcRenderer.on('db-lists', receiveDbs);
    requestDbListOnce();

    return () => {
      ipcRenderer.removeListener('db-lists', receiveDbs);
    }
  });

  const onLabelChange = (newLabel: string) => {
    setQuery({ ...localQuery, label: newLabel });
  };

  const onGroupChange = (newGroup: string) => {
    setQuery({ ...localQuery, group: newGroup });
  };

  const onDbChange = (newDb: string) => {
    // when db is changed we must change selected db state on app, as well as
    // request updates for db and table information. Otherwise database view tab
    // will show wrong informatio
    ipcRenderer
      .invoke('select-db', newDb)
      .then(() => {
        setQuery({ ...localQuery, db: newDb });
        setSelectedDb(newDb);
      })

      .catch(() =>
        sendFeedback({
          type: 'error',
          message: `Failed to connect to ${newDb}`,
        })
      );
  };
  const onSqlChange = (newSql: string) => {
    // because App's workingQuery changes ref
    setQuery({ ...localQuery, sqlString: newSql });
  };

  const onRun = () => {
    if (!localQuery.label.trim()) {
      sendFeedback({
        type: 'info',
        message: "Queries without a label will run but won't be saved",
      });
    }

    if (!localQuery.group.trim()) {
      sendFeedback({
        type: 'info',
        message: "Queries without a group will run but won't be saved",
      });
    }


    // request backend to run query
    ipcRenderer
      .invoke('run-query', {
        targetDb: localQuery.db,
        sqlString: localQuery.sqlString,
        selectedDb,
      })
      .then(({ db, sqlString, returnedRows, explainResults, error }) => {
        if (error) {
          throw error
        }

        const transformedData = {
          sqlString,
          returnedRows,
          executionPlan: explainResults[0]['QUERY PLAN'][0],
          label: localQuery.label,
          db,
          group: localQuery.group,
        };

        const keys:string[] = Object.keys(queries);
        for (let i = 0; i < keys.length; i++){
          if (keys[i].includes(`db:${localQuery.db} group:${localQuery.group}`)) {
           return sendFeedback({
              type: 'info',
              message: `${localQuery.db} already exists in ${localQuery.group}`,
            });
          };
          

        };
        createNewQuery(transformedData);
      })
      .then(() => {
        localQuery.sqlString = '';
      })
      .catch((err) => {
        sendFeedback({
          type: 'error',
          message: err ?? 'Failed to Run Query',
        });
      });
  };

  if (!show) return null;
  return (
    <QueryViewContainer>
      <TopRow>
        <QueryLabel label={localQuery.label} onChange={onLabelChange} />
        <QueryGroup group={localQuery.group} onChange={onGroupChange} />
        <QueryDb
          db={localQuery.db}
          onChange={onDbChange}
          databases={databases}
        />
        <QueryTopSummary
          rows={query?.returnedRows?.length}
          totalTime={getPrettyTime(query)}
        />
      </TopRow>
      <QuerySqlInput
        sql={localQuery?.sqlString ?? ''}
        onChange={onSqlChange}
        runQuery={onRun}
      />
      <CenterButton>
        <RunButton variant="contained" onClick={onRun}>
          Run Query
        </RunButton>
      </CenterButton>
      <QuerySummary executionPlan={query?.executionPlan} />
      <QueryTabs
        results={query?.returnedRows}
        executionPlan={query?.executionPlan}
      />
    </QueryViewContainer>
  );
}