diff --git a/README.md b/README.md
index 0485ff9..e0d1d2f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
# pulse-http
An API testing app, capable of testing HTTP and WebSocket APIs
-
+
diff --git a/package.json b/package.json
index 9976b4d..fa30314 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "pulse-http",
"private": true,
- "version": "0.2.0",
+ "version": "0.3.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index b632615..7c68b49 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -2897,7 +2897,7 @@ dependencies = [
[[package]]
name = "pulse-http"
-version = "0.2.0"
+version = "0.3.0"
dependencies = [
"serde",
"serde_json",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index e230197..415350f 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -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"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 3ebfd69..5d230cf 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -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",
diff --git a/src/app.tsx b/src/app.tsx
new file mode 100644
index 0000000..3e6f075
--- /dev/null
+++ b/src/app.tsx
@@ -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 (
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/src/common/components/themed-resize-handle.tsx b/src/common/components/themed-resize-handle.tsx
new file mode 100644
index 0000000..e53196b
--- /dev/null
+++ b/src/common/components/themed-resize-handle.tsx
@@ -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 (
+
+
+
+
+
+ );
+};
+
+export default ThemedResizeHandle;
diff --git a/src/common/state-stores/settings-store.ts b/src/common/state-stores/settings-store.ts
new file mode 100644
index 0000000..6fe45f6
--- /dev/null
+++ b/src/common/state-stores/settings-store.ts
@@ -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;
+ initializeStore: () => Promise;
+}
+
+const useSettingsStore = create((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'
+ );
+
+ if (storedDisplayTheme) {
+ set({ displayTheme: storedDisplayTheme });
+ }
+ },
+}));
+
+export default useSettingsStore;
diff --git a/src/common/stores.ts b/src/common/stores.ts
index 905bb51..a61c360 100644
--- a/src/common/stores.ts
+++ b/src/common/stores.ts
@@ -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');
diff --git a/src/common/themes.ts b/src/common/themes.ts
new file mode 100644
index 0000000..b8a7887
--- /dev/null
+++ b/src/common/themes.ts
@@ -0,0 +1,13 @@
+import { createTheme } from '@mui/material';
+
+export const lightTheme = createTheme({
+ palette: {
+ mode: 'light',
+ },
+});
+
+export const darkTheme = createTheme({
+ palette: {
+ mode: 'dark',
+ },
+});
diff --git a/src/common/types/settings-types.ts b/src/common/types/settings-types.ts
new file mode 100644
index 0000000..85e2f8b
--- /dev/null
+++ b/src/common/types/settings-types.ts
@@ -0,0 +1 @@
+export type DisplayTheme = 'light' | 'dark';
diff --git a/src/main.css b/src/main.css
index 9b0bf51..a58f3f1 100644
--- a/src/main.css
+++ b/src/main.css
@@ -3,5 +3,4 @@ body,
#root {
height: 100%;
margin: 0;
- background-color: antiquewhite;
}
diff --git a/src/main.tsx b/src/main.tsx
index ebb71b1..c985947 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -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(
-
+
);
diff --git a/src/pages/main-page/components/http-display/http-display.tsx b/src/pages/main-page/components/http-display/http-display.tsx
index 4d7807f..6e4f612 100644
--- a/src/pages/main-page/components/http-display/http-display.tsx
+++ b/src/pages/main-page/components/http-display/http-display.tsx
@@ -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;
@@ -64,18 +64,7 @@ const HTTPDisplay = ({
{response && (
<>
-
-
-
-
-
+
diff --git a/src/pages/main-page/components/sidebar/sidebar.tsx b/src/pages/main-page/components/sidebar/sidebar.tsx
index 43537fc..87e969f 100644
--- a/src/pages/main-page/components/sidebar/sidebar.tsx
+++ b/src/pages/main-page/components/sidebar/sidebar.tsx
@@ -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[];
@@ -20,6 +23,18 @@ const Sidebar = ({
}: SidebarProps) => {
const [activeTab, setActiveTab] = useState(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 (
+
+
+
+ PulseHTTP
+
+
+
+ {displayTheme === 'light' ? : }
+
+
+
- {[...recordHistory].reverse().map((record, index) => (
- onRecordSelected(record)}
- key={index}
- fullWidth
- />
- ))}
+ {recordHistory.length > 0 ? (
+ [...recordHistory]
+ .reverse()
+ .map((record, index) => (
+ onRecordSelected(record)}
+ key={index}
+ fullWidth
+ />
+ ))
+ ) : (
+
+ No history records found.
+
+ )}
) : (
- {[...savedRecords].reverse().map((record, index, array) => (
- onRecordSelected(record)}
- onDeleteRecord={() =>
- onDeleteSavedRecord(array.length - 1 - index)
- }
- key={index}
- />
- ))}
+ {savedRecords.length > 0 ? (
+ [...savedRecords]
+ .reverse()
+ .map((record, index, array) => (
+ onRecordSelected(record)}
+ onDeleteRecord={() =>
+ onDeleteSavedRecord(array.length - 1 - index)
+ }
+ key={index}
+ />
+ ))
+ ) : (
+
+ No saved records found.
+
+ )}
)}
diff --git a/src/pages/main-page/components/ws-display/ws-display.tsx b/src/pages/main-page/components/ws-display/ws-display.tsx
index 1a5ce3f..c4a5348 100644
--- a/src/pages/main-page/components/ws-display/ws-display.tsx
+++ b/src/pages/main-page/components/ws-display/ws-display.tsx
@@ -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;
@@ -62,18 +62,7 @@ const WSDisplay = ({
{connection && (
<>
-
-
-
-
-
+
diff --git a/src/pages/main-page/main-page.tsx b/src/pages/main-page/main-page.tsx
index ff57d09..e510585 100644
--- a/src/pages/main-page/main-page.tsx
+++ b/src/pages/main-page/main-page.tsx
@@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
-import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
+import { Panel, PanelGroup } from 'react-resizable-panels';
import { Box, Paper } from '@mui/material';
-import { GoGrabber } from 'react-icons/go';
import Sidebar from './components/sidebar/sidebar';
import { checkRecordsEqual } from './utils';
import { savedRecordsStore } from '../../common/stores';
@@ -14,6 +13,7 @@ import { WSInputs } from './components/ws-display/types';
import useInterfaceStore from '../../common/state-stores/interface-store';
import { InterfaceType } from '../../common/types/api-interface-types';
import { useShallow } from 'zustand/shallow';
+import ThemedResizeHandle from '../../common/components/themed-resize-handle';
const MainPage = () => {
const [recordHistory, setRecordHistory] = useState([]);
@@ -48,6 +48,7 @@ const MainPage = () => {
const newSavedRecords = [...savedRecords, record];
setSavedRecords(newSavedRecords);
await savedRecordsStore.set('data', newSavedRecords);
+ await savedRecordsStore.save();
};
const onRecordSelected = (record: InterfaceInputs) => {
@@ -79,6 +80,7 @@ const MainPage = () => {
);
setSavedRecords(newSavedRecords);
await savedRecordsStore.set('data', newSavedRecords);
+ await savedRecordsStore.save();
};
useEffect(() => {
@@ -104,19 +106,7 @@ const MainPage = () => {
/>
-
-
-
-
-
+
{interfaceType === InterfaceType.HTTP ? (