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 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 ? (