Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pulse-http
An API testing app, capable of testing HTTP and WebSocket APIs

<img width="802" height="632" alt="image" src="https://github.com/user-attachments/assets/5f738e02-9219-46e5-8e7d-0b9a3cea2285" />
<img width="950" height="691" alt="image" src="https://github.com/user-attachments/assets/cc551d32-94b4-4641-b4ed-99bdd77b596f" />
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "pulse-http",
"private": true,
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pulse-http"
version = "0.2.0"
version = "0.3.0"
description = "An API testing app"
authors = ["omerbenda"]
edition = "2021"
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "pulse-http",
"version": "0.2.0",
"version": "0.3.0",
"identifier": "com.pulsehttp",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
29 changes: 29 additions & 0 deletions src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ThemeProvider } from '@emotion/react';
import { CssBaseline } from '@mui/material';
import { darkTheme, lightTheme } from './common/themes';
import MainPage from './pages/main-page/main-page';
import useSettingsStore from './common/state-stores/settings-store';
import { useShallow } from 'zustand/shallow';
import { useEffect } from 'react';

const App = () => {
const { initializeStore, displayTheme } = useSettingsStore(
useShallow((state) => ({
initializeStore: state.initializeStore,
displayTheme: state.displayTheme,
}))
);

useEffect(() => {
initializeStore();
}, [initializeStore]);

return (
<ThemeProvider theme={displayTheme === 'dark' ? darkTheme : lightTheme}>
<CssBaseline />
<MainPage />
</ThemeProvider>
);
};

export default App;
38 changes: 38 additions & 0 deletions src/common/components/themed-resize-handle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Box, useTheme } from '@mui/material';
import { GoGrabber } from 'react-icons/go';
import {
PanelResizeHandle,
PanelResizeHandleProps,
} from 'react-resizable-panels';

type ThemedResizeHandleProps = {
direction: 'horizontal' | 'vertical';
} & PanelResizeHandleProps;

const ThemedResizeHandle = ({
direction,
...props
}: ThemedResizeHandleProps) => {
const theme = useTheme();

return (
<PanelResizeHandle {...props}>
<Box
display="flex"
flexDirection="column"
justifyContent="center"
alignItems="center"
bgcolor={theme.palette.divider}
width={direction === 'vertical' ? '16px' : '100%'}
height={direction === 'vertical' ? '100%' : '16px'}
>
<GoGrabber
size={16}
style={{ rotate: direction === 'horizontal' ? '90deg' : undefined }}
/>
</Box>
</PanelResizeHandle>
);
};

export default ThemedResizeHandle;
29 changes: 29 additions & 0 deletions src/common/state-stores/settings-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { create } from 'zustand';
import { settingsStore } from '../stores';
import { DisplayTheme } from '../types/settings-types';

interface SettingsState {
displayTheme: DisplayTheme;
setDisplayTheme: (displayTheme: DisplayTheme) => Promise<void>;
initializeStore: () => Promise<void>;
}

const useSettingsStore = create<SettingsState>((set) => ({
displayTheme: 'light',
setDisplayTheme: async (displayTheme: DisplayTheme) => {
set({ displayTheme });
await settingsStore.set('displayTheme', displayTheme);
await settingsStore.save();
},
initializeStore: async () => {
const storedDisplayTheme = await settingsStore.get<DisplayTheme>(
'displayTheme'
);

if (storedDisplayTheme) {
set({ displayTheme: storedDisplayTheme });
}
},
}));

export default useSettingsStore;
2 changes: 2 additions & 0 deletions src/common/stores.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { load } from '@tauri-apps/plugin-store';

export const savedRecordsStore = await load('saved-records.json');

export const settingsStore = await load('settings.json');
13 changes: 13 additions & 0 deletions src/common/themes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createTheme } from '@mui/material';

export const lightTheme = createTheme({
palette: {
mode: 'light',
},
});

export const darkTheme = createTheme({
palette: {
mode: 'dark',
},
});
1 change: 1 addition & 0 deletions src/common/types/settings-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type DisplayTheme = 'light' | 'dark';
1 change: 0 additions & 1 deletion src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ body,
#root {
height: 100%;
margin: 0;
background-color: antiquewhite;
}
4 changes: 2 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import MainPage from './pages/main-page/main-page';
import * as monaco from 'monaco-editor';
import { loader } from '@monaco-editor/react';
import App from './app';
import './Main.css';

loader.config({ monaco });

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<MainPage />
<App />
</React.StrictMode>
);
17 changes: 3 additions & 14 deletions src/pages/main-page/components/http-display/http-display.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { SubmitHandler, UseFormReturn } from 'react-hook-form';
import { Box } from '@mui/material';
import { GoGrabber } from 'react-icons/go';
import { PanelGroup, Panel, PanelResizeHandle } from 'react-resizable-panels';
import { PanelGroup, Panel } from 'react-resizable-panels';
import HTTPRequestForm from './components/http-request-form/http-request-form';
import HTTPResponse from './components/http-response/http-response';
import { useState } from 'react';
import { InterfaceInputs } from '../../types';
import { HTTPInputs } from './types';
import ThemedResizeHandle from '../../../../common/components/themed-resize-handle';

type HTTPDisplayProps = {
interfaceForm: UseFormReturn<HTTPInputs>;
Expand Down Expand Up @@ -64,18 +64,7 @@ const HTTPDisplay = ({
</Panel>
{response && (
<>
<PanelResizeHandle>
<Box
display="flex"
justifyContent="center"
alignItems="center"
bgcolor="lightgray"
width="100%"
height="16px"
>
<GoGrabber size={16} style={{ rotate: '90deg' }} />
</Box>
</PanelResizeHandle>
<ThemedResizeHandle direction="horizontal" />
<Panel>
<Box height="100%">
<HTTPResponse response={response} />
Expand Down
92 changes: 73 additions & 19 deletions src/pages/main-page/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Box, Tab, Tabs } from '@mui/material';
import { Box, Divider, IconButton, Tab, Tabs, Typography } from '@mui/material';
import HistoryRecordButton from './component/history-record-button';
import { SidebarTab } from './types';
import { useState } from 'react';
import SavedRecordBox from './component/saved-record-button';
import { InterfaceInputs } from '../../types';
import useSettingsStore from '../../../../common/state-stores/settings-store';
import { useShallow } from 'zustand/shallow';
import { FaMoon, FaSun } from 'react-icons/fa';

type SidebarProps = {
recordHistory: InterfaceInputs[];
Expand All @@ -20,6 +23,18 @@ const Sidebar = ({
}: SidebarProps) => {
const [activeTab, setActiveTab] = useState<SidebarTab>(SidebarTab.HISTORY);

const { displayTheme, setDisplayTheme } = useSettingsStore(
useShallow((state) => ({
displayTheme: state.displayTheme,
setDisplayTheme: state.setDisplayTheme,
}))
);

const changeDisplayTheme = async () => {
const newTheme = displayTheme === 'light' ? 'dark' : 'light';
await setDisplayTheme(newTheme);
};

return (
<Box
display="flex"
Expand All @@ -28,6 +43,17 @@ const Sidebar = ({
width="100%"
height="100%"
>
<Box display="flex" width="100%">
<Box display="flex" flexGrow={1} alignItems="center">
<Typography fontWeight="bold" sx={{ userSelect: 'none' }}>
PulseHTTP
</Typography>
</Box>
<IconButton onClick={changeDisplayTheme} size="small">
{displayTheme === 'light' ? <FaSun color="orange" /> : <FaMoon />}
</IconButton>
</Box>
<Divider />
<Tabs
value={activeTab}
variant="fullWidth"
Expand All @@ -45,14 +71,28 @@ const Sidebar = ({
width="100%"
height="100%"
>
{[...recordHistory].reverse().map((record, index) => (
<HistoryRecordButton
record={record}
onClick={() => onRecordSelected(record)}
key={index}
fullWidth
/>
))}
{recordHistory.length > 0 ? (
[...recordHistory]
.reverse()
.map((record, index) => (
<HistoryRecordButton
record={record}
onClick={() => onRecordSelected(record)}
key={index}
fullWidth
/>
))
) : (
<Box
display="flex"
justifyContent="center"
alignItems="center"
width="100%"
height="100%"
>
<Typography>No history records found.</Typography>
</Box>
)}
</Box>
) : (
<Box
Expand All @@ -63,16 +103,30 @@ const Sidebar = ({
width="100%"
height="100%"
>
{[...savedRecords].reverse().map((record, index, array) => (
<SavedRecordBox
record={record}
onRecordSelected={() => onRecordSelected(record)}
onDeleteRecord={() =>
onDeleteSavedRecord(array.length - 1 - index)
}
key={index}
/>
))}
{savedRecords.length > 0 ? (
[...savedRecords]
.reverse()
.map((record, index, array) => (
<SavedRecordBox
record={record}
onRecordSelected={() => onRecordSelected(record)}
onDeleteRecord={() =>
onDeleteSavedRecord(array.length - 1 - index)
}
key={index}
/>
))
) : (
<Box
display="flex"
justifyContent="center"
alignItems="center"
width="100%"
height="100%"
>
<Typography>No saved records found.</Typography>
</Box>
)}
</Box>
)}
</Box>
Expand Down
17 changes: 3 additions & 14 deletions src/pages/main-page/components/ws-display/ws-display.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useEffect, useState } from 'react';
import { Box } from '@mui/material';
import { SubmitHandler, UseFormReturn } from 'react-hook-form';
import { GoGrabber } from 'react-icons/go';
import { InterfaceInputs } from '../../types';
import { WSInputs } from './types';
import { PanelGroup, Panel, PanelResizeHandle } from 'react-resizable-panels';
import { PanelGroup, Panel } from 'react-resizable-panels';
import WSConnectForm from './components/ws-connect-form/ws-connection-form';
import WSConnection from './components/ws-connection/ws-connection';
import ThemedResizeHandle from '../../../../common/components/themed-resize-handle';

type WSDisplayProps = {
interfaceForm: UseFormReturn<WSInputs, any, WSInputs>;
Expand Down Expand Up @@ -62,18 +62,7 @@ const WSDisplay = ({
</Panel>
{connection && (
<>
<PanelResizeHandle>
<Box
display="flex"
justifyContent="center"
alignItems="center"
bgcolor="lightgray"
width="100%"
height="16px"
>
<GoGrabber size={16} style={{ rotate: '90deg' }} />
</Box>
</PanelResizeHandle>
<ThemedResizeHandle direction='horizontal' />
<Panel>
<Box height="100%">
<WSConnection connection={connection} />
Expand Down
Loading