From dc37e52c988e79e3afe422a5d4e03529fbc1080b Mon Sep 17 00:00:00 2001 From: omerbenda Date: Fri, 31 Oct 2025 19:21:31 +0200 Subject: [PATCH 1/6] feat(themed-resize-handle.tsx, themes.ts): Add dark theme and add component for themed resize handle --- .../components/themed-resize-handle.tsx | 38 +++++++++++++++++++ src/common/themes.ts | 7 ++++ src/main.css | 1 - src/main.tsx | 8 +++- .../components/http-display/http-display.tsx | 17 ++------- .../components/ws-display/ws-display.tsx | 17 ++------- src/pages/main-page/main-page.tsx | 18 ++------- 7 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 src/common/components/themed-resize-handle.tsx create mode 100644 src/common/themes.ts 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/themes.ts b/src/common/themes.ts new file mode 100644 index 0000000..ae3df81 --- /dev/null +++ b/src/common/themes.ts @@ -0,0 +1,7 @@ +import { createTheme } from '@mui/material'; + +export const darkTheme = createTheme({ + palette: { + mode: '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..f0a4e24 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,12 +3,18 @@ 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 { ThemeProvider } from '@emotion/react'; +import { darkTheme } from './common/themes'; +import { CssBaseline } from '@mui/material'; 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/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..7075297 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([]); @@ -104,19 +104,7 @@ const MainPage = () => { /> - - - - - + {interfaceType === InterfaceType.HTTP ? ( Date: Mon, 3 Nov 2025 01:34:09 +0200 Subject: [PATCH 2/6] feat(sidebar.tsx): Add setting to change theme --- src/app.tsx | 29 +++++++++++++++++++ src/common/state-stores/settings-store.ts | 29 +++++++++++++++++++ src/common/stores.ts | 2 ++ src/common/themes.ts | 6 ++++ src/common/types/settings-types.ts | 1 + src/main.tsx | 10 ++----- .../main-page/components/sidebar/sidebar.tsx | 26 ++++++++++++++++- src/pages/main-page/main-page.tsx | 4 ++- 8 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 src/app.tsx create mode 100644 src/common/state-stores/settings-store.ts create mode 100644 src/common/types/settings-types.ts 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/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 index ae3df81..b8a7887 100644 --- a/src/common/themes.ts +++ b/src/common/themes.ts @@ -1,5 +1,11 @@ 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.tsx b/src/main.tsx index f0a4e24..c985947 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,20 +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 { ThemeProvider } from '@emotion/react'; -import { darkTheme } from './common/themes'; -import { CssBaseline } from '@mui/material'; +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/sidebar/sidebar.tsx b/src/pages/main-page/components/sidebar/sidebar.tsx index 43537fc..357297e 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' ? : } + + + { 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,7 +106,7 @@ const MainPage = () => { /> - + {interfaceType === InterfaceType.HTTP ? ( Date: Mon, 3 Nov 2025 01:36:40 +0200 Subject: [PATCH 3/6] fix(sidebar.tsx): Make sidebar title unselectable --- src/pages/main-page/components/sidebar/sidebar.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/main-page/components/sidebar/sidebar.tsx b/src/pages/main-page/components/sidebar/sidebar.tsx index 357297e..4ffb758 100644 --- a/src/pages/main-page/components/sidebar/sidebar.tsx +++ b/src/pages/main-page/components/sidebar/sidebar.tsx @@ -45,7 +45,9 @@ const Sidebar = ({ > - PulseHTTP + + PulseHTTP + {displayTheme === 'light' ? : } From 986f781e3f900af6319684181e2d08f59029e8d5 Mon Sep 17 00:00:00 2001 From: omerbenda Date: Mon, 3 Nov 2025 01:45:34 +0200 Subject: [PATCH 4/6] feat(sidebar.tsx): Add message when no history or saved records are found --- .../main-page/components/sidebar/sidebar.tsx | 64 +++++++++++++------ 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/src/pages/main-page/components/sidebar/sidebar.tsx b/src/pages/main-page/components/sidebar/sidebar.tsx index 4ffb758..87e969f 100644 --- a/src/pages/main-page/components/sidebar/sidebar.tsx +++ b/src/pages/main-page/components/sidebar/sidebar.tsx @@ -71,14 +71,28 @@ const Sidebar = ({ width="100%" height="100%" > - {[...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. + + )} )} From a9d05bb86bb3b2a0c631bc41b8c9ecede3b5b445 Mon Sep 17 00:00:00 2001 From: omerbenda Date: Mon, 3 Nov 2025 01:50:00 +0200 Subject: [PATCH 5/6] docs(manifest files): Change version number to 0.3.0 --- package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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", From 4920b37d2ec2f8a6a2b091db713f67bbe45e1075 Mon Sep 17 00:00:00 2001 From: omerbenda <34948058+omerbenda@users.noreply.github.com> Date: Mon, 3 Nov 2025 01:51:05 +0200 Subject: [PATCH 6/6] docs(README.md): Update picture of the app in the readme file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 -image +image