diff --git a/package-lock.json b/package-lock.json index f4dfee31..154b7639 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "format-graphql": "^1.4.0", "framer-motion": "^9.0.4", "lucide-react": "^0.309.0", + "mime": "^4.0.1", "papaparse": "^5.4.1", "parse-color": "^1.0.0", "react": "^18.2.0", @@ -7208,6 +7209,20 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz", + "integrity": "sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", diff --git a/package.json b/package.json index 01e010de..3b51d80d 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "format-graphql": "^1.4.0", "framer-motion": "^9.0.4", "lucide-react": "^0.309.0", + "mime": "^4.0.1", "papaparse": "^5.4.1", "parse-color": "^1.0.0", "react": "^18.2.0", diff --git a/src-tauri/src/http.rs b/src-tauri/src/http.rs index 60a668ba..fdafbc94 100644 --- a/src-tauri/src/http.rs +++ b/src-tauri/src/http.rs @@ -244,6 +244,13 @@ pub async fn send_http_request( } } request_builder = request_builder.form(&form_params); + } else if body_type == "binary" && request_body.contains_key("filePath") { + let file_path = request_body.get("filePath").unwrap().as_str().unwrap(); + request_builder = request_builder.body( + fs::read(file_path) + .map_err(|e| e.to_string()) + .expect("Failed to read file"), + ); } else if body_type == "multipart/form-data" && request_body.contains_key("form") { let mut multipart_form = multipart::Form::new(); if let Some(form_definition) = request_body.get("form") { diff --git a/src-web/components/BinaryFileEditor.tsx b/src-web/components/BinaryFileEditor.tsx new file mode 100644 index 00000000..3df32f98 --- /dev/null +++ b/src-web/components/BinaryFileEditor.tsx @@ -0,0 +1,82 @@ +import { open } from '@tauri-apps/api/dialog'; +import mime from 'mime'; +import { useKeyValue } from '../hooks/useKeyValue'; +import type { HttpRequest } from '../lib/models'; +import { Banner } from './core/Banner'; +import { Button } from './core/Button'; +import { InlineCode } from './core/InlineCode'; +import { HStack, VStack } from './core/Stacks'; + +type Props = { + requestId: string; + contentType: string | null; + body: HttpRequest['body']; + onChange: (body: HttpRequest['body']) => void; + onChangeContentType: (contentType: string | null) => void; +}; + +export function BinaryFileEditor({ + contentType, + body, + onChange, + onChangeContentType, + requestId, +}: Props) { + const ignoreContentType = useKeyValue({ + namespace: 'global', + key: ['ignore_content_type', requestId], + fallback: false, + }); + + const handleClick = async () => { + await ignoreContentType.set(false); + const path = await open({ + title: 'Select File', + multiple: false, + }); + if (path) { + onChange({ filePath: path }); + } + }; + + const filePath = typeof body.filePath === 'string' ? body.filePath : undefined; + const mimeType = mime.getType(filePath ?? '') ?? 'application/octet-stream'; + + console.log('mimeType', mimeType, contentType); + + return ( + + + +
+ {/* Special character to insert ltr text in rtl element without making things wonky */} + ‎ + {filePath ?? 'Select File'} +
+
+ {mimeType !== contentType && !ignoreContentType.value && ( + +
+
Set Content-Type header to
+ {mimeType}? +
+ + + + +
+ )} +
+ ); +} diff --git a/src-web/components/FormMultipartEditor.tsx b/src-web/components/FormMultipartEditor.tsx index 31225c88..430529cf 100644 --- a/src-web/components/FormMultipartEditor.tsx +++ b/src-web/components/FormMultipartEditor.tsx @@ -6,7 +6,7 @@ import { PairEditor } from './core/PairEditor'; type Props = { forceUpdateKey: string; body: HttpRequest['body']; - onChange: (headers: HttpRequest['body']) => void; + onChange: (body: HttpRequest['body']) => void; }; export function FormMultipartEditor({ body, forceUpdateKey, onChange }: Props) { diff --git a/src-web/components/GlobalHooks.tsx b/src-web/components/GlobalHooks.tsx index 5a25861c..dccb6baa 100644 --- a/src-web/components/GlobalHooks.tsx +++ b/src-web/components/GlobalHooks.tsx @@ -18,7 +18,6 @@ import { settingsQueryKey } from '../hooks/useSettings'; import { useSyncAppearance } from '../hooks/useSyncAppearance'; import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle'; import { workspacesQueryKey } from '../hooks/useWorkspaces'; -import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore'; import type { Model } from '../lib/models'; import { modelsEq } from '../lib/models'; import { setPathname } from '../lib/persistPathname'; @@ -142,7 +141,7 @@ function removeById(model: T) { const shouldIgnoreModel = (payload: Model) => { if (payload.model === 'key_value') { - return payload.namespace === NAMESPACE_NO_SYNC; + return payload.namespace === 'no_sync'; } return false; }; diff --git a/src-web/components/RequestPane.tsx b/src-web/components/RequestPane.tsx index e7254c4e..76d63558 100644 --- a/src-web/components/RequestPane.tsx +++ b/src-web/components/RequestPane.tsx @@ -6,24 +6,27 @@ import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse'; import { useIsResponseLoading } from '../hooks/useIsResponseLoading'; import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; +import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders'; import { useSendRequest } from '../hooks/useSendRequest'; import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest'; import { tryFormatJson } from '../lib/formatters'; import type { HttpHeader, HttpRequest, HttpUrlParameter } from '../lib/models'; import { - BODY_TYPE_OTHER, AUTH_TYPE_BASIC, AUTH_TYPE_BEARER, AUTH_TYPE_NONE, + BODY_TYPE_BINARY, BODY_TYPE_FORM_MULTIPART, BODY_TYPE_FORM_URLENCODED, BODY_TYPE_GRAPHQL, BODY_TYPE_JSON, BODY_TYPE_NONE, + BODY_TYPE_OTHER, BODY_TYPE_XML, } from '../lib/models'; import { BasicAuth } from './BasicAuth'; import { BearerAuth } from './BearerAuth'; +import { BinaryFileEditor } from './BinaryFileEditor'; import { CountBadge } from './core/CountBadge'; import { Editor } from './core/Editor'; import type { TabItem } from './core/Tabs/Tabs'; @@ -56,6 +59,7 @@ export const RequestPane = memo(function RequestPane({ const [activeTab, setActiveTab] = useActiveTab(); const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState(0); const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null); + const contentType = useContentTypeFromHeaders(activeRequest.headers); const tabs: TabItem[] = useMemo( () => [ @@ -68,11 +72,12 @@ export const RequestPane = memo(function RequestPane({ { label: 'Url Encoded', value: BODY_TYPE_FORM_URLENCODED }, { label: 'Multi-Part', value: BODY_TYPE_FORM_MULTIPART }, { type: 'separator', label: 'Text Content' }, + { label: 'GraphQL', value: BODY_TYPE_GRAPHQL }, { label: 'JSON', value: BODY_TYPE_JSON }, { label: 'XML', value: BODY_TYPE_XML }, - { label: 'GraphQL', value: BODY_TYPE_GRAPHQL }, { label: 'Other', value: BODY_TYPE_OTHER }, { type: 'separator', label: 'Other' }, + { label: 'Binary File', value: BODY_TYPE_BINARY }, { label: 'No Body', shortLabel: 'Body', value: BODY_TYPE_NONE }, ], onChange: async (bodyType) => { @@ -94,7 +99,7 @@ export const RequestPane = memo(function RequestPane({ []), { name: 'Content-Type', - value: bodyType, + value: bodyType === BODY_TYPE_OTHER ? 'text/plain' : bodyType, enabled: true, }, ]; @@ -111,10 +116,10 @@ export const RequestPane = memo(function RequestPane({ ]; } + await updateRequest.mutateAsync(patch); + // Force update header editor so any changed headers are reflected setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100); - - updateRequest.mutate(patch); }, }, }, @@ -171,6 +176,27 @@ export const RequestPane = memo(function RequestPane({ (body: HttpRequest['body']) => updateRequest.mutate({ body }), [updateRequest], ); + const handleContentTypeChange = useCallback( + async (contentType: string | null) => { + const headers = + contentType != null + ? activeRequest.headers.map((h) => + h.name.toLowerCase() === 'content-type' ? { ...h, value: contentType } : h, + ) + : activeRequest.headers; + await updateRequest.mutateAsync({ headers }); + + // Force update header editor so any changed headers are reflected + setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100); + }, + [activeRequest.headers, updateRequest], + ); + const handleBinaryFileChange = useCallback( + (body: HttpRequest['body']) => { + updateRequest.mutate({ body }); + }, + [updateRequest], + ); const handleBodyTextChange = useCallback( (text: string) => updateRequest.mutate({ body: { text } }), [updateRequest], @@ -314,6 +340,14 @@ export const RequestPane = memo(function RequestPane({ body={activeRequest.body} onChange={handleBodyChange} /> + ) : activeRequest.bodyType === BODY_TYPE_BINARY ? ( + ) : ( No Body )} diff --git a/src-web/components/ResponsePane.tsx b/src-web/components/ResponsePane.tsx index 64e2cad0..26628196 100644 --- a/src-web/components/ResponsePane.tsx +++ b/src-web/components/ResponsePane.tsx @@ -3,7 +3,7 @@ import type { CSSProperties } from 'react'; import { memo, useMemo } from 'react'; import { createGlobalState } from 'react-use'; import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse'; -import { useResponseContentType } from '../hooks/useResponseContentType'; +import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders'; import { useResponseViewMode } from '../hooks/useResponseViewMode'; import type { HttpRequest } from '../lib/models'; import { isResponseLoading } from '../lib/models'; @@ -37,7 +37,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ const { activeResponse, setPinnedResponse, responses } = usePinnedHttpResponse(activeRequest); const [viewMode, setViewMode] = useResponseViewMode(activeResponse?.requestId); const [activeTab, setActiveTab] = useActiveTab(); - const contentType = useResponseContentType(activeResponse); + const contentType = useContentTypeFromHeaders(activeResponse?.headers ?? null); const tabs = useMemo( () => [ diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index fdf979e0..b197bf86 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -31,7 +31,6 @@ import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest'; import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest'; import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest'; import { fallbackRequestName } from '../lib/fallbackRequestName'; -import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore'; import type { Folder, GrpcRequest, HttpRequest, Workspace } from '../lib/models'; import { isResponseLoading } from '../lib/models'; import type { DropdownItem } from './core/Dropdown'; @@ -87,7 +86,7 @@ export function Sidebar({ className }: Props) { const collapsed = useKeyValue>({ key: ['sidebar_collapsed', activeWorkspace?.id ?? 'n/a'], fallback: {}, - namespace: NAMESPACE_NO_SYNC, + namespace: 'no_sync', }); useHotKey('http_request.duplicate', async () => { diff --git a/src-web/components/Workspace.tsx b/src-web/components/Workspace.tsx index 6e3d95fa..35c5dc0a 100644 --- a/src-web/components/Workspace.tsx +++ b/src-web/components/Workspace.tsx @@ -9,13 +9,19 @@ import type { import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useWindowSize } from 'react-use'; import { useActiveRequest } from '../hooks/useActiveRequest'; +import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; +import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId'; import { useImportData } from '../hooks/useImportData'; import { useIsFullscreen } from '../hooks/useIsFullscreen'; import { useOsInfo } from '../hooks/useOsInfo'; import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useSidebarWidth } from '../hooks/useSidebarWidth'; +import { useWorkspaces } from '../hooks/useWorkspaces'; +import { Banner } from './core/Banner'; import { Button } from './core/Button'; import { HotKeyList } from './core/HotKeyList'; +import { InlineCode } from './core/InlineCode'; +import { FeedbackLink } from './core/Link'; import { HStack } from './core/Stacks'; import { CreateDropdown } from './CreateDropdown'; import { GrpcConnectionLayout } from './GrpcConnectionLayout'; @@ -34,6 +40,9 @@ const drag = { gridArea: 'drag' }; const WINDOW_FLOATING_SIDEBAR_WIDTH = 600; export default function Workspace() { + const workspaces = useWorkspaces(); + const activeWorkspace = useActiveWorkspace(); + const activeWorkspaceId = useActiveWorkspaceId(); const { setWidth, width, resetWidth } = useSidebarWidth(); const { hide, show, hidden } = useSidebarHidden(); const activeRequest = useActiveRequest(); @@ -119,6 +128,11 @@ export default function Workspace() { ); } + // We're loading still + if (workspaces.length === 0) { + return null; + } + return (
- {activeRequest == null ? ( + {activeWorkspace == null ? ( +
+ + The active workspace{' '} + {activeWorkspaceId} was not found. + Select a workspace from the header menu or report this bug to + +
+ ) : activeRequest == null ? ( ); diff --git a/src-web/components/core/Banner.tsx b/src-web/components/core/Banner.tsx index 92d7beab..e84785cb 100644 --- a/src-web/components/core/Banner.tsx +++ b/src-web/components/core/Banner.tsx @@ -4,7 +4,7 @@ import type { ReactNode } from 'react'; interface Props { children: ReactNode; className?: string; - color?: 'danger' | 'success' | 'gray'; + color?: 'danger' | 'warning' | 'success' | 'gray'; } export function Banner({ children, className, color = 'gray' }: Props) { return ( @@ -14,6 +14,7 @@ export function Banner({ children, className, color = 'gray' }: Props) { className, 'border border-dashed italic px-3 py-2 rounded select-auto cursor-text', color === 'gray' && 'border-gray-500/60 bg-gray-300/10 text-gray-800', + color === 'warning' && 'border-orange-500/60 bg-orange-300/10 text-orange-800', color === 'danger' && 'border-red-500/60 bg-red-300/10 text-red-800', color === 'success' && 'border-green-500/60 bg-green-300/10 text-green-800', )} diff --git a/src-web/components/core/Link.tsx b/src-web/components/core/Link.tsx index ada25c3e..323ab6e5 100644 --- a/src-web/components/core/Link.tsx +++ b/src-web/components/core/Link.tsx @@ -33,3 +33,7 @@ export function Link({ href, children, className, ...other }: Props) { ); } + +export function FeedbackLink() { + return Feedback; +} diff --git a/src-web/components/responseViewers/TextViewer.tsx b/src-web/components/responseViewers/TextViewer.tsx index fc2b7a74..a0b05555 100644 --- a/src-web/components/responseViewers/TextViewer.tsx +++ b/src-web/components/responseViewers/TextViewer.tsx @@ -4,7 +4,7 @@ import { useCallback, useMemo } from 'react'; import { useDebouncedState } from '../../hooks/useDebouncedState'; import { useFilterResponse } from '../../hooks/useFilterResponse'; import { useResponseBodyText } from '../../hooks/useResponseBodyText'; -import { useResponseContentType } from '../../hooks/useResponseContentType'; +import { useContentTypeFromHeaders } from '../../hooks/useContentTypeFromHeaders'; import { useToggle } from '../../hooks/useToggle'; import { tryFormatJson, tryFormatXml } from '../../lib/formatters'; import type { HttpResponse } from '../../lib/models'; @@ -21,7 +21,7 @@ export function TextViewer({ response, pretty }: Props) { const [isSearching, toggleIsSearching] = useToggle(); const [filterText, setDebouncedFilterText, setFilterText] = useDebouncedState('', 400); - const contentType = useResponseContentType(response); + const contentType = useContentTypeFromHeaders(response.headers); const rawBody = useResponseBodyText(response) ?? ''; const formattedBody = pretty && contentType?.includes('json') diff --git a/src-web/hooks/useActiveCookieJar.ts b/src-web/hooks/useActiveCookieJar.ts index e31f403c..80bedac9 100644 --- a/src-web/hooks/useActiveCookieJar.ts +++ b/src-web/hooks/useActiveCookieJar.ts @@ -1,5 +1,4 @@ import { useEffect } from 'react'; -import { NAMESPACE_GLOBAL } from '../lib/keyValueStore'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useCookieJars } from './useCookieJars'; import { useKeyValue } from './useKeyValue'; @@ -9,7 +8,7 @@ export function useActiveCookieJar() { const cookieJars = useCookieJars(); const kv = useKeyValue({ - namespace: NAMESPACE_GLOBAL, + namespace: 'global', key: ['activeCookieJar', workspaceId ?? 'n/a'], fallback: null, }); diff --git a/src-web/hooks/useContentTypeFromHeaders.ts b/src-web/hooks/useContentTypeFromHeaders.ts new file mode 100644 index 00000000..7644022a --- /dev/null +++ b/src-web/hooks/useContentTypeFromHeaders.ts @@ -0,0 +1,9 @@ +import { useMemo } from 'react'; +import type { HttpHeader } from '../lib/models'; + +export function useContentTypeFromHeaders(headers: HttpHeader[] | null): string | null { + return useMemo( + () => headers?.find((h) => h.name.toLowerCase() === 'content-type')?.value ?? null, + [headers], + ); +} diff --git a/src-web/hooks/useGrpcProtoFiles.ts b/src-web/hooks/useGrpcProtoFiles.ts index ec7b5300..c0f90e56 100644 --- a/src-web/hooks/useGrpcProtoFiles.ts +++ b/src-web/hooks/useGrpcProtoFiles.ts @@ -1,9 +1,8 @@ -import { NAMESPACE_GLOBAL } from '../lib/keyValueStore'; import { useKeyValue } from './useKeyValue'; export function protoFilesArgs(requestId: string | null) { return { - namespace: NAMESPACE_GLOBAL, + namespace: 'global' as const, key: ['proto_files', requestId ?? 'n/a'], }; } diff --git a/src-web/hooks/useKeyValue.ts b/src-web/hooks/useKeyValue.ts index 95c2d776..35d05aaa 100644 --- a/src-web/hooks/useKeyValue.ts +++ b/src-web/hooks/useKeyValue.ts @@ -20,7 +20,7 @@ export function useKeyValue({ key, fallback, }: { - namespace?: string; + namespace?: 'app' | 'no_sync' | 'global'; key: string | string[]; fallback: T; }) { diff --git a/src-web/hooks/useRecentEnvironments.ts b/src-web/hooks/useRecentEnvironments.ts index 05fbb30c..153db76c 100644 --- a/src-web/hooks/useRecentEnvironments.ts +++ b/src-web/hooks/useRecentEnvironments.ts @@ -1,12 +1,12 @@ import { useEffect, useMemo } from 'react'; -import { getKeyValue, NAMESPACE_GLOBAL } from '../lib/keyValueStore'; +import { getKeyValue } from '../lib/keyValueStore'; import { useActiveEnvironmentId } from './useActiveEnvironmentId'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useEnvironments } from './useEnvironments'; import { useKeyValue } from './useKeyValue'; const kvKey = (workspaceId: string) => 'recent_environments::' + workspaceId; -const namespace = NAMESPACE_GLOBAL; +const namespace = 'global'; const fallback: string[] = []; export function useRecentEnvironments() { diff --git a/src-web/hooks/useRecentRequests.ts b/src-web/hooks/useRecentRequests.ts index 6ca2fa61..f78d8608 100644 --- a/src-web/hooks/useRecentRequests.ts +++ b/src-web/hooks/useRecentRequests.ts @@ -1,5 +1,5 @@ import { useEffect, useMemo } from 'react'; -import { getKeyValue, NAMESPACE_GLOBAL } from '../lib/keyValueStore'; +import { getKeyValue } from '../lib/keyValueStore'; import { useActiveRequestId } from './useActiveRequestId'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useGrpcRequests } from './useGrpcRequests'; @@ -7,7 +7,7 @@ import { useHttpRequests } from './useHttpRequests'; import { useKeyValue } from './useKeyValue'; const kvKey = (workspaceId: string) => 'recent_requests::' + workspaceId; -const namespace = NAMESPACE_GLOBAL; +const namespace = 'global'; const fallback: string[] = []; export function useRecentRequests() { diff --git a/src-web/hooks/useRecentWorkspaces.ts b/src-web/hooks/useRecentWorkspaces.ts index 767f3092..23d7e17c 100644 --- a/src-web/hooks/useRecentWorkspaces.ts +++ b/src-web/hooks/useRecentWorkspaces.ts @@ -1,11 +1,11 @@ import { useEffect, useMemo } from 'react'; -import { getKeyValue, NAMESPACE_GLOBAL } from '../lib/keyValueStore'; +import { getKeyValue } from '../lib/keyValueStore'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useKeyValue } from './useKeyValue'; import { useWorkspaces } from './useWorkspaces'; const kvKey = () => 'recent_workspaces'; -const namespace = NAMESPACE_GLOBAL; +const namespace = 'global'; const fallback: string[] = []; export function useRecentWorkspaces() { diff --git a/src-web/hooks/useResponseContentType.ts b/src-web/hooks/useResponseContentType.ts deleted file mode 100644 index 0653e550..00000000 --- a/src-web/hooks/useResponseContentType.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useMemo } from 'react'; -import type { HttpResponse } from '../lib/models'; - -export function useResponseContentType(response: HttpResponse | null): string | null { - return useMemo( - () => response?.headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ?? null, - [response], - ); -} diff --git a/src-web/hooks/useSidebarHidden.ts b/src-web/hooks/useSidebarHidden.ts index d6b3e9c0..e2d06536 100644 --- a/src-web/hooks/useSidebarHidden.ts +++ b/src-web/hooks/useSidebarHidden.ts @@ -1,12 +1,11 @@ import { useMemo } from 'react'; -import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useKeyValue } from './useKeyValue'; export function useSidebarHidden() { const activeWorkspaceId = useActiveWorkspaceId(); const { set, value } = useKeyValue({ - namespace: NAMESPACE_NO_SYNC, + namespace: 'no_sync', key: ['sidebar_hidden', activeWorkspaceId ?? 'n/a'], fallback: false, }); diff --git a/src-web/lib/keyValueStore.ts b/src-web/lib/keyValueStore.ts index a41f6db6..12127013 100644 --- a/src-web/lib/keyValueStore.ts +++ b/src-web/lib/keyValueStore.ts @@ -1,11 +1,8 @@ import { invoke } from '@tauri-apps/api'; import type { KeyValue } from './models'; -export const NAMESPACE_GLOBAL = 'global'; -export const NAMESPACE_NO_SYNC = 'no_sync'; - export async function setKeyValue({ - namespace = NAMESPACE_GLOBAL, + namespace = 'global', key, value, }: { @@ -21,7 +18,7 @@ export async function setKeyValue({ } export async function getKeyValue({ - namespace = NAMESPACE_GLOBAL, + namespace = 'global', key, fallback, }: { diff --git a/src-web/lib/models.ts b/src-web/lib/models.ts index ae629641..1b1776ee 100644 --- a/src-web/lib/models.ts +++ b/src-web/lib/models.ts @@ -1,6 +1,7 @@ export const BODY_TYPE_NONE = null; export const BODY_TYPE_GRAPHQL = 'graphql'; export const BODY_TYPE_JSON = 'application/json'; +export const BODY_TYPE_BINARY = 'binary'; export const BODY_TYPE_OTHER = 'other'; export const BODY_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'; export const BODY_TYPE_FORM_MULTIPART = 'multipart/form-data'; diff --git a/src-web/lib/persistPathname.ts b/src-web/lib/persistPathname.ts index 533195cc..f75fe341 100644 --- a/src-web/lib/persistPathname.ts +++ b/src-web/lib/persistPathname.ts @@ -1,8 +1,8 @@ import { appWindow } from '@tauri-apps/api/window'; -import { NAMESPACE_NO_SYNC, getKeyValue, setKeyValue } from './keyValueStore'; +import { getKeyValue, setKeyValue } from './keyValueStore'; const key = ['window_pathname', appWindow.label]; -const namespace = NAMESPACE_NO_SYNC; +const namespace = 'no_sync'; const fallback = undefined; export async function setPathname(value: string) { diff --git a/src-web/main.css b/src-web/main.css index cf0c5588..07aef3f7 100644 --- a/src-web/main.css +++ b/src-web/main.css @@ -65,6 +65,10 @@ } } + .rtl { + direction: rtl; + } + iframe { &::-webkit-scrollbar-corner, &::-webkit-scrollbar {