Run oxfmt across repo, add format script and docs

Add .oxfmtignore to skip generated bindings and wasm-pack output.
Add npm format script, update DEVELOPMENT.md for Vite+ toolchain,
and format all non-generated files with oxfmt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Gregory Schier
2026-03-13 10:15:49 -07:00
parent 45262edfbd
commit b4a1c418bb
664 changed files with 13638 additions and 13492 deletions

View File

@@ -1,5 +1,5 @@
import { convertFileSrc } from '@tauri-apps/api/core';
import { useEffect, useState } from 'react';
import { convertFileSrc } from "@tauri-apps/api/core";
import { useEffect, useState } from "react";
interface Props {
bodyPath?: string;
@@ -13,7 +13,7 @@ export function AudioViewer({ bodyPath, data }: Props) {
if (bodyPath) {
setSrc(convertFileSrc(bodyPath));
} else if (data) {
const blob = new Blob([new Uint8Array(data)], { type: 'audio/mpeg' });
const blob = new Blob([new Uint8Array(data)], { type: "audio/mpeg" });
const url = URL.createObjectURL(blob);
setSrc(url);
return () => URL.revokeObjectURL(url);

View File

@@ -1,11 +1,11 @@
import type { HttpResponse } from '@yaakapp-internal/models';
import { useSaveResponse } from '../../hooks/useSaveResponse';
import { getContentTypeFromHeaders } from '../../lib/model_util';
import { Banner } from '../core/Banner';
import { Button } from '../core/Button';
import { InlineCode } from '../core/InlineCode';
import { LoadingIcon } from '../core/LoadingIcon';
import { EmptyStateText } from '../EmptyStateText';
import type { HttpResponse } from "@yaakapp-internal/models";
import { useSaveResponse } from "../../hooks/useSaveResponse";
import { getContentTypeFromHeaders } from "../../lib/model_util";
import { Banner } from "../core/Banner";
import { Button } from "../core/Button";
import { InlineCode } from "../core/InlineCode";
import { LoadingIcon } from "../core/LoadingIcon";
import { EmptyStateText } from "../EmptyStateText";
interface Props {
response: HttpResponse;
@@ -13,10 +13,10 @@ interface Props {
export function BinaryViewer({ response }: Props) {
const saveResponse = useSaveResponse(response);
const contentType = getContentTypeFromHeaders(response.headers) ?? 'unknown';
const contentType = getContentTypeFromHeaders(response.headers) ?? "unknown";
// Wait until the response has been fully-downloaded
if (response.state !== 'closed') {
if (response.state !== "closed") {
return (
<EmptyStateText>
<LoadingIcon size="sm" />

View File

@@ -1,7 +1,7 @@
import classNames from 'classnames';
import Papa from 'papaparse';
import { useMemo } from 'react';
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '../core/Table';
import classNames from "classnames";
import Papa from "papaparse";
import { useMemo } from "react";
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "../core/Table";
interface Props {
text: string | null;
@@ -26,7 +26,7 @@ export function CsvViewerInner({ text, className }: { text: string | null; class
return (
<div className="overflow-auto h-full">
<Table className={classNames(className, 'text-sm')}>
<Table className={classNames(className, "text-sm")}>
<TableHead>
<TableRow>
{parsed.meta.fields?.map((field) => (
@@ -39,7 +39,7 @@ export function CsvViewerInner({ text, className }: { text: string | null; class
// oxlint-disable-next-line react/no-array-index-key
<TableRow key={i}>
{parsed.meta.fields?.map((key) => (
<TableCell key={key}>{row[key] ?? ''}</TableCell>
<TableCell key={key}>{row[key] ?? ""}</TableCell>
))}
</TableRow>
))}

View File

@@ -1,18 +1,18 @@
import type { HttpResponse } from '@yaakapp-internal/models';
import type { ServerSentEvent } from '@yaakapp-internal/sse';
import classNames from 'classnames';
import { Fragment, useMemo, useState } from 'react';
import { useFormatText } from '../../hooks/useFormatText';
import { useResponseBodyEventSource } from '../../hooks/useResponseBodyEventSource';
import { isJSON } from '../../lib/contentType';
import { Button } from '../core/Button';
import type { EditorProps } from '../core/Editor/Editor';
import { Editor } from '../core/Editor/LazyEditor';
import { EventDetailHeader, EventViewer } from '../core/EventViewer';
import { EventViewerRow } from '../core/EventViewerRow';
import { Icon } from '../core/Icon';
import { InlineCode } from '../core/InlineCode';
import { HStack, VStack } from '../core/Stacks';
import type { HttpResponse } from "@yaakapp-internal/models";
import type { ServerSentEvent } from "@yaakapp-internal/sse";
import classNames from "classnames";
import { Fragment, useMemo, useState } from "react";
import { useFormatText } from "../../hooks/useFormatText";
import { useResponseBodyEventSource } from "../../hooks/useResponseBodyEventSource";
import { isJSON } from "../../lib/contentType";
import { Button } from "../core/Button";
import type { EditorProps } from "../core/Editor/Editor";
import { Editor } from "../core/Editor/LazyEditor";
import { EventDetailHeader, EventViewer } from "../core/EventViewer";
import { EventViewerRow } from "../core/EventViewerRow";
import { Icon } from "../core/Icon";
import { InlineCode } from "../core/InlineCode";
import { HStack, VStack } from "../core/Stacks";
interface Props {
response: HttpResponse;
@@ -85,9 +85,9 @@ function EventDetail({
setShowingLarge: (v: boolean) => void;
onClose: () => void;
}) {
const language = useMemo<'text' | 'json'>(() => {
if (!event?.data) return 'text';
return isJSON(event?.data) ? 'json' : 'text';
const language = useMemo<"text" | "json">(() => {
if (!event?.data) return "text";
return isJSON(event?.data) ? "json" : "text";
}, [event?.data]);
return (
@@ -125,7 +125,7 @@ function EventDetail({
);
}
function FormattedEditor({ text, language }: { text: string; language: EditorProps['language'] }) {
function FormattedEditor({ text, language }: { text: string; language: EditorProps["language"] }) {
const formatted = useFormatText({ text, language, pretty: true });
if (formatted == null) return null;
return <Editor readOnly defaultValue={formatted} language={language} stateKey={null} />;
@@ -144,11 +144,11 @@ function EventLabels({
}) {
return (
<HStack space={1.5} alignItems="center" className={className}>
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
<InlineCode className={classNames("py-0", isActive && "bg-text-subtlest text-text")}>
{event.id ?? index}
</InlineCode>
{event.eventType && (
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
<InlineCode className={classNames("py-0", isActive && "bg-text-subtlest text-text")}>
{event.eventType}
</InlineCode>
)}

View File

@@ -1,12 +1,12 @@
import type { HttpResponse } from '@yaakapp-internal/models';
import { useMemo, useState } from 'react';
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
import { languageFromContentType } from '../../lib/contentType';
import { getContentTypeFromHeaders } from '../../lib/model_util';
import type { EditorProps } from '../core/Editor/Editor';
import { EmptyStateText } from '../EmptyStateText';
import { TextViewer } from './TextViewer';
import { WebPageViewer } from './WebPageViewer';
import type { HttpResponse } from "@yaakapp-internal/models";
import { useMemo, useState } from "react";
import { useResponseBodyText } from "../../hooks/useResponseBodyText";
import { languageFromContentType } from "../../lib/contentType";
import { getContentTypeFromHeaders } from "../../lib/model_util";
import type { EditorProps } from "../core/Editor/Editor";
import { EmptyStateText } from "../EmptyStateText";
import { TextViewer } from "./TextViewer";
import { WebPageViewer } from "./WebPageViewer";
interface Props {
response: HttpResponse;
@@ -17,14 +17,14 @@ interface Props {
export function HTMLOrTextViewer({ response, pretty, textViewerClassName }: Props) {
const rawTextBody = useResponseBodyText({ response, filter: null });
const contentType = getContentTypeFromHeaders(response.headers);
const language = languageFromContentType(contentType, rawTextBody.data ?? '');
const language = languageFromContentType(contentType, rawTextBody.data ?? "");
if (rawTextBody.isLoading || response.state === 'initialized') {
if (rawTextBody.isLoading || response.state === "initialized") {
return null;
}
if (language === 'html' && pretty) {
return <WebPageViewer html={rawTextBody.data ?? ''} baseUrl={response.url} />;
if (language === "html" && pretty) {
return <WebPageViewer html={rawTextBody.data ?? ""} baseUrl={response.url} />;
}
if (rawTextBody.data == null) {
return <EmptyStateText>Empty response</EmptyStateText>;
@@ -43,7 +43,7 @@ export function HTMLOrTextViewer({ response, pretty, textViewerClassName }: Prop
interface HttpTextViewerProps {
response: HttpResponse;
text: string;
language: EditorProps['language'];
language: EditorProps["language"];
pretty: boolean;
className?: string;
}

View File

@@ -1,6 +1,6 @@
import { convertFileSrc } from '@tauri-apps/api/core';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { convertFileSrc } from "@tauri-apps/api/core";
import classNames from "classnames";
import { useEffect, useState } from "react";
type Props = { className?: string } & (
| {
@@ -13,14 +13,14 @@ type Props = { className?: string } & (
export function ImageViewer({ className, ...props }: Props) {
const [src, setSrc] = useState<string>();
const bodyPath = 'bodyPath' in props ? props.bodyPath : null;
const data = 'data' in props ? props.data : null;
const bodyPath = "bodyPath" in props ? props.bodyPath : null;
const data = "data" in props ? props.data : null;
useEffect(() => {
if (bodyPath != null) {
setSrc(convertFileSrc(bodyPath));
} else if (data != null) {
const blob = new Blob([data], { type: 'image/png' });
const blob = new Blob([data], { type: "image/png" });
const url = URL.createObjectURL(blob);
setSrc(url);
return () => URL.revokeObjectURL(url);
@@ -33,7 +33,7 @@ export function ImageViewer({ className, ...props }: Props) {
<img
src={src}
alt="Response preview"
className={classNames(className, 'max-w-full max-h-full')}
className={classNames(className, "max-w-full max-h-full")}
/>
);
}

View File

@@ -1,5 +1,5 @@
import classNames from 'classnames';
import { JsonAttributeTree } from '../core/JsonAttributeTree';
import classNames from "classnames";
import { JsonAttributeTree } from "../core/JsonAttributeTree";
interface Props {
text: string;
@@ -15,7 +15,7 @@ export function JsonViewer({ text, className }: Props) {
}
return (
<div className={classNames(className, 'overflow-x-auto h-full')}>
<div className={classNames(className, "overflow-x-auto h-full")}>
<JsonAttributeTree attrValue={parsed} />
</div>
);

View File

@@ -1,19 +1,19 @@
import { type MultipartPart, parseMultipart } from '@mjackson/multipart-parser';
import { lazy, Suspense, useMemo } from 'react';
import { languageFromContentType } from '../../lib/contentType';
import { Banner } from '../core/Banner';
import { Icon } from '../core/Icon';
import { LoadingIcon } from '../core/LoadingIcon';
import { TabContent, Tabs } from '../core/Tabs/Tabs';
import { AudioViewer } from './AudioViewer';
import { CsvViewer } from './CsvViewer';
import { ImageViewer } from './ImageViewer';
import { SvgViewer } from './SvgViewer';
import { TextViewer } from './TextViewer';
import { VideoViewer } from './VideoViewer';
import { WebPageViewer } from './WebPageViewer';
import { type MultipartPart, parseMultipart } from "@mjackson/multipart-parser";
import { lazy, Suspense, useMemo } from "react";
import { languageFromContentType } from "../../lib/contentType";
import { Banner } from "../core/Banner";
import { Icon } from "../core/Icon";
import { LoadingIcon } from "../core/LoadingIcon";
import { TabContent, Tabs } from "../core/Tabs/Tabs";
import { AudioViewer } from "./AudioViewer";
import { CsvViewer } from "./CsvViewer";
import { ImageViewer } from "./ImageViewer";
import { SvgViewer } from "./SvgViewer";
import { TextViewer } from "./TextViewer";
import { VideoViewer } from "./VideoViewer";
import { WebPageViewer } from "./WebPageViewer";
const PdfViewer = lazy(() => import('./PdfViewer').then((m) => ({ default: m.PdfViewer })));
const PdfViewer = lazy(() => import("./PdfViewer").then((m) => ({ default: m.PdfViewer })));
interface Props {
data: Uint8Array;
@@ -21,7 +21,7 @@ interface Props {
idPrefix?: string;
}
export function MultipartViewer({ data, boundary, idPrefix = 'multipart' }: Props) {
export function MultipartViewer({ data, boundary, idPrefix = "multipart" }: Props) {
const parseResult = useMemo(() => {
try {
const maxFileSize = 1024 * 1024 * 10; // 10MB
@@ -58,10 +58,10 @@ export function MultipartViewer({ data, boundary, idPrefix = 'multipart' }: Prop
layout="horizontal"
tabListClassName="border-r border-r-border -ml-3"
tabs={parts.map((part, i) => ({
label: part.name ?? '',
label: part.name ?? "",
value: tabValue(part, i),
rightSlot:
part.filename && part.headers.contentType.mediaType?.startsWith('image/') ? (
part.filename && part.headers.contentType.mediaType?.startsWith("image/") ? (
<div className="h-5 w-5 overflow-auto flex items-center justify-end">
<ImageViewer
data={part.arrayBuffer}
@@ -89,7 +89,7 @@ export function MultipartViewer({ data, boundary, idPrefix = 'multipart' }: Prop
function Part({ part }: { part: MultipartPart }) {
const mimeType = part.headers.contentType.mediaType ?? null;
const contentTypeHeader = part.headers.get('content-type');
const contentTypeHeader = part.headers.get("content-type");
const { uint8Array, content, detectedLanguage } = useMemo(() => {
const uint8Array = new Uint8Array(part.arrayBuffer);
@@ -118,7 +118,7 @@ function Part({ part }: { part: MultipartPart }) {
return <CsvViewer text={content} className="bg-primary h-10 w-10" />;
}
if (mimeType?.match(/^text\/html/i) || detectedLanguage === 'html') {
if (mimeType?.match(/^text\/html/i) || detectedLanguage === "html") {
return <WebPageViewer html={content} />;
}
@@ -134,5 +134,5 @@ function Part({ part }: { part: MultipartPart }) {
}
function tabValue(part: MultipartPart, i: number) {
return `${part.name ?? ''}::${i}`;
return `${part.name ?? ""}::${i}`;
}

View File

@@ -1,19 +1,21 @@
import 'react-pdf/dist/Page/TextLayer.css';
import 'react-pdf/dist/Page/AnnotationLayer.css';
import { convertFileSrc } from '@tauri-apps/api/core';
import './PdfViewer.css';
import type { PDFDocumentProxy } from 'pdfjs-dist';
import { useEffect, useRef, useState } from 'react';
import { Document, Page } from 'react-pdf';
import { useContainerSize } from '../../hooks/useContainerQuery';
import { fireAndForget } from '../../lib/fireAndForget';
import "react-pdf/dist/Page/TextLayer.css";
import "react-pdf/dist/Page/AnnotationLayer.css";
import { convertFileSrc } from "@tauri-apps/api/core";
import "./PdfViewer.css";
import type { PDFDocumentProxy } from "pdfjs-dist";
import { useEffect, useRef, useState } from "react";
import { Document, Page } from "react-pdf";
import { useContainerSize } from "../../hooks/useContainerQuery";
import { fireAndForget } from "../../lib/fireAndForget";
fireAndForget(import('react-pdf').then(({ pdfjs }) => {
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url,
).toString();
}));
fireAndForget(
import("react-pdf").then(({ pdfjs }) => {
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
}),
);
interface Props {
bodyPath?: string;
@@ -21,8 +23,8 @@ interface Props {
}
const options = {
cMapUrl: '/cmaps/',
standardFontDataUrl: '/standard_fonts/',
cMapUrl: "/cmaps/",
standardFontDataUrl: "/standard_fonts/",
};
export function PdfViewer({ bodyPath, data }: Props) {

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useState } from "react";
interface Props {
text: string;
@@ -13,7 +13,7 @@ export function SvgViewer({ text, className }: Props) {
return setSrc(null);
}
const blob = new Blob([text], { type: 'image/svg+xml;charset=utf-8' });
const blob = new Blob([text], { type: "image/svg+xml;charset=utf-8" });
const url = URL.createObjectURL(blob);
setSrc(url);
@@ -25,6 +25,6 @@ export function SvgViewer({ text, className }: Props) {
}
return (
<img src={src} alt="Response preview" className={className ?? 'max-w-full max-h-full pb-2'} />
<img src={src} alt="Response preview" className={className ?? "max-w-full max-h-full pb-2"} />
);
}

View File

@@ -1,20 +1,20 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
import { useCallback, useMemo } from 'react';
import { createGlobalState } from 'react-use';
import { useDebouncedValue } from '../../hooks/useDebouncedValue';
import { useFormatText } from '../../hooks/useFormatText';
import type { EditorProps } from '../core/Editor/Editor';
import { hyperlink } from '../core/Editor/hyperlink/extension';
import { Editor } from '../core/Editor/LazyEditor';
import { IconButton } from '../core/IconButton';
import { Input } from '../core/Input';
import classNames from "classnames";
import type { ReactNode } from "react";
import { useCallback, useMemo } from "react";
import { createGlobalState } from "react-use";
import { useDebouncedValue } from "../../hooks/useDebouncedValue";
import { useFormatText } from "../../hooks/useFormatText";
import type { EditorProps } from "../core/Editor/Editor";
import { hyperlink } from "../core/Editor/hyperlink/extension";
import { Editor } from "../core/Editor/LazyEditor";
import { IconButton } from "../core/IconButton";
import { Input } from "../core/Input";
const extraExtensions = [hyperlink];
interface Props {
text: string;
language: EditorProps['language'];
language: EditorProps["language"];
stateKey: string | null;
pretty?: boolean;
className?: string;
@@ -49,11 +49,11 @@ export function TextViewer({ language, text, stateKey, pretty, className, onFilt
if (isSearching) {
setFilterText(null);
} else {
setFilterText('');
setFilterText("");
}
}, [isSearching, setFilterText]);
const canFilter = onFilter && (language === 'json' || language === 'xml' || language === 'html');
const canFilter = onFilter && (language === "json" || language === "xml" || language === "html");
const actions = useMemo<ReactNode[]>(() => {
const nodes: ReactNode[] = [];
@@ -64,17 +64,17 @@ export function TextViewer({ language, text, stateKey, pretty, className, onFilt
nodes.push(
<div key="input" className="w-full !opacity-100">
<Input
key={stateKey ?? 'filter'}
key={stateKey ?? "filter"}
validate={!filteredResponse.error}
hideLabel
autoFocus
containerClassName="bg-surface"
size="sm"
placeholder={language === 'json' ? 'JSONPath expression' : 'XPath expression'}
placeholder={language === "json" ? "JSONPath expression" : "XPath expression"}
label="Filter expression"
name="filter"
defaultValue={filterText}
onKeyDown={(e) => e.key === 'Escape' && toggleSearch()}
onKeyDown={(e) => e.key === "Escape" && toggleSearch()}
onChange={setFilterText}
stateKey={stateKey ? `filter.${stateKey}` : null}
/>
@@ -87,10 +87,10 @@ export function TextViewer({ language, text, stateKey, pretty, className, onFilt
key="icon"
size="sm"
isLoading={filteredResponse.isPending}
icon={isSearching ? 'x' : 'filter'}
title={isSearching ? 'Close filter' : 'Filter response'}
icon={isSearching ? "x" : "filter"}
title={isSearching ? "Close filter" : "Filter response"}
onClick={toggleSearch}
className={classNames('border !border-border-subtle', isSearching && '!opacity-100')}
className={classNames("border !border-border-subtle", isSearching && "!opacity-100")}
/>,
);
@@ -115,18 +115,18 @@ export function TextViewer({ language, text, stateKey, pretty, className, onFilt
let body: string;
if (isSearching && filterText?.length > 0) {
if (filteredResponse.error) {
body = '';
body = "";
} else {
body = filteredResponse.data != null ? filteredResponse.data : '';
body = filteredResponse.data != null ? filteredResponse.data : "";
}
} else {
body = formattedBody;
}
// Decode unicode sequences in the text to readable characters
if (language === 'json' && pretty) {
if (language === "json" && pretty) {
body = decodeUnicodeLiterals(body);
body = body.replace(/\\\//g, '/'); // Hide unnecessary escaping of '/' by some older frameworks
body = body.replace(/\\\//g, "/"); // Hide unnecessary escaping of '/' by some older frameworks
}
return (

View File

@@ -1,5 +1,5 @@
import { convertFileSrc } from '@tauri-apps/api/core';
import { useEffect, useState } from 'react';
import { convertFileSrc } from "@tauri-apps/api/core";
import { useEffect, useState } from "react";
interface Props {
bodyPath?: string;
@@ -13,7 +13,7 @@ export function VideoViewer({ bodyPath, data }: Props) {
if (bodyPath) {
setSrc(convertFileSrc(bodyPath));
} else if (data) {
const blob = new Blob([new Uint8Array(data)], { type: 'video/mp4' });
const blob = new Blob([new Uint8Array(data)], { type: "video/mp4" });
const url = URL.createObjectURL(blob);
setSrc(url);
return () => URL.revokeObjectURL(url);

View File

@@ -1,4 +1,4 @@
import { useMemo } from 'react';
import { useMemo } from "react";
interface Props {
html: string;
@@ -7,7 +7,7 @@ interface Props {
export function WebPageViewer({ html, baseUrl }: Props) {
const contentForIframe: string | undefined = useMemo(() => {
if (baseUrl && html.includes('<head>')) {
if (baseUrl && html.includes("<head>")) {
return html.replace(/<head>/gi, `<head><base href="${baseUrl}"/>`);
}
return html;
@@ -16,7 +16,7 @@ export function WebPageViewer({ html, baseUrl }: Props) {
return (
<div className="h-full pb-3">
<iframe
key={html ? 'has-body' : 'no-body'}
key={html ? "has-body" : "no-body"}
title="Yaak response preview"
srcDoc={contentForIframe}
sandbox="allow-scripts allow-forms"