mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-07-02 19:11:39 +02:00
Compare commits
4 Commits
v2026.5.0-beta.3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 95ac3e310a | |||
| 9b524e3dc7 | |||
| bdf78254b5 | |||
| c5545c8781 |
@@ -19,10 +19,12 @@ Generate formatted markdown release notes for a Yaak tag.
|
||||
- `gh pr view <PR_NUMBER> --json number,title,body,author,url`
|
||||
5. Extract useful details:
|
||||
- Feedback URLs (`feedback.yaak.app`)
|
||||
- Contributor GitHub handles from `author.login`
|
||||
- Plugin install links or other notable context
|
||||
6. Format notes using Yaak style:
|
||||
- Changelog badge at top
|
||||
- Bulleted items with PR links where available
|
||||
- Contributor handles for external PRs
|
||||
- Feedback links where available
|
||||
- Full changelog compare link at bottom
|
||||
|
||||
@@ -31,6 +33,7 @@ Generate formatted markdown release notes for a Yaak tag.
|
||||
- Wrap final notes in a markdown code fence.
|
||||
- Keep a blank line before and after the code fence.
|
||||
- Output the markdown code block last.
|
||||
- Append contributor attribution to PR-backed bullets for non-`@gschier` authors, using `by [@handle](https://github.com/handle)`.
|
||||
- Do not append `by @gschier` for PRs authored by `@gschier`.
|
||||
- These are app release notes. Exclude CLI-only changes (commits prefixed with `cli:` or only touching `crates-cli/`) since the CLI has its own release process.
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
name: Update Flathub
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag to publish to Flathub
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -10,8 +15,6 @@ jobs:
|
||||
update-flathub:
|
||||
name: Update Flathub manifest
|
||||
runs-on: ubuntu-latest
|
||||
# Only run for stable releases (skip betas/pre-releases)
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
steps:
|
||||
- name: Checkout app repo
|
||||
uses: actions/checkout@v4
|
||||
@@ -39,7 +42,7 @@ jobs:
|
||||
git clone --depth 1 https://github.com/flatpak/flatpak-builder-tools flatpak/flatpak-builder-tools
|
||||
|
||||
- name: Run update-manifest.sh
|
||||
run: bash flatpak/update-manifest.sh "${{ github.event.release.tag_name }}" flathub-repo
|
||||
run: bash flatpak/update-manifest.sh "${{ inputs.tag }}" flathub-repo
|
||||
|
||||
- name: Commit and push to Flathub
|
||||
working-directory: flathub-repo
|
||||
@@ -48,5 +51,5 @@ jobs:
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add -A
|
||||
git diff --cached --quiet && echo "No changes to commit" && exit 0
|
||||
git commit -m "Update to ${{ github.event.release.tag_name }}"
|
||||
git commit -m "Update to ${{ inputs.tag }}"
|
||||
git push
|
||||
|
||||
@@ -10,7 +10,7 @@ export function openFolderSettings(folderId: string, tab?: FolderSettingsTab) {
|
||||
id: "folder-settings",
|
||||
title: null,
|
||||
size: "lg",
|
||||
className: "h-[50rem]",
|
||||
className: "h-200",
|
||||
noPadding: true,
|
||||
render: () => <FolderSettingsDialog folderId={folderId} tab={tab} />,
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ export function BinaryFileEditor({
|
||||
<VStack space={2}>
|
||||
<SelectFile onChange={handleChange} filePath={filePath} />
|
||||
{filePath != null && mimeType !== contentType && !ignoreContentType.value && (
|
||||
<Banner className="mt-3 !py-5">
|
||||
<Banner className="mt-3 py-5!">
|
||||
<div className="mb-4 text-center">
|
||||
<div>Set Content-Type header</div>
|
||||
<InlineCode>{mimeType}</InlineCode> for current request?
|
||||
|
||||
@@ -108,7 +108,7 @@ export function CloneGitRepositoryDialog({ hide }: Props) {
|
||||
rightSlot={
|
||||
<IconButton
|
||||
size="xs"
|
||||
className="mr-0.5 !h-auto my-0.5"
|
||||
className="mr-0.5 h-auto! my-0.5"
|
||||
icon="folder"
|
||||
title="Browse"
|
||||
onClick={handleSelectDirectory}
|
||||
|
||||
@@ -11,7 +11,7 @@ export function ColorIndicator({ color, onClick, className }: Props) {
|
||||
const style: CSSProperties = { backgroundColor: color ?? undefined };
|
||||
const finalClassName = classNames(
|
||||
className,
|
||||
"inline-block w-[0.75em] h-[0.75em] rounded-full mr-1.5 border border-transparent flex-shrink-0",
|
||||
"inline-block w-[0.75em] h-[0.75em] rounded-full mr-1.5 border border-transparent shrink-0",
|
||||
);
|
||||
|
||||
if (onClick) {
|
||||
|
||||
@@ -439,7 +439,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
name="command"
|
||||
label="Command"
|
||||
placeholder="Search or type a command"
|
||||
className="font-sans !text-base"
|
||||
className="font-sans text-base!"
|
||||
defaultValue={command}
|
||||
onChange={handleSetCommand}
|
||||
onKeyDownCapture={handleKeyDown}
|
||||
@@ -448,7 +448,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
<div className="h-full px-1.5 overflow-y-auto pt-2 pb-1">
|
||||
{filteredGroups.map((g) => (
|
||||
<div key={g.key} className="mb-1.5 w-full">
|
||||
<Heading level={2} className="!text-xs uppercase px-1.5 h-sm flex items-center">
|
||||
<Heading level={2} className="text-xs! uppercase px-1.5 h-sm flex items-center">
|
||||
{g.label}
|
||||
</Heading>
|
||||
{g.items.map((v) => (
|
||||
@@ -491,7 +491,7 @@ function CommandPaletteItem({
|
||||
color="custom"
|
||||
justify="start"
|
||||
className={classNames(
|
||||
"w-full h-sm flex items-center rounded px-1.5",
|
||||
"w-full h-sm flex items-center rounded-sm px-1.5",
|
||||
"hover:text-text",
|
||||
active && "bg-surface-highlight",
|
||||
!active && "text-text-subtle",
|
||||
|
||||
@@ -155,7 +155,7 @@ export const CookieDialog = ({ cookieJarId }: Props) => {
|
||||
rightSlot={
|
||||
filter.length > 0 && (
|
||||
<IconButton
|
||||
className="!bg-transparent !h-auto min-h-full opacity-50 hover:opacity-100 -mr-1"
|
||||
className="bg-transparent! h-auto! min-h-full opacity-50 hover:opacity-100 -mr-1"
|
||||
icon="x"
|
||||
title="Clear filter"
|
||||
onClick={() => {
|
||||
@@ -239,7 +239,7 @@ export const CookieDialog = ({ cookieJarId }: Props) => {
|
||||
<TableCell className={classNames("pl-2", isSelected && "rounded-l")}>
|
||||
{c.name}
|
||||
</TableCell>
|
||||
<TruncatedWideTableCell className="min-w-[10rem]">
|
||||
<TruncatedWideTableCell className="min-w-40">
|
||||
{c.value}
|
||||
</TruncatedWideTableCell>
|
||||
<TableCell>{cookieDomain(c)}</TableCell>
|
||||
@@ -547,7 +547,7 @@ function CookieEditor({
|
||||
}
|
||||
|
||||
function CookieKeyValueRow({ labelClassName, ...props }: ComponentProps<typeof KeyValueRow>) {
|
||||
return <KeyValueRow labelClassName={classNames("w-[7rem]", labelClassName)} {...props} />;
|
||||
return <KeyValueRow labelClassName={classNames("w-28", labelClassName)} {...props} />;
|
||||
}
|
||||
|
||||
function CookieTextInput({
|
||||
@@ -589,7 +589,7 @@ function CookieTextarea({ onChange, value }: { onChange: (value: string) => void
|
||||
<textarea
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
className={classNames(cookieInputClassName, "min-h-[5rem] resize-y")}
|
||||
className={classNames(cookieInputClassName, "min-h-20 resize-y")}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
value={value}
|
||||
/>
|
||||
@@ -600,7 +600,7 @@ const NEW_COOKIE_KEY = "__new-cookie__";
|
||||
const NON_EMPTY_INPUT_PATTERN = ".*\\S.*";
|
||||
const cookieInputClassName = classNames(
|
||||
"x-theme-input w-full min-w-0 min-h-sm rounded-md bg-transparent",
|
||||
"border border-border-subtle outline-none",
|
||||
"border border-border-subtle outline-hidden",
|
||||
"px-2 text-xs font-mono cursor-text placeholder:text-placeholder",
|
||||
"focus:border-border-focus invalid:border-danger",
|
||||
"disabled:opacity-disabled disabled:border-dotted",
|
||||
|
||||
@@ -75,7 +75,7 @@ export function DnsOverridesEditor({ workspace }: Props) {
|
||||
<VStack space={3} className="pb-3">
|
||||
<div className="text-text-subtle text-sm">
|
||||
Override DNS resolution for specific hostnames. This works like{" "}
|
||||
<code className="text-text-subtlest bg-surface-highlight px-1 rounded">/etc/hosts</code> but
|
||||
<code className="text-text-subtlest bg-surface-highlight px-1 rounded-sm">/etc/hosts</code> but
|
||||
only for requests made from this workspace.
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ export const DropMarker = memo(
|
||||
<div
|
||||
className={classNames(
|
||||
"absolute bg-primary rounded-full",
|
||||
orientation === "horizontal" && "left-2 right-2 -bottom-[0.1rem] h-[0.2rem]",
|
||||
orientation === "vertical" && "-left-[0.1rem] top-0 bottom-0 w-[0.2rem]",
|
||||
orientation === "horizontal" && "left-2 right-2 bottom-[-0.1rem] h-[0.2rem]",
|
||||
orientation === "vertical" && "left-[-0.1rem] top-0 bottom-0 w-[0.2rem]",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -204,7 +204,7 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
<div key={i + stateKey}>
|
||||
<DetailsBanner
|
||||
summary={input.label}
|
||||
className={classNames("!mb-auto", disabled && "opacity-disabled")}
|
||||
className={classNames("mb-auto!", disabled && "opacity-disabled")}
|
||||
>
|
||||
<div className="mt-3">
|
||||
<FormInputsStack
|
||||
@@ -300,7 +300,7 @@ function TextArg({
|
||||
onChange,
|
||||
name: arg.name,
|
||||
multiLine: arg.multiLine,
|
||||
className: arg.multiLine ? "min-h-[4rem]" : undefined,
|
||||
className: arg.multiLine ? "min-h-16" : undefined,
|
||||
defaultValue: value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value,
|
||||
required: !arg.optional,
|
||||
disabled: arg.disabled,
|
||||
@@ -359,7 +359,7 @@ function EditorArg({
|
||||
className={classNames(
|
||||
"border border-border rounded-md overflow-hidden px-2 py-1",
|
||||
"focus-within:border-border-focus",
|
||||
!arg.rows && "max-h-[10rem]", // So it doesn't take up too much space
|
||||
!arg.rows && "max-h-40", // So it doesn't take up too much space
|
||||
)}
|
||||
style={arg.rows ? { height: `${arg.rows * 1.4 + 0.75}rem` } : undefined}
|
||||
>
|
||||
@@ -372,7 +372,7 @@ function EditorArg({
|
||||
onChange={onChange}
|
||||
hideGutter
|
||||
heightMode="auto"
|
||||
className="min-h-[3rem]"
|
||||
className="min-h-12"
|
||||
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
||||
placeholder={arg.placeholder ?? undefined}
|
||||
autocompleteFunctions={autocompleteFunctions}
|
||||
@@ -392,7 +392,7 @@ function EditorArg({
|
||||
id: "id",
|
||||
size: "full",
|
||||
title: arg.readOnly ? "View Value" : "Edit Value",
|
||||
className: "!max-w-[50rem] !max-h-[60rem]",
|
||||
className: "max-w-200! max-h-240!",
|
||||
description: arg.label && (
|
||||
<Label
|
||||
htmlFor={id}
|
||||
|
||||
@@ -62,7 +62,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
size="sm"
|
||||
className={classNames(
|
||||
className,
|
||||
"text !px-2 truncate",
|
||||
"text px-2! truncate",
|
||||
!activeEnvironment && !hasBaseVars && "text-text-subtlest italic",
|
||||
)}
|
||||
// If no environments, the button simply opens the dialog.
|
||||
|
||||
@@ -57,7 +57,7 @@ export function EnvironmentEditDialog({ initialEnvironmentId, setRef }: Props) {
|
||||
defaultRatio={0.75}
|
||||
layout="horizontal"
|
||||
className="gap-0"
|
||||
resizeHandleClassName="-translate-x-[1px]"
|
||||
resizeHandleClassName="-translate-x-px"
|
||||
firstSlot={() => (
|
||||
<EnvironmentEditDialogSidebar
|
||||
selectedEnvironmentId={selectedEnvironment?.id ?? null}
|
||||
|
||||
@@ -163,7 +163,7 @@ function HttpRequestCard({ request }: { request: HttpRequest }) {
|
||||
|
||||
return (
|
||||
<div className="grid grid-rows-2 grid-cols-[minmax(0,1fr)] gap-2 overflow-hidden">
|
||||
<code className="font-mono text-editor text-info border border-info rounded px-2.5 py-0.5 truncate w-full min-w-0">
|
||||
<code className="font-mono text-editor text-info border border-info rounded-sm px-2.5 py-0.5 truncate w-full min-w-0">
|
||||
{request.method} {request.url}
|
||||
</code>
|
||||
{latestResponse ? (
|
||||
@@ -190,7 +190,7 @@ function HttpRequestCard({ request }: { request: HttpRequest }) {
|
||||
className={classNames(
|
||||
"cursor-default select-none",
|
||||
"whitespace-nowrap w-full pl-3 overflow-x-auto font-mono text-sm hide-scrollbars",
|
||||
"font-mono text-editor border rounded px-1.5 py-0.5 truncate w-full",
|
||||
"font-mono text-editor border rounded-sm px-1.5 py-0.5 truncate w-full",
|
||||
)}
|
||||
>
|
||||
{latestResponse.state !== "closed" && <LoadingIcon size="sm" />}
|
||||
|
||||
@@ -84,12 +84,12 @@ export function FolderSettingsDialog({ folderId, tab }: Props) {
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex items-center gap-3 px-6 pr-10 mt-4 mb-2 min-w-0 text-xl">
|
||||
<Icon icon="folder_cog" size="lg" color="secondary" className="flex-shrink-0" />
|
||||
<Icon icon="folder_cog" size="lg" color="secondary" className="shrink-0" />
|
||||
<div className="flex items-center gap-1.5 font-semibold text-text min-w-0 overflow-hidden flex-1">
|
||||
{breadcrumbs.map((item, index) => (
|
||||
<Fragment key={item.id}>
|
||||
{index > 0 && (
|
||||
<Icon icon="chevron_right" size="lg" className="opacity-50 flex-shrink-0" />
|
||||
<Icon icon="chevron_right" size="lg" className="opacity-50 shrink-0" />
|
||||
)}
|
||||
<span className="text-text-subtle truncate min-w-0" title={item.name}>
|
||||
{item.name}
|
||||
@@ -97,7 +97,7 @@ export function FolderSettingsDialog({ folderId, tab }: Props) {
|
||||
</Fragment>
|
||||
))}
|
||||
{breadcrumbs.length > 0 && (
|
||||
<Icon icon="chevron_right" size="lg" className="opacity-50 flex-shrink-0" />
|
||||
<Icon icon="chevron_right" size="lg" className="opacity-50 shrink-0" />
|
||||
)}
|
||||
<span className="whitespace-nowrap" title={folder.name}>
|
||||
{folder.name}
|
||||
@@ -149,7 +149,7 @@ export function FolderSettingsDialog({ folderId, tab }: Props) {
|
||||
<InlineCode className="flex gap-1 items-center text-primary pl-2.5">
|
||||
{folder.id}
|
||||
<CopyIconButton
|
||||
className="opacity-70 !text-primary"
|
||||
className="opacity-70 text-primary!"
|
||||
size="2xs"
|
||||
iconSize="sm"
|
||||
title="Copy folder ID"
|
||||
|
||||
@@ -126,7 +126,7 @@ export function GrpcEditor({
|
||||
|
||||
const actions = useMemo(
|
||||
() => [
|
||||
<div key="reflection" className={classNames(services == null && "!opacity-100")}>
|
||||
<div key="reflection" className={classNames(services == null && "opacity-100!")}>
|
||||
<Button
|
||||
size="xs"
|
||||
color={
|
||||
|
||||
@@ -162,7 +162,7 @@ export function GrpcRequestPane({
|
||||
className={classNames(
|
||||
"grid grid-cols-[minmax(0,1fr)_auto] gap-1.5",
|
||||
paneWidth === 0 && "opacity-0",
|
||||
paneWidth > 0 && paneWidth < 400 && "!grid-cols-1",
|
||||
paneWidth > 0 && paneWidth < 400 && "grid-cols-1!",
|
||||
)}
|
||||
>
|
||||
<UrlBar
|
||||
@@ -201,7 +201,7 @@ export function GrpcRequestPane({
|
||||
rightSlot={<Icon size="sm" icon="chevron_down" />}
|
||||
disabled={isStreaming || services == null}
|
||||
className={classNames(
|
||||
"font-mono text-editor min-w-[5rem] !ring-0",
|
||||
"font-mono text-editor min-w-20 ring-0!",
|
||||
paneWidth < 400 && "flex-1",
|
||||
)}
|
||||
>
|
||||
@@ -259,7 +259,7 @@ export function GrpcRequestPane({
|
||||
<Tabs
|
||||
label="Request"
|
||||
tabs={tabs}
|
||||
tabListClassName="mt-1 !mb-1.5"
|
||||
tabListClassName="mt-1 mb-1.5!"
|
||||
storageKey="grpc_request_tabs"
|
||||
activeTabKey={activeRequest.id}
|
||||
>
|
||||
@@ -296,7 +296,7 @@ export function GrpcRequestPane({
|
||||
hideLabel
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
defaultValue={activeRequest.name}
|
||||
className="font-sans !text-xl !px-0"
|
||||
className="font-sans text-xl! px-0!"
|
||||
containerClassName="border-0"
|
||||
placeholder={resolvedModelName(activeRequest)}
|
||||
onChange={(name) => patchModel(activeRequest, { name })}
|
||||
|
||||
@@ -156,7 +156,7 @@ export function HttpAuthenticationEditor({ model }: Props) {
|
||||
title="Authentication Actions"
|
||||
icon="settings"
|
||||
size="xs"
|
||||
className="!text-secondary"
|
||||
className="text-secondary!"
|
||||
/>
|
||||
</Dropdown>
|
||||
)}
|
||||
|
||||
@@ -57,7 +57,7 @@ export function HttpRequestLayout({ activeRequest, style }: Props) {
|
||||
<GraphQLDocsExplorer
|
||||
requestId={activeRequest.id}
|
||||
schema={graphQLSchema}
|
||||
className={classNames(orientation === "horizontal" && "!ml-0")}
|
||||
className={classNames(orientation === "horizontal" && "ml-0!")}
|
||||
style={style}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -346,7 +346,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
onUrlChange={handleUrlChange}
|
||||
leftSlot={
|
||||
<div className="py-0.5">
|
||||
<RequestMethodDropdown request={activeRequest} className="ml-0.5 !h-full" />
|
||||
<RequestMethodDropdown request={activeRequest} className="ml-0.5 h-full!" />
|
||||
</div>
|
||||
}
|
||||
forceUpdateKey={updateKey}
|
||||
@@ -456,7 +456,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
hideLabel
|
||||
forceUpdateKey={updateKey}
|
||||
defaultValue={activeRequest.name}
|
||||
className="font-sans !text-xl !px-0"
|
||||
className="font-sans text-xl! px-0!"
|
||||
containerClassName="border-0"
|
||||
placeholder={resolvedModelName(activeRequest)}
|
||||
onChange={(name) => patchModel(activeRequest, { name })}
|
||||
|
||||
@@ -4,10 +4,12 @@ import classNames from "classnames";
|
||||
import type { ComponentType, CSSProperties } from "react";
|
||||
import { lazy, Suspense, useMemo } from "react";
|
||||
import { useCancelHttpResponse } from "../hooks/useCancelHttpResponse";
|
||||
import { useCopyHttpResponse } from "../hooks/useCopyHttpResponse";
|
||||
import { useHttpResponseEvents } from "../hooks/useHttpResponseEvents";
|
||||
import { usePinnedHttpResponse } from "../hooks/usePinnedHttpResponse";
|
||||
import { useResponseBodyBytes, useResponseBodyText } from "../hooks/useResponseBodyText";
|
||||
import { useResponseViewMode } from "../hooks/useResponseViewMode";
|
||||
import { useSaveResponse } from "../hooks/useSaveResponse";
|
||||
import { useTimelineViewMode } from "../hooks/useTimelineViewMode";
|
||||
import { getMimeTypeFromContentType } from "../lib/contentType";
|
||||
import { getContentTypeFromHeaders, getCookieCounts } from "../lib/model_util";
|
||||
@@ -78,6 +80,8 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
activeResponse?.state === "closed" && redirectDropWarning != null;
|
||||
|
||||
const cookieCounts = useMemo(() => getCookieCounts(responseEvents.data), [responseEvents.data]);
|
||||
const saveResponse = useSaveResponse(activeResponse ?? null);
|
||||
const copyResponse = useCopyHttpResponse(activeResponse ?? null);
|
||||
|
||||
const tabs = useMemo<TabItem[]>(
|
||||
() => [
|
||||
@@ -93,6 +97,22 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
? []
|
||||
: [{ label: "Response (Raw)", shortLabel: "Raw", value: "raw" }]),
|
||||
],
|
||||
itemsAfter: [
|
||||
{
|
||||
label: "Save to File",
|
||||
onSelect: saveResponse.mutate,
|
||||
leftSlot: <Icon icon="save" />,
|
||||
hidden: activeResponse == null || !!activeResponse.error,
|
||||
disabled: activeResponse?.state !== "closed" && (activeResponse?.status ?? 0) >= 100,
|
||||
},
|
||||
{
|
||||
label: "Copy Body",
|
||||
onSelect: copyResponse.mutate,
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
hidden: activeResponse == null || !!activeResponse.error,
|
||||
disabled: activeResponse?.state !== "closed" && (activeResponse?.status ?? 0) >= 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -135,12 +155,18 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
],
|
||||
[
|
||||
activeResponse?.headers,
|
||||
activeResponse,
|
||||
activeResponse?.error,
|
||||
activeResponse?.requestContentLength,
|
||||
activeResponse?.requestHeaders.length,
|
||||
activeResponse?.state,
|
||||
activeResponse?.status,
|
||||
cookieCounts.sent,
|
||||
cookieCounts.received,
|
||||
copyResponse.mutate,
|
||||
mimeType,
|
||||
responseEvents.data?.length,
|
||||
saveResponse.mutate,
|
||||
setViewMode,
|
||||
viewMode,
|
||||
timelineViewMode,
|
||||
@@ -167,7 +193,7 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
<div className="h-full w-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1">
|
||||
<HStack
|
||||
className={classNames(
|
||||
"text-text-subtle w-full flex-shrink-0",
|
||||
"text-text-subtle w-full shrink-0",
|
||||
// Remove a bit of space because the tabs have lots too
|
||||
"-mb-1.5",
|
||||
)}
|
||||
@@ -180,7 +206,7 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
"whitespace-nowrap w-full pl-3 overflow-x-auto font-mono text-sm hide-scrollbars",
|
||||
)}
|
||||
>
|
||||
<HStack space={2} className="w-full flex-shrink-0">
|
||||
<HStack space={2} className="w-full shrink-0">
|
||||
{activeResponse.state !== "closed" && <LoadingIcon size="sm" />}
|
||||
<HttpStatusTag showReason response={activeResponse} />
|
||||
<span>•</span>
|
||||
@@ -194,7 +220,7 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
{shouldShowRedirectDropWarning ? (
|
||||
<Tooltip
|
||||
tabIndex={0}
|
||||
className="my-auto pl-3 flex-shrink-0 max-w-full justify-self-end overflow-hidden"
|
||||
className="my-auto pl-3 shrink-0 max-w-full justify-self-end overflow-hidden"
|
||||
content={
|
||||
<VStack alignItems="start" space={1} className="text-xs">
|
||||
<span className="font-medium text-warning">
|
||||
@@ -223,7 +249,7 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
<span className="inline-flex min-w-0">
|
||||
<PillButton
|
||||
color="warning"
|
||||
className="font-sans text-sm !flex-shrink max-w-full"
|
||||
className="font-sans text-sm shrink! max-w-full"
|
||||
innerClassName="flex items-center"
|
||||
leftSlot={<Icon icon="alert_triangle" size="xs" color="warning" />}
|
||||
>
|
||||
@@ -236,7 +262,7 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<div className="justify-self-end flex-shrink-0">
|
||||
<div className="justify-self-end shrink-0">
|
||||
<RecentHttpResponsesDropdown
|
||||
responses={responses}
|
||||
activeResponse={activeResponse}
|
||||
@@ -249,7 +275,7 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
|
||||
<div className="overflow-hidden flex flex-col min-h-0">
|
||||
{activeResponse?.error && (
|
||||
<Banner color="danger" className="mx-3 mt-1 flex-shrink-0">
|
||||
<Banner color="danger" className="mx-3 mt-1 shrink-0">
|
||||
{activeResponse.error}
|
||||
</Banner>
|
||||
)}
|
||||
|
||||
@@ -73,14 +73,14 @@ export function JsonBodyEditor({ forceUpdateKey, heightMode, request }: Props) {
|
||||
const actions = useMemo<EditorProps["actions"]>(
|
||||
() => [
|
||||
showBanner && (
|
||||
<Banner color="notice" className="!opacity-100 h-sm !py-0 !px-2 flex items-center text-xs">
|
||||
<Banner color="notice" className="opacity-100! h-sm py-0! px-2! flex items-center text-xs">
|
||||
<p className="inline-flex items-center gap-1 min-w-0">
|
||||
<span className="truncate">Auto-fix enabled</span>
|
||||
<Icon icon="arrow_right" size="sm" className="opacity-disabled" />
|
||||
</p>
|
||||
</Banner>
|
||||
),
|
||||
<div key="settings" className="!opacity-100 !shadow">
|
||||
<div key="settings" className="opacity-100! shadow!">
|
||||
<Dropdown
|
||||
onOpen={handleDropdownOpen}
|
||||
items={
|
||||
|
||||
@@ -59,7 +59,7 @@ function getDetail(
|
||||
label: `License expired ${formatDate(data.data.periodEnd, "MMM dd, yyyy")}`,
|
||||
},
|
||||
{
|
||||
label: <div className="min-w-[12rem]">Renew License</div>,
|
||||
label: <div className="min-w-48">Renew License</div>,
|
||||
leftSlot: <Icon icon="refresh" />,
|
||||
rightSlot: <Icon icon="external_link" size="sm" className="opacity-disabled" />,
|
||||
hidden: data.data.changesUrl == null,
|
||||
|
||||
@@ -33,7 +33,7 @@ export function MarkdownEditor({
|
||||
<Editor
|
||||
hideGutter
|
||||
wrapLines
|
||||
className={classNames(editorClassName, "[&_.cm-line]:!max-w-lg max-h-full")}
|
||||
className={classNames(editorClassName, "[&_.cm-line]:max-w-lg! max-h-full")}
|
||||
language="markdown"
|
||||
defaultValue={defaultValue}
|
||||
onChange={onChange}
|
||||
@@ -46,7 +46,7 @@ export function MarkdownEditor({
|
||||
defaultValue.length === 0 ? (
|
||||
<p className="text-text-subtlest">No description</p>
|
||||
) : (
|
||||
<div className="pr-1.5 overflow-y-auto max-h-full [&_*]:cursor-auto [&_*]:select-auto">
|
||||
<div className="pr-1.5 overflow-y-auto max-h-full **:cursor-auto **:select-auto">
|
||||
<Markdown className="max-w-lg select-auto cursor-auto">{defaultValue}</Markdown>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -539,7 +539,7 @@ function NumberUnitInput({
|
||||
placeholder={placeholder}
|
||||
defaultValue={value}
|
||||
className="[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
||||
containerClassName="!w-48"
|
||||
containerClassName="w-48!"
|
||||
validate={validate}
|
||||
rightSlot={
|
||||
<span className="flex self-stretch items-center border-l border-border-subtle px-2 text-xs font-medium text-text-subtle">
|
||||
|
||||
@@ -66,7 +66,7 @@ export function MoveToWorkspaceDialog({ onDone, requests, activeWorkspaceId }: P
|
||||
<Button
|
||||
size="xs"
|
||||
color="secondary"
|
||||
className="mr-auto min-w-[5rem]"
|
||||
className="mr-auto min-w-20"
|
||||
onClick={async () => {
|
||||
await router.navigate({
|
||||
to: "/workspaces/$workspaceId",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@reference "../main.css";
|
||||
|
||||
.prose {
|
||||
@apply text-text;
|
||||
|
||||
@@ -98,7 +100,7 @@
|
||||
@apply text-notice hover:underline;
|
||||
|
||||
* {
|
||||
@apply text-notice !important;
|
||||
@apply text-notice!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,12 +115,12 @@
|
||||
ol code,
|
||||
ul code {
|
||||
@apply text-xs bg-surface-active text-info font-normal whitespace-nowrap;
|
||||
@apply px-1.5 py-0.5 rounded not-italic;
|
||||
@apply px-1.5 py-0.5 rounded-sm not-italic;
|
||||
@apply select-text;
|
||||
}
|
||||
|
||||
pre {
|
||||
@apply bg-surface-highlight text-text !important;
|
||||
@apply bg-surface-highlight! text-text!;
|
||||
@apply px-4 py-3 rounded-md;
|
||||
@apply overflow-auto whitespace-pre;
|
||||
@apply text-editor font-mono;
|
||||
@@ -130,7 +132,7 @@
|
||||
|
||||
.banner {
|
||||
@apply border border-dashed;
|
||||
@apply border-border bg-surface-highlight text-text px-4 py-3 rounded text-base;
|
||||
@apply border-border bg-surface-highlight text-text px-4 py-3 rounded-sm text-base;
|
||||
|
||||
&::before {
|
||||
@apply block font-bold mb-1;
|
||||
@@ -161,7 +163,7 @@
|
||||
}
|
||||
|
||||
blockquote {
|
||||
@apply italic py-3 pl-5 pr-3 border-l-8 border-surface-active text-lg text-text bg-surface-highlight rounded shadow-lg;
|
||||
@apply italic py-3 pl-5 pr-3 border-l-8 border-surface-active text-lg text-text bg-surface-highlight rounded-sm shadow-lg;
|
||||
|
||||
p {
|
||||
@apply m-0;
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import type { GrpcConnection } from "@yaakapp-internal/models";
|
||||
import { deleteModel } from "@yaakapp-internal/models";
|
||||
import { HStack, Icon } from "@yaakapp-internal/ui";
|
||||
import { formatDistanceToNowStrict } from "date-fns";
|
||||
import {
|
||||
differenceInHours,
|
||||
differenceInMinutes,
|
||||
format,
|
||||
isToday,
|
||||
isYesterday,
|
||||
} from "date-fns";
|
||||
import { useDeleteGrpcConnections } from "../hooks/useDeleteGrpcConnections";
|
||||
import { pluralizeCount } from "../lib/pluralize";
|
||||
import { Dropdown } from "./core/Dropdown";
|
||||
import { Dropdown, type DropdownItem } from "./core/Dropdown";
|
||||
import { formatMillis } from "./core/HttpResponseDurationTag";
|
||||
import { IconButton } from "./core/IconButton";
|
||||
|
||||
interface Props {
|
||||
@@ -20,6 +27,63 @@ export function RecentGrpcConnectionsDropdown({
|
||||
}: Props) {
|
||||
const deleteAllConnections = useDeleteGrpcConnections(activeConnection?.requestId);
|
||||
const latestConnectionId = connections[0]?.id ?? "n/a";
|
||||
const connectionHistoryItems: DropdownItem[] = [];
|
||||
let lastHistoryGroup: string | null = null;
|
||||
let hasRecentConnections = false;
|
||||
let hasShownRecentEmptyState = false;
|
||||
const now = new Date();
|
||||
|
||||
for (const c of connections) {
|
||||
const createdAt = `${c.createdAt}Z`;
|
||||
const createdAtDate = new Date(createdAt);
|
||||
const minutesAgo = differenceInMinutes(now, createdAtDate);
|
||||
const hoursAgo = differenceInHours(now, createdAtDate);
|
||||
let historyGroup = format(createdAtDate, "MMM d, yyyy");
|
||||
if (minutesAgo < 5) historyGroup = "Just now";
|
||||
else if (minutesAgo < 15) historyGroup = "5 minutes ago";
|
||||
else if (minutesAgo < 60) historyGroup = "15 minutes ago";
|
||||
else if (hoursAgo < 3) historyGroup = "1 hour ago";
|
||||
else if (hoursAgo < 6) historyGroup = "3 hours ago";
|
||||
else if (isToday(createdAtDate)) historyGroup = "Today";
|
||||
else if (isYesterday(createdAtDate)) historyGroup = "Yesterday";
|
||||
else if (createdAtDate.getFullYear() === now.getFullYear()) historyGroup = format(createdAtDate, "MMM d");
|
||||
const absoluteTime = format(createdAt, "MMM d, yyyy, h:mm:ss a O");
|
||||
|
||||
if (historyGroup === "Just now") {
|
||||
hasRecentConnections = true;
|
||||
} else if (!hasRecentConnections && !hasShownRecentEmptyState) {
|
||||
connectionHistoryItems.push({
|
||||
type: "content",
|
||||
label: <span className="block px-4 py-1 text-sm text-text-subtle">No recent connections</span>,
|
||||
});
|
||||
hasShownRecentEmptyState = true;
|
||||
}
|
||||
|
||||
if (historyGroup !== "Just now" && historyGroup !== lastHistoryGroup) {
|
||||
connectionHistoryItems.push({
|
||||
type: "separator",
|
||||
label: <span title={absoluteTime}>{historyGroup}</span>,
|
||||
});
|
||||
lastHistoryGroup = historyGroup;
|
||||
}
|
||||
|
||||
connectionHistoryItems.push({
|
||||
label: (
|
||||
<HStack space={2} className="text-sm" title={absoluteTime}>
|
||||
<span className="font-mono">{formatMillis(c.elapsed)}</span>
|
||||
</HStack>
|
||||
),
|
||||
leftSlot: activeConnection?.id === c.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
onSelect: () => onPinnedConnectionId(c.id),
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasRecentConnections && !hasShownRecentEmptyState) {
|
||||
connectionHistoryItems.push({
|
||||
type: "content",
|
||||
label: <span className="block px-4 py-1 text-sm text-text-subtle">No recent connections</span>,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
@@ -36,16 +100,7 @@ export function RecentGrpcConnectionsDropdown({
|
||||
disabled: connections.length === 0,
|
||||
},
|
||||
{ type: "separator", label: "History" },
|
||||
...connections.map((c) => ({
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
{formatDistanceToNowStrict(`${c.createdAt}Z`)} ago •{" "}
|
||||
<span className="font-mono text-sm">{c.elapsed}ms</span>
|
||||
</HStack>
|
||||
),
|
||||
leftSlot: activeConnection?.id === c.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
onSelect: () => onPinnedConnectionId(c.id),
|
||||
})),
|
||||
...connectionHistoryItems,
|
||||
]}
|
||||
>
|
||||
<IconButton
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import type { HttpResponse } from "@yaakapp-internal/models";
|
||||
import { deleteModel } from "@yaakapp-internal/models";
|
||||
import { HStack, Icon } from "@yaakapp-internal/ui";
|
||||
import { useCopyHttpResponse } from "../hooks/useCopyHttpResponse";
|
||||
import {
|
||||
differenceInHours,
|
||||
differenceInMinutes,
|
||||
format,
|
||||
isToday,
|
||||
isYesterday,
|
||||
} from "date-fns";
|
||||
import { useDeleteHttpResponses } from "../hooks/useDeleteHttpResponses";
|
||||
import { useSaveResponse } from "../hooks/useSaveResponse";
|
||||
import { pluralize } from "../lib/pluralize";
|
||||
import { Dropdown } from "./core/Dropdown";
|
||||
import { useKeyValue } from "../hooks/useKeyValue";
|
||||
import { DismissibleBanner } from "./core/DismissibleBanner";
|
||||
import { Dropdown, type DropdownItem } from "./core/Dropdown";
|
||||
import { formatMillis } from "./core/HttpResponseDurationTag";
|
||||
import { HttpStatusTag } from "./core/HttpStatusTag";
|
||||
import { IconButton } from "./core/IconButton";
|
||||
import { SizeTag } from "./core/SizeTag";
|
||||
|
||||
interface Props {
|
||||
responses: HttpResponse[];
|
||||
@@ -22,32 +30,93 @@ export const RecentHttpResponsesDropdown = function ResponsePane({
|
||||
onPinnedResponseId,
|
||||
}: Props) {
|
||||
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
||||
const movedActionsBannerId = "response-actions-moved-to-response-menu-2026-07-02-v2";
|
||||
const { value: dismissedMovedActions } = useKeyValue<boolean>({
|
||||
namespace: "global",
|
||||
key: ["dismiss-banner", movedActionsBannerId],
|
||||
fallback: false,
|
||||
});
|
||||
const latestResponseId = responses[0]?.id ?? "n/a";
|
||||
const saveResponse = useSaveResponse(activeResponse);
|
||||
const copyResponse = useCopyHttpResponse(activeResponse);
|
||||
const responseHistoryItems: DropdownItem[] = [];
|
||||
let lastHistoryGroup: string | null = null;
|
||||
let hasRecentResponses = false;
|
||||
let hasShownRecentEmptyState = false;
|
||||
const now = new Date();
|
||||
|
||||
for (const r of responses) {
|
||||
const createdAt = `${r.createdAt}Z`;
|
||||
const createdAtDate = new Date(createdAt);
|
||||
const minutesAgo = differenceInMinutes(now, createdAtDate);
|
||||
const hoursAgo = differenceInHours(now, createdAtDate);
|
||||
let historyGroup = format(createdAtDate, "MMM d, yyyy");
|
||||
if (minutesAgo < 5) historyGroup = "Just now";
|
||||
else if (minutesAgo < 15) historyGroup = "5 minutes ago";
|
||||
else if (minutesAgo < 60) historyGroup = "15 minutes ago";
|
||||
else if (hoursAgo < 3) historyGroup = "1 hour ago";
|
||||
else if (hoursAgo < 6) historyGroup = "3 hours ago";
|
||||
else if (isToday(createdAtDate)) historyGroup = "Today";
|
||||
else if (isYesterday(createdAtDate)) historyGroup = "Yesterday";
|
||||
else if (createdAtDate.getFullYear() === now.getFullYear()) historyGroup = format(createdAtDate, "MMM d");
|
||||
const absoluteTime = format(createdAt, "MMM d, yyyy, h:mm:ss a O");
|
||||
|
||||
if (historyGroup === "Just now") {
|
||||
hasRecentResponses = true;
|
||||
} else if (!hasRecentResponses && !hasShownRecentEmptyState) {
|
||||
responseHistoryItems.push({
|
||||
type: "content",
|
||||
label: <span className="block px-4 py-1 text-sm text-text-subtle">No recent requests</span>,
|
||||
});
|
||||
hasShownRecentEmptyState = true;
|
||||
}
|
||||
|
||||
if (historyGroup !== "Just now" && historyGroup !== lastHistoryGroup) {
|
||||
responseHistoryItems.push({
|
||||
type: "separator",
|
||||
label: <span title={absoluteTime}>{historyGroup}</span>,
|
||||
});
|
||||
lastHistoryGroup = historyGroup;
|
||||
}
|
||||
|
||||
responseHistoryItems.push({
|
||||
label: (
|
||||
<HStack space={2} className="text-sm" title={absoluteTime}>
|
||||
<HttpStatusTag short className="text-xs" response={r} />
|
||||
<span className="text-text-subtlest">•</span>
|
||||
<span className="font-mono">{r.elapsed >= 0 ? formatMillis(r.elapsed) : "n/a"}</span>
|
||||
<span className="text-text-subtlest">•</span>
|
||||
<SizeTag
|
||||
className="text-xs"
|
||||
contentLength={r.contentLength ?? 0}
|
||||
contentLengthCompressed={r.contentLengthCompressed}
|
||||
/>
|
||||
</HStack>
|
||||
),
|
||||
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
onSelect: () => onPinnedResponseId(r.id),
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasRecentResponses && !hasShownRecentEmptyState) {
|
||||
responseHistoryItems.push({
|
||||
type: "content",
|
||||
label: <span className="block px-4 py-1 text-sm text-text-subtle">No recent requests</span>,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
label: "Save to File",
|
||||
onSelect: saveResponse.mutate,
|
||||
leftSlot: <Icon icon="save" />,
|
||||
hidden: responses.length === 0 || !!activeResponse.error,
|
||||
disabled: activeResponse.state !== "closed" && activeResponse.status >= 100,
|
||||
},
|
||||
{
|
||||
label: "Copy Body",
|
||||
onSelect: copyResponse.mutate,
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
hidden: responses.length === 0 || !!activeResponse.error,
|
||||
disabled: activeResponse.state !== "closed" && activeResponse.status >= 100,
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: () => deleteModel(activeResponse),
|
||||
},
|
||||
{
|
||||
label: "Delete all",
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: deleteAllResponses.mutate,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{
|
||||
label: "Unpin Response",
|
||||
onSelect: () => onPinnedResponseId(activeResponse.id),
|
||||
@@ -55,25 +124,25 @@ export const RecentHttpResponsesDropdown = function ResponsePane({
|
||||
hidden: latestResponseId === activeResponse.id,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{ type: "separator", label: "History" },
|
||||
{
|
||||
label: `Delete ${responses.length} ${pluralize("Response", responses.length)}`,
|
||||
onSelect: deleteAllResponses.mutate,
|
||||
hidden: responses.length === 0,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{ type: "separator" },
|
||||
...responses.map((r: HttpResponse) => ({
|
||||
type: "content",
|
||||
hidden: dismissedMovedActions === true,
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
<HttpStatusTag short className="text-xs" response={r} />
|
||||
<span className="text-text-subtle">→</span>{" "}
|
||||
<span className="font-mono text-sm">{r.elapsed >= 0 ? `${r.elapsed}ms` : "n/a"}</span>
|
||||
</HStack>
|
||||
<DismissibleBanner
|
||||
id={movedActionsBannerId}
|
||||
color="info"
|
||||
size="xs"
|
||||
className="max-w-72"
|
||||
>
|
||||
<p>Copy and save actions moved to the Response tab menu.</p>
|
||||
</DismissibleBanner>
|
||||
),
|
||||
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
onSelect: () => onPinnedResponseId(r.id),
|
||||
})),
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
label: "Recent",
|
||||
},
|
||||
...responseHistoryItems,
|
||||
]}
|
||||
>
|
||||
<IconButton
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import type { WebsocketConnection } from "@yaakapp-internal/models";
|
||||
import { deleteModel, getModel } from "@yaakapp-internal/models";
|
||||
import { HStack, Icon } from "@yaakapp-internal/ui";
|
||||
import { formatDistanceToNowStrict } from "date-fns";
|
||||
import {
|
||||
differenceInHours,
|
||||
differenceInMinutes,
|
||||
format,
|
||||
isToday,
|
||||
isYesterday,
|
||||
} from "date-fns";
|
||||
import { deleteWebsocketConnections } from "../commands/deleteWebsocketConnections";
|
||||
import { pluralizeCount } from "../lib/pluralize";
|
||||
import { Dropdown } from "./core/Dropdown";
|
||||
import { Dropdown, type DropdownItem } from "./core/Dropdown";
|
||||
import { formatMillis } from "./core/HttpResponseDurationTag";
|
||||
import { IconButton } from "./core/IconButton";
|
||||
|
||||
interface Props {
|
||||
@@ -19,6 +26,63 @@ export function RecentWebsocketConnectionsDropdown({
|
||||
onPinnedConnectionId,
|
||||
}: Props) {
|
||||
const latestConnectionId = connections[0]?.id ?? "n/a";
|
||||
const connectionHistoryItems: DropdownItem[] = [];
|
||||
let lastHistoryGroup: string | null = null;
|
||||
let hasRecentConnections = false;
|
||||
let hasShownRecentEmptyState = false;
|
||||
const now = new Date();
|
||||
|
||||
for (const c of connections) {
|
||||
const createdAt = `${c.createdAt}Z`;
|
||||
const createdAtDate = new Date(createdAt);
|
||||
const minutesAgo = differenceInMinutes(now, createdAtDate);
|
||||
const hoursAgo = differenceInHours(now, createdAtDate);
|
||||
let historyGroup = format(createdAtDate, "MMM d, yyyy");
|
||||
if (minutesAgo < 5) historyGroup = "Just now";
|
||||
else if (minutesAgo < 15) historyGroup = "5 minutes ago";
|
||||
else if (minutesAgo < 60) historyGroup = "15 minutes ago";
|
||||
else if (hoursAgo < 3) historyGroup = "1 hour ago";
|
||||
else if (hoursAgo < 6) historyGroup = "3 hours ago";
|
||||
else if (isToday(createdAtDate)) historyGroup = "Today";
|
||||
else if (isYesterday(createdAtDate)) historyGroup = "Yesterday";
|
||||
else if (createdAtDate.getFullYear() === now.getFullYear()) historyGroup = format(createdAtDate, "MMM d");
|
||||
const absoluteTime = format(createdAt, "MMM d, yyyy, h:mm:ss a O");
|
||||
|
||||
if (historyGroup === "Just now") {
|
||||
hasRecentConnections = true;
|
||||
} else if (!hasRecentConnections && !hasShownRecentEmptyState) {
|
||||
connectionHistoryItems.push({
|
||||
type: "content",
|
||||
label: <span className="block px-4 py-1 text-sm text-text-subtle">No recent connections</span>,
|
||||
});
|
||||
hasShownRecentEmptyState = true;
|
||||
}
|
||||
|
||||
if (historyGroup !== "Just now" && historyGroup !== lastHistoryGroup) {
|
||||
connectionHistoryItems.push({
|
||||
type: "separator",
|
||||
label: <span title={absoluteTime}>{historyGroup}</span>,
|
||||
});
|
||||
lastHistoryGroup = historyGroup;
|
||||
}
|
||||
|
||||
connectionHistoryItems.push({
|
||||
label: (
|
||||
<HStack space={2} className="text-sm" title={absoluteTime}>
|
||||
<span className="font-mono">{formatMillis(c.elapsed)}</span>
|
||||
</HStack>
|
||||
),
|
||||
leftSlot: activeConnection?.id === c.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
onSelect: () => onPinnedConnectionId(c.id),
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasRecentConnections && !hasShownRecentEmptyState) {
|
||||
connectionHistoryItems.push({
|
||||
type: "content",
|
||||
label: <span className="block px-4 py-1 text-sm text-text-subtle">No recent connections</span>,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
@@ -40,16 +104,7 @@ export function RecentWebsocketConnectionsDropdown({
|
||||
disabled: connections.length === 0,
|
||||
},
|
||||
{ type: "separator", label: "History" },
|
||||
...connections.map((c) => ({
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
{formatDistanceToNowStrict(`${c.createdAt}Z`)} ago •{" "}
|
||||
<span className="font-mono text-sm">{c.elapsed}ms</span>
|
||||
</HStack>
|
||||
),
|
||||
leftSlot: activeConnection?.id === c.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
onSelect: () => onPinnedConnectionId(c.id),
|
||||
})),
|
||||
...connectionHistoryItems,
|
||||
]}
|
||||
>
|
||||
<IconButton
|
||||
|
||||
@@ -167,7 +167,7 @@ export function ResponseCookies({ response }: Props) {
|
||||
{cookie.value}
|
||||
</span>
|
||||
{cookie.isDeleted && (
|
||||
<span className="text-xs font-sans text-danger bg-danger/10 px-1.5 py-0.5 rounded">
|
||||
<span className="text-xs font-sans text-danger bg-danger/10 px-1.5 py-0.5 rounded-sm">
|
||||
Deleted
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import type { HttpResponse } from "@yaakapp-internal/models";
|
||||
import { format, formatDistanceToNowStrict } from "date-fns";
|
||||
import { useMemo } from "react";
|
||||
import { CountBadge } from "./core/CountBadge";
|
||||
import { DetailsBanner } from "./core/DetailsBanner";
|
||||
@@ -29,12 +30,20 @@ export function ResponseHeaders({ response }: Props) {
|
||||
<div className="overflow-auto h-full pb-4 gap-y-3 flex flex-col pr-0.5">
|
||||
<DetailsBanner storageKey={`${response.requestId}.general`} summary={<h2>Info</h2>}>
|
||||
<KeyValueRows>
|
||||
<KeyValueRow labelColor="secondary" label="Sent">
|
||||
<time
|
||||
dateTime={new Date(`${response.createdAt}Z`).toISOString()}
|
||||
title={formatDistanceToNowStrict(`${response.createdAt}Z`, { addSuffix: true })}
|
||||
>
|
||||
{format(`${response.createdAt}Z`, "MMM d, yyyy, h:mm:ss a O")}
|
||||
</time>
|
||||
</KeyValueRow>
|
||||
<KeyValueRow labelColor="secondary" label="Request URL">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="select-text cursor-text">{response.url}</span>
|
||||
<IconButton
|
||||
iconSize="sm"
|
||||
className="inline-block w-auto !h-auto opacity-50 hover:opacity-100"
|
||||
className="inline-block w-auto h-auto! opacity-50 hover:opacity-100"
|
||||
icon="external_link"
|
||||
onClick={() => openUrl(response.url)}
|
||||
title="Open in browser"
|
||||
|
||||
@@ -24,7 +24,7 @@ export function ResponseInfo({ response }: Props) {
|
||||
URL
|
||||
<IconButton
|
||||
iconSize="sm"
|
||||
className="inline-block w-auto ml-1 !h-auto opacity-50 hover:opacity-100"
|
||||
className="inline-block w-auto ml-1 h-auto! opacity-50 hover:opacity-100"
|
||||
icon="external_link"
|
||||
onClick={() => openUrl(response.url)}
|
||||
title="Open in browser"
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function RouteError({ error }: { error: unknown }) {
|
||||
typeof error === "object" && error != null && "stack" in error ? String(error.stack) : null;
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<VStack space={5} className="w-[50rem] !h-auto">
|
||||
<VStack space={5} className="w-200 h-auto!">
|
||||
<Heading>Route Error 🔥</Heading>
|
||||
<FormattedError>
|
||||
{message}
|
||||
|
||||
@@ -108,7 +108,7 @@ export function SelectFile({
|
||||
"rtl mr-1.5",
|
||||
inline && "w-full",
|
||||
filePath && inline && "font-mono text-xs",
|
||||
isHovering && "!border-notice",
|
||||
isHovering && "border-notice!",
|
||||
)}
|
||||
color={isHovering ? "primary" : "secondary"}
|
||||
onClick={handleClick}
|
||||
|
||||
@@ -93,7 +93,7 @@ export default function Settings({ hide }: Props) {
|
||||
layout="horizontal"
|
||||
defaultValue={mainTab || tabFromQuery}
|
||||
addBorders
|
||||
tabListClassName="min-w-[10rem] bg-surface x-theme-sidebar border-r border-border pl-3"
|
||||
tabListClassName="min-w-40 bg-surface x-theme-sidebar border-r border-border pl-3"
|
||||
label="Settings"
|
||||
tabs={tabs.map(
|
||||
(value): TabItem => ({
|
||||
@@ -131,28 +131,28 @@ export default function Settings({ hide }: Props) {
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full px-6 !py-4">
|
||||
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full px-6 py-4!">
|
||||
<SettingsGeneral />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_INTERFACE} className="overflow-y-auto h-full px-6 !py-4">
|
||||
<TabContent value={TAB_INTERFACE} className="overflow-y-auto h-full px-6 py-4!">
|
||||
<SettingsInterface />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_THEME} className="overflow-y-auto h-full px-6 !py-4">
|
||||
<TabContent value={TAB_THEME} className="overflow-y-auto h-full px-6 py-4!">
|
||||
<SettingsTheme />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_SHORTCUTS} className="overflow-y-auto h-full px-6 !py-4">
|
||||
<TabContent value={TAB_SHORTCUTS} className="overflow-y-auto h-full px-6 py-4!">
|
||||
<SettingsHotkeys />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_PLUGINS} className="h-full grid grid-rows-1">
|
||||
<SettingsPlugins defaultSubtab={mainTab === TAB_PLUGINS ? subtab : undefined} />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_PROXY} className="overflow-y-auto h-full px-6 !py-4">
|
||||
<TabContent value={TAB_PROXY} className="overflow-y-auto h-full px-6 py-4!">
|
||||
<SettingsProxy />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_CERTIFICATES} className="overflow-y-auto h-full px-6 !py-4">
|
||||
<TabContent value={TAB_CERTIFICATES} className="overflow-y-auto h-full px-6 py-4!">
|
||||
<SettingsCertificates />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_LICENSE} className="overflow-y-auto h-full px-6 !py-4">
|
||||
<TabContent value={TAB_LICENSE} className="overflow-y-auto h-full px-6 py-4!">
|
||||
<SettingsLicense />
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
|
||||
@@ -56,7 +56,7 @@ export function SettingsGeneral() {
|
||||
model={settings}
|
||||
modelKey="updateChannel"
|
||||
label="Update Channel"
|
||||
selectClassName="!w-full"
|
||||
selectClassName="w-full!"
|
||||
options={[
|
||||
{ label: "Stable", value: "stable" },
|
||||
{ label: "Beta", value: "beta" },
|
||||
|
||||
@@ -341,7 +341,7 @@ function RecordHotkeyDialog({ label, onSave, onCancel }: RecordHotkeyDialogProps
|
||||
}}
|
||||
className={classNames(
|
||||
"flex items-center justify-center",
|
||||
"px-4 py-2 rounded-lg bg-surface-highlight border outline-none cursor-default w-full",
|
||||
"px-4 py-2 rounded-lg bg-surface-highlight border outline-hidden cursor-default w-full",
|
||||
"border-border-subtle focus:border-border-focus",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -89,7 +89,7 @@ export function SettingsInterface() {
|
||||
<SettingSelectControl
|
||||
name="uiFont"
|
||||
label="Interface font"
|
||||
selectClassName="!w-72"
|
||||
selectClassName="w-72!"
|
||||
value={settings.interfaceFont ?? NULL_FONT_VALUE}
|
||||
defaultValue={NULL_FONT_VALUE}
|
||||
options={[
|
||||
@@ -106,7 +106,7 @@ export function SettingsInterface() {
|
||||
<SettingSelectControl
|
||||
name="interfaceFontSize"
|
||||
label="Interface Font Size"
|
||||
selectClassName="!w-20"
|
||||
selectClassName="w-20!"
|
||||
value={`${settings.interfaceFontSize}`}
|
||||
defaultValue="14"
|
||||
options={fontSizeOptions}
|
||||
@@ -123,7 +123,7 @@ export function SettingsInterface() {
|
||||
<SettingSelectControl
|
||||
name="editorFont"
|
||||
label="Editor font"
|
||||
selectClassName="!w-72"
|
||||
selectClassName="w-72!"
|
||||
value={settings.editorFont ?? NULL_FONT_VALUE}
|
||||
defaultValue={NULL_FONT_VALUE}
|
||||
options={[
|
||||
@@ -139,7 +139,7 @@ export function SettingsInterface() {
|
||||
<SettingSelectControl
|
||||
name="editorFontSize"
|
||||
label="Editor Font Size"
|
||||
selectClassName="!w-20"
|
||||
selectClassName="w-20!"
|
||||
value={`${settings.editorFontSize}`}
|
||||
defaultValue="12"
|
||||
options={fontSizeOptions}
|
||||
|
||||
@@ -211,7 +211,7 @@ function PluginTableRow({
|
||||
return (
|
||||
<TableRow>
|
||||
{showCheckbox && (
|
||||
<TableCell className="!py-0">
|
||||
<TableCell className="py-0!">
|
||||
<Checkbox
|
||||
hideLabel
|
||||
title={plugin?.enabled ? "Disable plugin" : "Enable plugin"}
|
||||
@@ -249,7 +249,7 @@ function PluginTableRow({
|
||||
)}
|
||||
</HStack>
|
||||
</TableCell>
|
||||
<TableCell className="!py-0">
|
||||
<TableCell className="py-0!">
|
||||
<HStack justifyContent="end" space={1.5}>
|
||||
{plugin != null && latestVersion != null ? (
|
||||
<Button
|
||||
|
||||
@@ -56,7 +56,7 @@ export function SettingsProxy() {
|
||||
{ label: "Custom proxy configuration", value: "enabled" },
|
||||
{ label: "No proxy", value: "disabled" },
|
||||
]}
|
||||
selectClassName="!w-64"
|
||||
selectClassName="w-64!"
|
||||
/>
|
||||
</SettingsSection>
|
||||
|
||||
@@ -99,7 +99,7 @@ export function SettingsProxy() {
|
||||
description="Comma-separated list of hosts that should bypass the proxy."
|
||||
value={settings.proxy.bypass}
|
||||
placeholder="127.0.0.1, *.example.com, localhost:3000"
|
||||
inputWidthClassName="!w-96"
|
||||
inputWidthClassName="w-96!"
|
||||
onChange={(bypass) => patchProxy({ bypass })}
|
||||
/>
|
||||
</SettingsSection>
|
||||
|
||||
@@ -120,7 +120,7 @@ export function SettingsTheme() {
|
||||
<SettingsSection title="Preview">
|
||||
<VStack
|
||||
space={3}
|
||||
className="mt-4 w-full bg-surface p-3 border border-dashed border-border-subtle rounded overflow-x-auto"
|
||||
className="mt-4 w-full bg-surface p-3 border border-dashed border-border-subtle rounded-sm overflow-x-auto"
|
||||
>
|
||||
<HStack className="text" space={1.5}>
|
||||
<Icon icon={appearance === "dark" ? "moon" : "sun"} />
|
||||
|
||||
@@ -588,7 +588,7 @@ function Sidebar({ className }: { className?: string }) {
|
||||
rightSlot={
|
||||
filterText.text && (
|
||||
<IconButton
|
||||
className="!bg-transparent !h-auto min-h-full opacity-50 hover:opacity-100 -mr-1"
|
||||
className="bg-transparent! h-auto! min-h-full opacity-50 hover:opacity-100 -mr-1"
|
||||
icon="x"
|
||||
title="Clear filter"
|
||||
onClick={clearFilterText}
|
||||
@@ -667,8 +667,8 @@ function Sidebar({ className }: { className?: string }) {
|
||||
<div className="p-3 text-sm text-center">
|
||||
{(emptyFilterSuggestions?.length ?? 0) > 0 ? (
|
||||
<EmptyStateText
|
||||
wrapperClassName="!h-auto mb-auto"
|
||||
className="!h-auto py-3 px-3 !text-text-subtle text-sm leading-relaxed text-center"
|
||||
wrapperClassName="h-auto! mb-auto"
|
||||
className="h-auto! py-3 px-3 text-text-subtle! text-sm leading-relaxed text-center"
|
||||
>
|
||||
<div>
|
||||
No results, but found matches for{" "}
|
||||
@@ -677,7 +677,7 @@ function Sidebar({ className }: { className?: string }) {
|
||||
{i > 0 && " or "}
|
||||
<button
|
||||
type="button"
|
||||
className="max-w-full rounded align-middle focus-visible:outline focus-visible:outline-2 focus-visible:outline-info"
|
||||
className="max-w-full rounded-sm align-middle focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-info"
|
||||
onClick={() => applyFilterExample(suggestion.filterText)}
|
||||
>
|
||||
<InlineCode className="inline-block max-w-36 truncate align-middle whitespace-nowrap transition-colors hover:border-border hover:bg-surface-active hover:text-text">
|
||||
@@ -690,8 +690,8 @@ function Sidebar({ className }: { className?: string }) {
|
||||
</EmptyStateText>
|
||||
) : (
|
||||
<EmptyStateText
|
||||
wrapperClassName="!h-auto mb-auto"
|
||||
className="!h-auto py-3 px-3 !text-text-subtle text-sm leading-relaxed text-center"
|
||||
wrapperClassName="h-auto! mb-auto"
|
||||
className="h-auto! py-3 px-3 text-text-subtle! text-sm leading-relaxed text-center"
|
||||
>
|
||||
<div>
|
||||
No results for{" "}
|
||||
|
||||
@@ -208,10 +208,10 @@ function InitializedTemplateFunctionDialog({
|
||||
)}
|
||||
/>
|
||||
</HStack>
|
||||
<div className="relative w-full max-h-[10rem]">
|
||||
<div className="relative w-full max-h-40">
|
||||
<InlineCode
|
||||
className={classNames(
|
||||
"block whitespace-pre-wrap !select-text cursor-text max-h-[10rem] overflow-auto hide-scrollbars !border-text-subtlest",
|
||||
"block whitespace-pre-wrap select-text! cursor-text max-h-40 overflow-auto hide-scrollbars border-text-subtlest!",
|
||||
tooLarge && "italic text-danger",
|
||||
)}
|
||||
>
|
||||
@@ -246,7 +246,7 @@ function InitializedTemplateFunctionDialog({
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<div className="flex justify-stretch w-full flex-grow gap-2 [&>*]:flex-1">
|
||||
<div className="flex justify-stretch w-full grow gap-2 *:flex-1">
|
||||
{templateFunction.data.name === "secure" && (
|
||||
<Button variant="border" color="secondary" onClick={setupOrConfigureEncryption}>
|
||||
Reveal Encryption Key
|
||||
@@ -271,7 +271,7 @@ TemplateFunctionDialog.show = (
|
||||
showDialog({
|
||||
id: `template-function-${Math.random()}`, // Allow multiple at once
|
||||
size: "md",
|
||||
className: "h-[60rem]",
|
||||
className: "h-240",
|
||||
noPadding: true,
|
||||
title: <InlineCode>{fn.name}(…)</InlineCode>,
|
||||
description: fn.description,
|
||||
|
||||
@@ -94,7 +94,7 @@ export const UrlBar = memo(function UrlBar({
|
||||
iconSize="md"
|
||||
title="Send Request"
|
||||
type="submit"
|
||||
className="w-8 mr-0.5 !h-full"
|
||||
className="w-8 mr-0.5 h-full!"
|
||||
iconColor="secondary"
|
||||
icon={isLoading ? "x" : submitIcon}
|
||||
hotkeyAction="request.send"
|
||||
|
||||
@@ -217,7 +217,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
title="Close connection"
|
||||
icon="x"
|
||||
iconColor="secondary"
|
||||
className="w-8 mr-0.5 !h-full"
|
||||
className="w-8 mr-0.5 h-full!"
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
)
|
||||
@@ -236,7 +236,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
ref={tabsRef}
|
||||
label="Request"
|
||||
tabs={tabs}
|
||||
tabListClassName="mt-1 !mb-1.5"
|
||||
tabListClassName="mt-1 mb-1.5!"
|
||||
storageKey={TABS_STORAGE_KEY}
|
||||
activeTabKey={activeRequestId}
|
||||
>
|
||||
@@ -283,7 +283,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
hideLabel
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
defaultValue={activeRequest.name}
|
||||
className="font-sans !text-xl !px-0"
|
||||
className="font-sans text-xl! px-0!"
|
||||
containerClassName="border-0"
|
||||
placeholder={resolvedModelName(activeRequest)}
|
||||
onChange={(name) => patchModel(activeRequest, { name })}
|
||||
|
||||
@@ -85,7 +85,7 @@ export function Workspace() {
|
||||
<div style={environmentBgStyle} className="absolute inset-0 opacity-[0.07]" />
|
||||
<div
|
||||
style={environmentBgStyle}
|
||||
className="absolute left-0 right-0 -bottom-[1px] h-[1px] opacity-20"
|
||||
className="absolute left-0 right-0 -bottom-px h-px opacity-20"
|
||||
/>
|
||||
</div>
|
||||
<WorkspaceHeader className="pointer-events-none" floatingSidebar={floating} />
|
||||
@@ -162,7 +162,7 @@ function WorkspaceBody() {
|
||||
// Delay the entering because the workspaces might load after a slight delay
|
||||
transition={{ delay: 0.5 }}
|
||||
>
|
||||
<Banner color="warning" className="max-w-[30rem]">
|
||||
<Banner color="warning" className="max-w-120">
|
||||
The active workspace was not found. Select a workspace from the header menu or report this
|
||||
bug to <FeedbackLink />
|
||||
</Banner>
|
||||
|
||||
@@ -176,7 +176,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
size="sm"
|
||||
className={classNames(
|
||||
className,
|
||||
"text !px-2 truncate",
|
||||
"text px-2! truncate",
|
||||
workspace === null && "italic opacity-disabled",
|
||||
)}
|
||||
{...buttonProps}
|
||||
|
||||
@@ -324,7 +324,7 @@ function KeyRevealer({
|
||||
|
||||
function HighlightedKey({ keyText, show }: { keyText: string; show: boolean }) {
|
||||
return (
|
||||
<span className="text-xs font-mono [&_*]:cursor-auto [&_*]:select-text">
|
||||
<span className="text-xs font-mono **:cursor-auto **:select-text">
|
||||
{show ? (
|
||||
keyText.split("").map((c, i) => {
|
||||
return (
|
||||
|
||||
@@ -127,7 +127,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
|
||||
placeholder="Workspace Name"
|
||||
label="Name"
|
||||
defaultValue={workspace.name}
|
||||
className="!text-base font-sans"
|
||||
className="text-base! font-sans"
|
||||
onChange={(name) => patchModel(workspace, { name })}
|
||||
/>
|
||||
|
||||
@@ -161,7 +161,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
|
||||
<InlineCode className="flex gap-1 items-center text-primary pl-2.5">
|
||||
{workspaceId}
|
||||
<CopyIconButton
|
||||
className="opacity-70 !text-primary"
|
||||
className="opacity-70 text-primary!"
|
||||
size="2xs"
|
||||
iconSize="sm"
|
||||
title="Copy workspace ID"
|
||||
@@ -182,7 +182,7 @@ WorkspaceSettingsDialog.show = (workspaceId: string, tab?: WorkspaceSettingsTab)
|
||||
showDialog({
|
||||
id: "workspace-settings",
|
||||
size: "lg",
|
||||
className: "h-[calc(100vh-5rem)] !max-h-[50rem]",
|
||||
className: "h-[calc(100vh-5rem)] max-h-200!",
|
||||
noPadding: true,
|
||||
render: ({ hide }) => (
|
||||
<WorkspaceSettingsDialog workspaceId={workspaceId} hide={hide} tab={tab} />
|
||||
|
||||
@@ -72,7 +72,7 @@ export function AutoScroller<T>({
|
||||
size="sm"
|
||||
iconSize="md"
|
||||
variant="border"
|
||||
className="!bg-surface z-10"
|
||||
className="bg-surface! z-10"
|
||||
onClick={() => setAutoScroll((v) => !v)}
|
||||
/>
|
||||
</div>
|
||||
@@ -80,7 +80,7 @@ export function AutoScroller<T>({
|
||||
{header ?? <span aria-hidden />}
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="h-full w-full overflow-y-auto focus:outline-none"
|
||||
className="h-full w-full overflow-y-auto focus:outline-hidden"
|
||||
onScroll={handleScroll}
|
||||
tabIndex={focusable ? 0 : undefined}
|
||||
>
|
||||
|
||||
@@ -39,10 +39,10 @@ export function Checkbox({
|
||||
<input
|
||||
aria-hidden
|
||||
className={classNames(
|
||||
"appearance-none flex-shrink-0 border border-border",
|
||||
"appearance-none shrink-0 border border-border",
|
||||
size === "sm" && "w-4 h-4",
|
||||
size === "md" && "w-5 h-5",
|
||||
"rounded outline-none ring-0",
|
||||
"rounded-sm outline-hidden ring-0",
|
||||
!disabled && "hocus:border-border-focus hocus:bg-focus/[5%]",
|
||||
disabled && "border-dotted",
|
||||
)}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function ColorPicker({ onChange, color, className }: Props) {
|
||||
<div className={className}>
|
||||
<HexColorPicker
|
||||
color={color ?? undefined}
|
||||
className="!w-full"
|
||||
className="w-full!"
|
||||
onChange={(color) => {
|
||||
onChange(color);
|
||||
regenerateKey(); // To force input to change
|
||||
@@ -96,7 +96,7 @@ export function ColorPickerWithThemeColors({ onChange, color, className }: Props
|
||||
<>
|
||||
<HexColorPicker
|
||||
color={color ?? undefined}
|
||||
className="!w-full"
|
||||
className="w-full!"
|
||||
onChange={(color) => {
|
||||
onChange(color);
|
||||
regenerateKey(); // To force input to change
|
||||
|
||||
@@ -18,7 +18,7 @@ export function CountBadge({ count, count2, className, color, showZero }: Props)
|
||||
className={classNames(
|
||||
className,
|
||||
"flex items-center",
|
||||
"opacity-70 border text-4xs rounded mb-0.5 px-1 ml-1 h-4 font-mono",
|
||||
"opacity-70 border text-4xs rounded-sm mb-0.5 px-1 ml-1 h-4 font-mono",
|
||||
color == null && "border-border-subtle",
|
||||
color === "primary" && "text-primary",
|
||||
color === "secondary" && "text-secondary",
|
||||
|
||||
@@ -42,7 +42,7 @@ export function DetailsBanner({
|
||||
return (
|
||||
<Banner color={color} className={className}>
|
||||
<details className="group list-none" open={isOpen} onToggle={handleToggle} {...extraProps}>
|
||||
<summary className="!cursor-default !select-none list-none flex items-center gap-3 focus:outline-none opacity-70">
|
||||
<summary className="cursor-default! select-none! list-none flex items-center gap-3 focus:outline-hidden opacity-70">
|
||||
<div
|
||||
className={classNames(
|
||||
"transition-transform",
|
||||
|
||||
@@ -74,13 +74,13 @@ export function Dialog({
|
||||
"relative bg-surface pointer-events-auto",
|
||||
"rounded-lg",
|
||||
"border border-border-subtle shadow-lg shadow-[rgba(0,0,0,0.1)]",
|
||||
"min-h-[10rem]",
|
||||
"min-h-40",
|
||||
"max-w-[calc(100vw-5rem)] max-h-[calc(100vh-5rem)]",
|
||||
size === "sm" && "w-[30rem]",
|
||||
size === "md" && "w-[50rem]",
|
||||
size === "lg" && "w-[70rem]",
|
||||
size === "full" && "w-[100vw] h-[100vh]",
|
||||
size === "dynamic" && "min-w-[20rem] max-w-[100vw]",
|
||||
size === "sm" && "w-120",
|
||||
size === "md" && "w-200",
|
||||
size === "lg" && "w-280",
|
||||
size === "full" && "w-screen h-screen",
|
||||
size === "dynamic" && "min-w-80 max-w-[100vw]",
|
||||
)}
|
||||
>
|
||||
{title ? (
|
||||
|
||||
@@ -2,21 +2,26 @@ import type { Color } from "@yaakapp-internal/plugins";
|
||||
import type { BannerProps } from "@yaakapp-internal/ui";
|
||||
import { Banner } from "@yaakapp-internal/ui";
|
||||
import classNames from "classnames";
|
||||
import type { MouseEvent } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useKeyValue } from "../../hooks/useKeyValue";
|
||||
import type { ButtonProps } from "./Button";
|
||||
import { Button } from "./Button";
|
||||
|
||||
type DismissibleBannerSize = "sm" | "xs";
|
||||
|
||||
export function DismissibleBanner({
|
||||
children,
|
||||
className,
|
||||
id,
|
||||
size = "sm",
|
||||
onDismiss,
|
||||
onShow,
|
||||
actions,
|
||||
...props
|
||||
}: BannerProps & {
|
||||
id: string;
|
||||
size?: DismissibleBannerSize;
|
||||
onDismiss?: () => void | Promise<void>;
|
||||
onShow?: () => void | Promise<void>;
|
||||
actions?: {
|
||||
@@ -46,17 +51,36 @@ export function DismissibleBanner({
|
||||
|
||||
if (!shouldShow) return null;
|
||||
|
||||
const actionSize: ButtonProps["size"] = size === "xs" ? "2xs" : "xs";
|
||||
const stopParentClick = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<Banner className={classNames(className, "relative")} {...props}>
|
||||
<Banner
|
||||
className={classNames(
|
||||
className,
|
||||
"relative",
|
||||
size === "xs" && "!px-2 !py-2 text-xs",
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="@container">
|
||||
<div className="grid gap-2 @[34rem]:grid-cols-[minmax(0,1fr)_auto] @[34rem]:items-center @[34rem]:gap-3">
|
||||
<div
|
||||
className={classNames(
|
||||
"grid @[34rem]:grid-cols-[minmax(0,1fr)_auto] @[34rem]:items-center",
|
||||
size === "xs" ? "gap-1.5 @[34rem]:gap-2" : "gap-2 @[34rem]:gap-3",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<div className="flex flex-wrap gap-1.5 @[34rem]:justify-end">
|
||||
<Button
|
||||
variant="border"
|
||||
color={props.color}
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
size={actionSize}
|
||||
onClick={(event) => {
|
||||
stopParentClick(event);
|
||||
setDismissed(true).catch(console.error);
|
||||
Promise.resolve(onDismiss?.()).catch(console.error);
|
||||
}}
|
||||
@@ -69,8 +93,11 @@ export function DismissibleBanner({
|
||||
key={a.label}
|
||||
variant={a.variant ?? "border"}
|
||||
color={a.color ?? props.color}
|
||||
size="xs"
|
||||
onClick={a.onClick}
|
||||
size={actionSize}
|
||||
onClick={(event) => {
|
||||
stopParentClick(event);
|
||||
a.onClick();
|
||||
}}
|
||||
title={a.label}
|
||||
>
|
||||
{a.label}
|
||||
|
||||
@@ -712,7 +712,7 @@ const Menu = forwardRef<Omit<DropdownRef, "open" | "isOpen" | "toggle" | "items"
|
||||
className={classNames(
|
||||
className,
|
||||
"x-theme-menu",
|
||||
"outline-none my-1 pointer-events-auto z-40",
|
||||
"outline-hidden my-1 pointer-events-auto z-40",
|
||||
"fixed",
|
||||
)}
|
||||
>
|
||||
@@ -734,7 +734,7 @@ const Menu = forwardRef<Omit<DropdownRef, "open" | "isOpen" | "toggle" | "items"
|
||||
{filter && (
|
||||
<HStack
|
||||
space={2}
|
||||
className="pb-0.5 px-1.5 mb-2 text-sm border border-border-subtle mx-2 rounded font-mono h-xs"
|
||||
className="pb-0.5 px-1.5 mb-2 text-sm border border-border-subtle mx-2 rounded-sm font-mono h-xs"
|
||||
>
|
||||
<Icon icon="search" size="xs" />
|
||||
<div className="text">{filter}</div>
|
||||
@@ -916,24 +916,24 @@ function MenuItem({
|
||||
)
|
||||
}
|
||||
rightSlot={rightSlot && <div className="ml-auto pl-3">{rightSlot}</div>}
|
||||
innerClassName="!text-left"
|
||||
innerClassName="text-left!"
|
||||
color="custom"
|
||||
className={classNames(
|
||||
className,
|
||||
"h-xs", // More compact
|
||||
"min-w-[8rem] outline-none px-2 mx-1.5 flex whitespace-nowrap",
|
||||
"focus:bg-surface-highlight focus:text rounded focus:outline-none focus-visible:outline-1",
|
||||
isParentOfActiveSubmenu && "bg-surface-highlight text rounded",
|
||||
item.color === "danger" && "!text-danger",
|
||||
item.color === "primary" && "!text-primary",
|
||||
item.color === "success" && "!text-success",
|
||||
item.color === "warning" && "!text-warning",
|
||||
item.color === "notice" && "!text-notice",
|
||||
item.color === "info" && "!text-info",
|
||||
"min-w-32 outline-hidden px-2 mx-1.5 flex whitespace-nowrap",
|
||||
"focus:bg-surface-highlight focus:text rounded-sm focus:outline-hidden focus-visible:outline-1",
|
||||
isParentOfActiveSubmenu && "bg-surface-highlight text rounded-sm",
|
||||
item.color === "danger" && "text-danger!",
|
||||
item.color === "primary" && "text-primary!",
|
||||
item.color === "success" && "text-success!",
|
||||
item.color === "warning" && "text-warning!",
|
||||
item.color === "notice" && "text-notice!",
|
||||
item.color === "info" && "text-info!",
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className={classNames("truncate min-w-[5rem]")}>{item.label}</div>
|
||||
<div className={classNames("truncate min-w-20")}>{item.label}</div>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@reference "../../../main.css";
|
||||
|
||||
.cm-wrapper.cm-multiline .cm-mergeView {
|
||||
@apply h-full w-full overflow-auto pr-0.5;
|
||||
|
||||
@@ -9,7 +11,7 @@
|
||||
@apply w-full min-h-full relative;
|
||||
|
||||
.cm-collapsedLines {
|
||||
@apply bg-none bg-surface border border-border py-1 mx-0.5 text-text opacity-80 hover:opacity-100 rounded cursor-default;
|
||||
@apply bg-none bg-surface border border-border py-1 mx-0.5 text-text opacity-80 hover:opacity-100 rounded-sm cursor-default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,21 +21,21 @@
|
||||
.cm-changedLine {
|
||||
/* Round top corners only if previous line is not a changed line */
|
||||
&:not(.cm-changedLine + &) {
|
||||
@apply rounded-t;
|
||||
@apply rounded-t-sm;
|
||||
}
|
||||
/* Round bottom corners only if next line is not a changed line */
|
||||
&:not(:has(+ .cm-changedLine)) {
|
||||
@apply rounded-b;
|
||||
@apply rounded-b-sm;
|
||||
}
|
||||
}
|
||||
|
||||
/* Let content grow and disable individual scrolling for sync */
|
||||
.cm-editor {
|
||||
@apply h-auto relative !important;
|
||||
@apply h-auto! relative!;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.cm-scroller {
|
||||
@apply overflow-visible !important;
|
||||
@apply overflow-visible!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@reference "../../../main.css";
|
||||
|
||||
.cm-wrapper {
|
||||
@apply h-full overflow-hidden;
|
||||
|
||||
@@ -7,7 +9,7 @@
|
||||
/* Regular cursor */
|
||||
|
||||
.cm-cursor {
|
||||
@apply border-text !important;
|
||||
@apply border-text!;
|
||||
/* Widen the cursor a bit */
|
||||
@apply border-l-[2px];
|
||||
}
|
||||
@@ -15,8 +17,8 @@
|
||||
/* Vim-mode cursor */
|
||||
|
||||
.cm-fat-cursor {
|
||||
@apply outline-0 bg-text !important;
|
||||
@apply text-surface !important;
|
||||
@apply outline-0! bg-text!;
|
||||
@apply text-surface!;
|
||||
}
|
||||
|
||||
/* Matching bracket */
|
||||
@@ -59,12 +61,12 @@
|
||||
|
||||
* {
|
||||
@apply cursor-text;
|
||||
@apply caret-transparent !important;
|
||||
@apply caret-transparent!;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
@apply bg-selection !important;
|
||||
@apply bg-selection!;
|
||||
}
|
||||
|
||||
/* Fix WebKit/WKWebView rendering bug where selection layer leaves a ghost
|
||||
@@ -88,7 +90,7 @@
|
||||
}
|
||||
|
||||
.cm-gutter-lint {
|
||||
@apply w-auto !important;
|
||||
@apply w-auto!;
|
||||
|
||||
.cm-gutterElement {
|
||||
@apply px-0;
|
||||
@@ -111,7 +113,7 @@
|
||||
@apply bg-surface text-text-subtle border-border-subtle whitespace-nowrap cursor-default;
|
||||
@apply hover:border-border hover:text-text hover:bg-surface-highlight;
|
||||
|
||||
@apply inline border px-1 mx-[0.5px] rounded dark:shadow;
|
||||
@apply inline border px-1 mx-[0.5px] rounded-sm dark:shadow;
|
||||
|
||||
-webkit-text-security: none;
|
||||
|
||||
@@ -162,7 +164,7 @@
|
||||
|
||||
&::-webkit-scrollbar-corner,
|
||||
&::-webkit-scrollbar {
|
||||
@apply hidden !important;
|
||||
@apply hidden!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,16 +191,16 @@
|
||||
|
||||
/* Style search matches */
|
||||
.cm-searchMatch {
|
||||
@apply bg-transparent !important;
|
||||
@apply bg-transparent!;
|
||||
@apply rounded-[2px] outline outline-1;
|
||||
|
||||
&.cm-searchMatch-selected {
|
||||
@apply outline-text;
|
||||
@apply bg-text !important;
|
||||
@apply bg-text!;
|
||||
|
||||
&,
|
||||
* {
|
||||
@apply text-surface font-semibold !important;
|
||||
@apply text-surface! font-semibold!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,8 +225,8 @@
|
||||
}
|
||||
|
||||
.cm-editor .fold-gutter-icon {
|
||||
@apply pt-[0.25em] pl-[0.4em] px-[0.4em] h-4 rounded;
|
||||
@apply cursor-default !important;
|
||||
@apply pt-[0.25em] pl-[0.4em] px-[0.4em] h-4 rounded-sm;
|
||||
@apply cursor-default!;
|
||||
}
|
||||
|
||||
.cm-editor .fold-gutter-icon::after {
|
||||
@@ -248,7 +250,7 @@
|
||||
.cm-editor .cm-foldPlaceholder {
|
||||
@apply px-2 border border-border-subtle bg-surface-highlight;
|
||||
@apply hover:text-text hover:border-border-subtle text-text;
|
||||
@apply cursor-default !important;
|
||||
@apply cursor-default!;
|
||||
}
|
||||
|
||||
.cm-editor .cm-activeLineGutter {
|
||||
@@ -277,7 +279,7 @@
|
||||
}
|
||||
|
||||
.cm-tooltip-lint {
|
||||
@apply font-mono text-editor rounded overflow-hidden bg-surface-highlight border border-border shadow !important;
|
||||
@apply font-mono! text-editor! rounded-sm! overflow-hidden! bg-surface-highlight! border! border-border! shadow!;
|
||||
|
||||
.cm-diagnostic-error {
|
||||
@apply border-l-danger px-4 py-2;
|
||||
@@ -293,18 +295,18 @@
|
||||
}
|
||||
|
||||
.cm-tooltip.cm-tooltip-hover {
|
||||
@apply shadow-lg bg-surface rounded text-text-subtle border border-border-subtle z-50 pointer-events-auto text-sm;
|
||||
@apply shadow-lg bg-surface rounded-sm text-text-subtle border border-border-subtle z-50 pointer-events-auto text-sm;
|
||||
@apply p-1.5;
|
||||
|
||||
/* Style the tooltip for popping up "open in browser" and other stuff */
|
||||
|
||||
a,
|
||||
button {
|
||||
@apply text-text hover:bg-surface-highlight w-full h-sm flex items-center px-2 rounded;
|
||||
@apply text-text hover:bg-surface-highlight w-full h-sm flex items-center px-2 rounded-sm;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply cursor-default !important;
|
||||
@apply cursor-default!;
|
||||
|
||||
&::after {
|
||||
@apply text-text bg-text h-3 w-3 ml-1;
|
||||
@@ -319,10 +321,10 @@
|
||||
/* NOTE: Extra selector required to override default styles */
|
||||
.cm-tooltip.cm-tooltip-autocomplete,
|
||||
.cm-tooltip.cm-completionInfo {
|
||||
@apply shadow-lg bg-surface rounded text-text-subtle border border-border-subtle z-50 pointer-events-auto;
|
||||
@apply shadow-lg bg-surface rounded-sm text-text-subtle border border-border-subtle z-50 pointer-events-auto;
|
||||
|
||||
& * {
|
||||
@apply font-mono text-editor !important;
|
||||
@apply font-mono! text-editor!;
|
||||
}
|
||||
|
||||
.cm-completionIcon {
|
||||
@@ -409,7 +411,7 @@
|
||||
}
|
||||
|
||||
.cm-completionIcon {
|
||||
@apply text-sm flex items-center pb-0.5 flex-shrink-0;
|
||||
@apply text-sm flex items-center pb-0.5 shrink-0;
|
||||
}
|
||||
|
||||
.cm-completionLabel {
|
||||
@@ -427,7 +429,7 @@
|
||||
|
||||
input,
|
||||
button {
|
||||
@apply rounded-sm outline-none;
|
||||
@apply rounded-sm outline-hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
@@ -436,12 +438,12 @@
|
||||
}
|
||||
|
||||
button[name="close"] {
|
||||
@apply text-text-subtle hocus:text-text px-2 -mr-1.5 !important;
|
||||
@apply text-text-subtle! hocus:text-text! px-2! -mr-1.5!;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply bg-surface border-border-subtle focus:border-border-focus;
|
||||
@apply border outline-none;
|
||||
@apply border outline-hidden;
|
||||
}
|
||||
|
||||
input.cm-textfield {
|
||||
|
||||
@@ -486,7 +486,7 @@ function EditorInner({
|
||||
const decoratedActions = useMemo(() => {
|
||||
const results = [];
|
||||
const actionClassName = classNames(
|
||||
"bg-surface transition-opacity transform-gpu opacity-0 group-hover:opacity-100 hover:!opacity-100 shadow",
|
||||
"bg-surface transition-opacity transform-gpu opacity-0 group-hover:opacity-100 hover:opacity-100! shadow",
|
||||
);
|
||||
|
||||
if (format) {
|
||||
|
||||
@@ -24,8 +24,8 @@ export function EventViewerRow({
|
||||
onClick={onClick}
|
||||
className={classNames(
|
||||
"w-full grid grid-cols-[auto_minmax(0,1fr)_auto] gap-2 items-center text-left",
|
||||
"px-1.5 h-xs font-mono text-editor cursor-default group focus:outline-none focus:text-text rounded",
|
||||
isActive && "bg-surface-active !text-text",
|
||||
"px-1.5 h-xs font-mono text-editor cursor-default group focus:outline-hidden focus:text-text rounded-sm",
|
||||
isActive && "bg-surface-active text-text!",
|
||||
"text-text-subtle hover:text",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function HotkeyRaw({ labelParts, className, variant }: HotkeyRawProps) {
|
||||
className={classNames(
|
||||
className,
|
||||
variant === "with-bg" &&
|
||||
"rounded bg-surface-highlight px-1 border border-border text-text-subtle",
|
||||
"rounded-sm bg-surface-highlight px-1 border border-border text-text-subtle",
|
||||
variant === "text" && "text-text-subtlest",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -81,7 +81,7 @@ export function HttpMethodTagRaw({
|
||||
colored && m === "PATCH" && "text-notice",
|
||||
colored && m === "POST" && "text-success",
|
||||
colored && m === "DELETE" && "text-danger",
|
||||
"font-mono flex-shrink-0 whitespace-pre",
|
||||
"font-mono shrink-0 whitespace-pre",
|
||||
"pt-[0.15em]", // Fix for monospace font not vertically centering
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -31,7 +31,7 @@ export function HttpResponseDurationTag({ response }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function formatMillis(ms: number) {
|
||||
export function formatMillis(ms: number) {
|
||||
if (ms < 1000) {
|
||||
return `${ms} ms`;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ function BaseInput({
|
||||
const id = useRef(`input-${generateId()}`);
|
||||
const editorClassName = classNames(
|
||||
className,
|
||||
"!bg-transparent min-w-0 h-auto w-full focus:outline-none placeholder:text-placeholder",
|
||||
"bg-transparent! min-w-0 h-auto w-full focus:outline-hidden placeholder:text-placeholder",
|
||||
);
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
@@ -264,7 +264,7 @@ function BaseInput({
|
||||
"border",
|
||||
focused && !disabled ? "border-border-focus" : "border-border",
|
||||
disabled && "border-dotted",
|
||||
!isValid && hasChanged && "!border-danger",
|
||||
!isValid && hasChanged && "border-danger!",
|
||||
size === "md" && "min-h-md",
|
||||
size === "sm" && "min-h-sm",
|
||||
size === "xs" && "min-h-xs",
|
||||
@@ -333,7 +333,7 @@ function BaseInput({
|
||||
: `Obscure ${typeof label === "string" ? label : "field"}`
|
||||
}
|
||||
size="xs"
|
||||
className={classNames("mr-0.5 !h-auto my-0.5", disabled && "opacity-disabled")}
|
||||
className={classNames("mr-0.5 h-auto! my-0.5", disabled && "opacity-disabled")}
|
||||
color={tint}
|
||||
// iconClassName={classNames(
|
||||
// tint === 'primary' && 'text-primary',
|
||||
@@ -548,9 +548,9 @@ function EncryptionInput({
|
||||
color={tint}
|
||||
aria-label="Configure encryption"
|
||||
className={classNames(
|
||||
"flex items-center justify-center !h-full !px-1",
|
||||
"flex items-center justify-center h-full! px-1!",
|
||||
"opacity-70", // Makes it a bit subtler
|
||||
props.disabled && "!opacity-disabled",
|
||||
props.disabled && "opacity-disabled!",
|
||||
)}
|
||||
>
|
||||
<HStack space={0.5}>
|
||||
|
||||
@@ -73,7 +73,7 @@ export function KeyValueRow({
|
||||
<>
|
||||
<td
|
||||
className={classNames(
|
||||
"select-none py-0.5 pr-2 h-full max-w-[10rem]",
|
||||
"select-none py-0.5 pr-2 h-full max-w-40",
|
||||
align === "top" && "align-top",
|
||||
align === "middle" && "align-middle",
|
||||
labelClassName,
|
||||
@@ -86,12 +86,12 @@ export function KeyValueRow({
|
||||
</td>
|
||||
<td
|
||||
className={classNames(
|
||||
"select-none py-0.5 break-all max-w-[15rem]",
|
||||
"select-none py-0.5 break-all max-w-60",
|
||||
align === "top" && "align-top",
|
||||
align === "middle" && "align-middle",
|
||||
)}
|
||||
>
|
||||
<div className="select-text cursor-text max-h-[12rem] overflow-y-auto grid grid-cols-[auto_minmax(0,1fr)_auto]">
|
||||
<div className="select-text cursor-text max-h-48 overflow-y-auto grid grid-cols-[auto_minmax(0,1fr)_auto]">
|
||||
{leftSlot ?? <span aria-hidden />}
|
||||
{children}
|
||||
{resolvedRightSlot ? (
|
||||
|
||||
@@ -27,7 +27,7 @@ export function Label({
|
||||
className={classNames(
|
||||
className,
|
||||
visuallyHidden && "sr-only",
|
||||
"flex-shrink-0 text-sm",
|
||||
"shrink-0 text-sm",
|
||||
"text-text-subtle whitespace-nowrap flex items-center gap-1 mb-0.5",
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -566,7 +566,7 @@ export function PairEditorRow({
|
||||
title={pair.enabled ? "Disable item" : "Enable item"}
|
||||
disabled={isLast || disabled}
|
||||
checked={isLast ? false : !!pair.enabled}
|
||||
className={classNames(isLast && "!opacity-disabled")}
|
||||
className={classNames(isLast && "opacity-disabled!")}
|
||||
onChange={handleChangeEnabled}
|
||||
/>
|
||||
{!isLast && !disableDrag ? (
|
||||
@@ -586,7 +586,7 @@ export function PairEditorRow({
|
||||
<div
|
||||
className={classNames(
|
||||
"grid items-center",
|
||||
"@xs:gap-2 @xs:!grid-rows-1 @xs:!grid-cols-[minmax(0,1fr)_minmax(0,1fr)]",
|
||||
"@xs:gap-2 @xs:grid-rows-1! @xs:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]!",
|
||||
"gap-0.5 grid-cols-1 grid-rows-2",
|
||||
)}
|
||||
>
|
||||
@@ -830,7 +830,7 @@ function MultilineEditDialog({
|
||||
const [value, setValue] = useState<string>(defaultValue);
|
||||
const language = languageFromContentType(contentType, value);
|
||||
return (
|
||||
<div className="w-[100vw] max-w-[40rem] h-[50vh] max-h-full grid grid-rows-[minmax(0,1fr)_auto]">
|
||||
<div className="w-screen max-w-160 h-[50vh] max-h-full grid grid-rows-[minmax(0,1fr)_auto]">
|
||||
<Editor
|
||||
heightMode="auto"
|
||||
defaultValue={defaultValue}
|
||||
|
||||
@@ -26,7 +26,7 @@ export function PairOrBulkEditor({ preferenceName, ...props }: Props) {
|
||||
variant="border"
|
||||
title={useBulk ? "Enable form edit" : "Enable bulk edit"}
|
||||
className={classNames(
|
||||
"transition-opacity opacity-0 group-hover:opacity-80 hover:!opacity-100 shadow",
|
||||
"transition-opacity opacity-0 group-hover:opacity-80 hover:opacity-100! shadow",
|
||||
"bg-surface hover:text group-hover/wrapper:opacity-100",
|
||||
)}
|
||||
onClick={() => setUseBulk((b) => !b)}
|
||||
|
||||
@@ -7,7 +7,7 @@ export function PillButton({ className, ...props }: ButtonProps) {
|
||||
<Button
|
||||
size="2xs"
|
||||
variant="border"
|
||||
className={classNames(className, "!rounded-full mx-1 !px-3")}
|
||||
className={classNames(className, "rounded-full! mx-1 px-3!")}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -116,7 +116,7 @@ export const PlainInput = forwardRef<{ focus: () => void }, PlainInputProps>(fun
|
||||
const id = useRef(`input-${generateId()}`);
|
||||
const commonClassName = classNames(
|
||||
className,
|
||||
"!bg-transparent min-w-0 w-full focus:outline-none placeholder:text-placeholder",
|
||||
"bg-transparent! min-w-0 w-full focus:outline-hidden placeholder:text-placeholder",
|
||||
"px-2 text-xs font-mono cursor-text",
|
||||
);
|
||||
|
||||
@@ -167,7 +167,7 @@ export const PlainInput = forwardRef<{ focus: () => void }, PlainInputProps>(fun
|
||||
"overflow-hidden",
|
||||
focused && !disabled ? "border-border-focus" : "border-border-subtle",
|
||||
disabled && "border-dotted",
|
||||
hasChanged && "has-[:invalid]:border-danger", // For built-in HTML validation
|
||||
hasChanged && "has-invalid:border-danger", // For built-in HTML validation
|
||||
size === "md" && "min-h-md",
|
||||
size === "sm" && "min-h-sm",
|
||||
size === "xs" && "min-h-xs",
|
||||
@@ -225,7 +225,7 @@ export const PlainInput = forwardRef<{ focus: () => void }, PlainInputProps>(fun
|
||||
: `Obscure ${typeof label === "string" ? label : "field"}`
|
||||
}
|
||||
size="xs"
|
||||
className="mr-0.5 group/obscure !h-auto my-0.5"
|
||||
className="mr-0.5 group/obscure h-auto! my-0.5"
|
||||
iconClassName="group-hover/obscure:text"
|
||||
iconSize="sm"
|
||||
icon={obscured ? "eye" : "eye_closed"}
|
||||
|
||||
@@ -43,7 +43,7 @@ export function RadioCards<T extends string>({
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
"mt-1 w-4 h-4 flex-shrink-0 rounded-full border",
|
||||
"mt-1 w-4 h-4 shrink-0 rounded-full border",
|
||||
"flex items-center justify-center",
|
||||
selected ? "border-focus" : "border-border",
|
||||
)}
|
||||
|
||||
@@ -92,7 +92,7 @@ export function SegmentedControl<T extends string>({
|
||||
role="radio"
|
||||
tabIndex={isSelected ? 0 : -1}
|
||||
className={classNames(
|
||||
isActive && "!text-text",
|
||||
isActive && "text-text!",
|
||||
"focus:ring-1 focus:ring-border-focus",
|
||||
)}
|
||||
onClick={() => onChange(o.value)}
|
||||
@@ -111,8 +111,8 @@ export function SegmentedControl<T extends string>({
|
||||
role="radio"
|
||||
tabIndex={isSelected ? 0 : -1}
|
||||
className={classNames(
|
||||
isActive && "!text-text",
|
||||
"!px-1.5 !w-auto",
|
||||
isActive && "text-text!",
|
||||
"px-1.5! w-auto!",
|
||||
"focus:ring-border-focus",
|
||||
)}
|
||||
title={o.label}
|
||||
|
||||
@@ -90,8 +90,8 @@ export function Select<T extends string>({
|
||||
onBlur={() => setFocused(false)}
|
||||
disabled={disabled}
|
||||
className={classNames(
|
||||
"pr-7 w-full outline-none bg-transparent disabled:opacity-disabled",
|
||||
"leading-[1] rounded-none", // Center the text better vertically
|
||||
"pr-7 w-full outline-hidden bg-transparent disabled:opacity-disabled",
|
||||
"leading-none rounded-none", // Center the text better vertically
|
||||
)}
|
||||
>
|
||||
{isInvalidSelection && <option value={"__NONE__"}>-- Select an Option --</option>}
|
||||
|
||||
@@ -189,7 +189,7 @@ export function ModelSettingRowBoolean<M extends AnyModel, K extends ModelKeyOfV
|
||||
|
||||
export function SettingRowNumber({
|
||||
inputClassName,
|
||||
inputWidthClassName = "!w-48",
|
||||
inputWidthClassName = "w-48!",
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
@@ -251,7 +251,7 @@ export function ModelSettingRowNumber<M extends AnyModel, K extends ModelKeyOfVa
|
||||
|
||||
export function SettingRowText({
|
||||
inputClassName,
|
||||
inputWidthClassName = "!w-80",
|
||||
inputWidthClassName = "w-80!",
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
@@ -358,7 +358,7 @@ export function SettingRowSelect<T extends string>({
|
||||
name,
|
||||
onChange,
|
||||
options,
|
||||
selectClassName = "!w-48",
|
||||
selectClassName = "w-48!",
|
||||
title,
|
||||
value,
|
||||
...props
|
||||
@@ -393,7 +393,7 @@ export function SettingSelectControl<T extends string>({
|
||||
name,
|
||||
onChange,
|
||||
options,
|
||||
selectClassName = "!w-48",
|
||||
selectClassName = "w-48!",
|
||||
value,
|
||||
}: {
|
||||
defaultValue?: T;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { formatSize } from "@yaakapp-internal/lib/formatSize";
|
||||
import classNames from "classnames";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
contentLength: number;
|
||||
contentLengthCompressed?: number | null;
|
||||
}
|
||||
|
||||
export function SizeTag({ contentLength, contentLengthCompressed }: Props) {
|
||||
export function SizeTag({ className, contentLength, contentLengthCompressed }: Props) {
|
||||
return (
|
||||
<span
|
||||
className="font-mono"
|
||||
className={classNames("font-mono", className)}
|
||||
title={
|
||||
`${contentLength} bytes` +
|
||||
(contentLengthCompressed ? `\n${contentLengthCompressed} bytes compressed` : "")
|
||||
|
||||
@@ -342,7 +342,7 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
|
||||
<div
|
||||
className={classNames(
|
||||
layout === "horizontal" && "flex flex-col w-full pb-3 mb-auto",
|
||||
layout === "vertical" && "flex flex-row flex-shrink-0 w-full",
|
||||
layout === "vertical" && "flex flex-row shrink-0 w-full",
|
||||
)}
|
||||
>
|
||||
{tabButtons}
|
||||
@@ -456,9 +456,9 @@ function TabButton({
|
||||
onChangeValue?.(tab.value);
|
||||
},
|
||||
className: classNames(
|
||||
"flex items-center rounded whitespace-nowrap",
|
||||
"!px-2 ml-[1px]",
|
||||
"outline-none",
|
||||
"flex items-center rounded-sm whitespace-nowrap",
|
||||
"px-2! ml-px",
|
||||
"outline-hidden",
|
||||
"ring-none",
|
||||
"focus-visible-or-class:outline-2",
|
||||
addBorders && "border focus-visible:bg-surface-highlight",
|
||||
@@ -468,7 +468,7 @@ function TabButton({
|
||||
: layout === "vertical"
|
||||
? "border-border-subtle"
|
||||
: "border-transparent",
|
||||
layout === "horizontal" && "min-w-[10rem]",
|
||||
layout === "horizontal" && "min-w-40",
|
||||
isDragging && "opacity-50",
|
||||
overlay && "opacity-80",
|
||||
),
|
||||
|
||||
@@ -54,11 +54,11 @@ export function Toast({ children, open, onClose, timeout, action, icon, color }:
|
||||
`x-theme-toast x-theme-toast--${color}`,
|
||||
"pointer-events-auto overflow-hidden",
|
||||
"relative pointer-events-auto bg-surface text-text rounded-lg",
|
||||
"border border-border shadow-lg w-[25rem]",
|
||||
"border border-border shadow-lg w-100",
|
||||
)}
|
||||
>
|
||||
<div className="pl-3 py-3 pr-10 flex items-start gap-2 w-full max-h-[11rem] overflow-auto">
|
||||
{toastIcon && <Icon icon={toastIcon} color={color} className="mt-1 flex-shrink-0" />}
|
||||
<div className="pl-3 py-3 pr-10 flex items-start gap-2 w-full max-h-44 overflow-auto">
|
||||
{toastIcon && <Icon icon={toastIcon} color={color} className="mt-1 shrink-0" />}
|
||||
<VStack space={2} className="w-full min-w-0">
|
||||
<div className="select-auto">{children}</div>
|
||||
{action?.({ hide: onClose })}
|
||||
@@ -68,7 +68,7 @@ export function Toast({ children, open, onClose, timeout, action, icon, color }:
|
||||
<IconButton
|
||||
color={color}
|
||||
variant="border"
|
||||
className="opacity-60 border-0 !absolute top-2 right-2"
|
||||
className="opacity-60 border-0 absolute! top-2 right-2"
|
||||
title="Dismiss"
|
||||
icon="x"
|
||||
onClick={onClose}
|
||||
|
||||
@@ -116,7 +116,7 @@ export function Tooltip({ children, className, content, tabIndex, size = "md" }:
|
||||
role="button"
|
||||
aria-describedby={openState ? id.current : undefined}
|
||||
tabIndex={tabIndex ?? -1}
|
||||
className={classNames(className, "flex-grow-0 flex items-center")}
|
||||
className={classNames(className, "grow-0 flex items-center")}
|
||||
onClick={handleToggleImmediate}
|
||||
onMouseEnter={handleOpen}
|
||||
onMouseLeave={handleClose}
|
||||
@@ -141,10 +141,10 @@ function Triangle({ className, position }: { className?: string; position: "top"
|
||||
shapeRendering="crispEdges"
|
||||
className={classNames(
|
||||
className,
|
||||
"absolute z-50 left-[calc(50%-0.4rem)] h-[0.5rem] w-[0.8rem]",
|
||||
"absolute z-50 left-[calc(50%-0.4rem)] h-2 w-[0.8rem]",
|
||||
isBottom
|
||||
? "border-t-[2px] border-surface-highlight -bottom-[calc(0.5rem-3px)] mb-2"
|
||||
: "border-b-[2px] border-surface-highlight -top-[calc(0.5rem-3px)] mt-2",
|
||||
? "border-t-2 border-surface-highlight -bottom-[calc(0.5rem-3px)] mb-2"
|
||||
: "border-b-2 border-surface-highlight -top-[calc(0.5rem-3px)] mt-2",
|
||||
)}
|
||||
>
|
||||
<title>Triangle</title>
|
||||
|
||||
@@ -117,7 +117,7 @@ function CommitListItem({
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
"w-full min-w-0 text-left rounded px-2 py-1.5",
|
||||
"w-full min-w-0 text-left rounded-sm px-2 py-1.5",
|
||||
selected && "bg-surface-active",
|
||||
)}
|
||||
onClick={onSelect}
|
||||
|
||||
@@ -241,7 +241,7 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
|
||||
secondSlot={({ style: innerStyle }) => (
|
||||
<div style={innerStyle} className="grid grid-rows-[minmax(0,1fr)_auto] gap-3 pb-2">
|
||||
<Input
|
||||
className="!text-base font-sans rounded-md"
|
||||
className="text-base! font-sans rounded-md"
|
||||
placeholder="Commit message..."
|
||||
onChange={setMessage}
|
||||
stateKey={null}
|
||||
@@ -325,7 +325,7 @@ function TreeNodeChildren({
|
||||
)}
|
||||
>
|
||||
{isSelected && (
|
||||
<div className="absolute -left-[100vw] right-0 top-0 bottom-0 bg-surface-active opacity-30 -z-10" />
|
||||
<div className="absolute left-[-100vw] right-0 top-0 bottom-0 bg-surface-active opacity-30 -z-10" />
|
||||
)}
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
@@ -358,7 +358,7 @@ function TreeNodeChildren({
|
||||
{node.status.status !== "current" && (
|
||||
<InlineCode
|
||||
className={classNames(
|
||||
"py-0 bg-transparent w-[6rem] text-center shrink-0",
|
||||
"py-0 bg-transparent w-24 text-center shrink-0",
|
||||
node.status.status === "modified" && "text-info",
|
||||
node.status.status === "untracked" && "text-success",
|
||||
node.status.status === "removed" && "text-danger",
|
||||
@@ -400,7 +400,7 @@ function ExternalTreeNode({
|
||||
return (
|
||||
<Checkbox
|
||||
fullWidth
|
||||
className="h-xs w-full hover:bg-surface-highlight rounded px-1 group"
|
||||
className="h-xs w-full hover:bg-surface-highlight rounded-sm px-1 group"
|
||||
checked={entry.staged}
|
||||
onChange={() => onCheck(entry)}
|
||||
title={
|
||||
@@ -409,7 +409,7 @@ function ExternalTreeNode({
|
||||
<div className="truncate">{entry.relaPath}</div>
|
||||
<InlineCode
|
||||
className={classNames(
|
||||
"py-0 ml-auto bg-transparent w-[6rem] text-center",
|
||||
"py-0 ml-auto bg-transparent w-24 text-center",
|
||||
entry.status === "modified" && "text-info",
|
||||
entry.status === "untracked" && "text-success",
|
||||
entry.status === "removed" && "text-danger",
|
||||
|
||||
@@ -559,7 +559,7 @@ const GitMenuButton = forwardRef<HTMLButtonElement, HTMLAttributes<HTMLButtonEle
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
className,
|
||||
"px-3 h-md border-t border-border flex items-center justify-between text-text-subtle outline-none focus-visible:bg-surface-highlight",
|
||||
"px-3 h-md border-t border-border flex items-center justify-between text-text-subtle outline-hidden focus-visible:bg-surface-highlight",
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -113,19 +113,19 @@ export const GraphQLDocsExplorer = memo(function GraphQLDocsExplorer({
|
||||
name={{ value: "query", color: "primary" }}
|
||||
item={qryItem}
|
||||
setItem={setActiveItem}
|
||||
className="!my-0"
|
||||
className="my-0!"
|
||||
/>
|
||||
<GqlTypeRow
|
||||
name={{ value: "mutation", color: "primary" }}
|
||||
item={mutItem}
|
||||
setItem={setActiveItem}
|
||||
className="!my-0"
|
||||
className="my-0!"
|
||||
/>
|
||||
<GqlTypeRow
|
||||
name={{ value: "subscription", color: "primary" }}
|
||||
item={subItem}
|
||||
setItem={setActiveItem}
|
||||
className="!my-0"
|
||||
className="my-0!"
|
||||
/>
|
||||
<Subheading count={Object.keys(allTypes).length}>All Schema Types</Subheading>
|
||||
<DocMarkdown>{schema.description ?? null}</DocMarkdown>
|
||||
@@ -192,7 +192,7 @@ function GraphQLExplorerHeader({
|
||||
noTruncate
|
||||
item={crumb}
|
||||
setItem={setItem}
|
||||
className="!font-sans !text-sm flex-shrink-0"
|
||||
className="font-sans! text-sm! shrink-0"
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
@@ -208,7 +208,7 @@ function GraphQLExplorerHeader({
|
||||
className="hidden @[10rem]:block"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-auto flex gap-1 [&>*]:text-text-subtle">
|
||||
<div className="ml-auto flex gap-1 *:text-text-subtle">
|
||||
<IconButton icon="x" size="sm" title="Close documentation explorer" onClick={onClose} />
|
||||
</div>
|
||||
</nav>
|
||||
@@ -528,7 +528,7 @@ function GqlTypeRow({
|
||||
<span className="text-text-subtle">:</span>{" "}
|
||||
<GqlTypeLink color="notice" item={returnItem} setItem={setItem} />
|
||||
</div>
|
||||
<DocMarkdown className="!text-text-subtle mt-0.5">
|
||||
<DocMarkdown className="text-text-subtle! mt-0.5">
|
||||
{item.type.description ?? null}
|
||||
</DocMarkdown>
|
||||
</div>
|
||||
@@ -786,8 +786,8 @@ function GqlSchemaSearch({
|
||||
className={classNames(
|
||||
className,
|
||||
"relative flex items-center bg-surface z-20 min-w-0",
|
||||
!focused && "max-w-[6rem] ml-auto",
|
||||
focused && "!absolute top-0 left-1.5 right-1.5 bottom-0 pt-1.5",
|
||||
!focused && "max-w-24 ml-auto",
|
||||
focused && "absolute! top-0 left-1.5 right-1.5 bottom-0 pt-1.5",
|
||||
)}
|
||||
>
|
||||
<PlainInput
|
||||
@@ -879,7 +879,7 @@ function SearchResult({
|
||||
ref={initRef}
|
||||
className={classNames(
|
||||
className,
|
||||
"px-3 truncate w-full text-left h-sm rounded text-editor font-mono",
|
||||
"px-3 truncate w-full text-left h-sm rounded-sm text-editor font-mono",
|
||||
isActive && "bg-surface-highlight",
|
||||
)}
|
||||
{...extraProps}
|
||||
@@ -893,7 +893,7 @@ function Heading({ children }: { children: ReactNode }) {
|
||||
|
||||
function DocMarkdown({ children, className }: { children: string | null; className?: string }) {
|
||||
return (
|
||||
<Markdown className={classNames(className, "!text-text-subtle italic")}>{children}</Markdown>
|
||||
<Markdown className={classNames(className, "text-text-subtle! italic")}>{children}</Markdown>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,8 +77,8 @@ function GraphQLEditorInner({ request, onChange, baseRequest, ...extraEditorProp
|
||||
|
||||
const actions = useMemo<EditorProps["actions"]>(
|
||||
() => [
|
||||
<div key="actions" className="flex flex-row !opacity-100 !shadow">
|
||||
<div key="introspection" className="!opacity-100">
|
||||
<div key="actions" className="flex flex-row opacity-100! shadow!">
|
||||
<div key="introspection" className="opacity-100!">
|
||||
{schema === undefined ? null /* Initializing */ : (
|
||||
<Dropdown
|
||||
items={[
|
||||
@@ -217,7 +217,7 @@ function GraphQLEditorInner({ request, onChange, baseRequest, ...extraEditorProp
|
||||
stateKey={`graphql_body.${request.id}`}
|
||||
{...extraEditorProps}
|
||||
/>
|
||||
<div className="grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 min-h-[5rem]">
|
||||
<div className="grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 min-h-20">
|
||||
<Separator dashed className="pb-1">
|
||||
Variables
|
||||
</Separator>
|
||||
|
||||
@@ -298,7 +298,7 @@ function SseSummaryFooter({
|
||||
<Markdown className="select-auto cursor-auto">{summary}</Markdown>
|
||||
</div>
|
||||
) : (
|
||||
<pre className="font-mono whitespace-pre-wrap break-words select-auto cursor-auto">
|
||||
<pre className="font-mono whitespace-pre-wrap wrap-break-word select-auto cursor-auto">
|
||||
{summary}
|
||||
</pre>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { HttpResponse } from "@yaakapp-internal/models";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useCopyHttpResponse } from "../../hooks/useCopyHttpResponse";
|
||||
import { useResponseBodyText } from "../../hooks/useResponseBodyText";
|
||||
import { useSaveResponse } from "../../hooks/useSaveResponse";
|
||||
import { languageFromContentType } from "../../lib/contentType";
|
||||
import { getContentTypeFromHeaders } from "../../lib/model_util";
|
||||
import type { EditorProps } from "../core/Editor/Editor";
|
||||
import { IconButton } from "../core/IconButton";
|
||||
import { EmptyStateText } from "../EmptyStateText";
|
||||
import { TextViewer } from "./TextViewer";
|
||||
import { WebPageViewer } from "./WebPageViewer";
|
||||
@@ -51,6 +54,9 @@ interface HttpTextViewerProps {
|
||||
function HttpTextViewer({ response, text, language, pretty, className }: HttpTextViewerProps) {
|
||||
const [currentFilter, setCurrentFilter] = useState<string | null>(null);
|
||||
const filteredBody = useResponseBodyText({ response, filter: currentFilter });
|
||||
const saveResponse = useSaveResponse(response);
|
||||
const copyResponse = useCopyHttpResponse(response);
|
||||
const actionsDisabled = response.state !== "closed" && response.status >= 100;
|
||||
|
||||
const filterCallback = useMemo(
|
||||
() => (filter: string) => {
|
||||
@@ -72,6 +78,26 @@ function HttpTextViewer({ response, text, language, pretty, className }: HttpTex
|
||||
filterStateKey={`response.body.${response.requestId}`}
|
||||
pretty={pretty}
|
||||
className={className}
|
||||
footerActions={[
|
||||
<IconButton
|
||||
key="save"
|
||||
size="sm"
|
||||
icon="save"
|
||||
title="Save response to file"
|
||||
disabled={actionsDisabled}
|
||||
onClick={() => saveResponse.mutate()}
|
||||
className="border !border-border-subtle"
|
||||
/>,
|
||||
<IconButton
|
||||
key="copy"
|
||||
size="sm"
|
||||
icon="copy"
|
||||
title="Copy response body"
|
||||
disabled={actionsDisabled}
|
||||
onClick={() => copyResponse.mutate()}
|
||||
className="border !border-border-subtle"
|
||||
/>,
|
||||
]}
|
||||
onFilter={filterCallback}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -63,7 +63,7 @@ export function MultipartViewer({ data, boundary, idPrefix = "multipart" }: Prop
|
||||
<div className="h-5 w-5 overflow-auto flex items-center justify-end">
|
||||
<ImageViewer
|
||||
data={part.arrayBuffer}
|
||||
className="ml-auto w-auto rounded overflow-hidden"
|
||||
className="ml-auto w-auto rounded-sm overflow-hidden"
|
||||
/>
|
||||
</div>
|
||||
) : part.filename ? (
|
||||
@@ -76,7 +76,7 @@ export function MultipartViewer({ data, boundary, idPrefix = "multipart" }: Prop
|
||||
// oxlint-disable-next-line react/no-array-index-key -- Nothing else to key on
|
||||
key={idPrefix + part.name + i}
|
||||
value={tabValue(part, i)}
|
||||
className="pl-3 !pt-0"
|
||||
className="pl-3 pt-0!"
|
||||
>
|
||||
<Part part={part} />
|
||||
</TabContent>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classNames from "classnames";
|
||||
import type { ReactNode } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Children, useCallback, useMemo } from "react";
|
||||
import { createGlobalState } from "react-use";
|
||||
import { useDebouncedValue } from "@yaakapp-internal/ui";
|
||||
import { useFormatText } from "../../hooks/useFormatText";
|
||||
@@ -19,6 +19,7 @@ interface Props {
|
||||
filterStateKey?: string | null;
|
||||
pretty?: boolean;
|
||||
className?: string;
|
||||
footerActions?: ReactNode;
|
||||
onFilter?: (filter: string) => {
|
||||
data: string | null | undefined;
|
||||
isPending: boolean;
|
||||
@@ -35,6 +36,7 @@ export function TextViewer({
|
||||
filterStateKey,
|
||||
pretty,
|
||||
className,
|
||||
footerActions,
|
||||
onFilter,
|
||||
}: Props) {
|
||||
const filterKey = filterStateKey ?? stateKey;
|
||||
@@ -66,13 +68,13 @@ export function TextViewer({
|
||||
const canFilter = onFilter && (language === "json" || language === "xml" || language === "html");
|
||||
|
||||
const actions = useMemo<ReactNode[]>(() => {
|
||||
const nodes: ReactNode[] = [];
|
||||
const nodes: ReactNode[] = isSearching ? [] : Children.toArray(footerActions);
|
||||
|
||||
if (!canFilter) return nodes;
|
||||
|
||||
if (isSearching) {
|
||||
nodes.push(
|
||||
<div key="input" className="w-full !opacity-100">
|
||||
<div key="input" className="w-full opacity-100!">
|
||||
<Input
|
||||
key={filterKey ?? "filter"}
|
||||
validate={!filteredResponse.error}
|
||||
@@ -100,13 +102,14 @@ export function TextViewer({
|
||||
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!")}
|
||||
/>,
|
||||
);
|
||||
|
||||
return nodes;
|
||||
}, [
|
||||
canFilter,
|
||||
footerActions,
|
||||
filterKey,
|
||||
filterText,
|
||||
filteredResponse.error,
|
||||
|
||||
@@ -3,10 +3,12 @@ import { copyToClipboard } from "../lib/copy";
|
||||
import { getResponseBodyText } from "../lib/responseBody";
|
||||
import { useFastMutation } from "./useFastMutation";
|
||||
|
||||
export function useCopyHttpResponse(response: HttpResponse) {
|
||||
export function useCopyHttpResponse(response: HttpResponse | null) {
|
||||
return useFastMutation({
|
||||
mutationKey: ["copy_http_response", response.id],
|
||||
mutationKey: ["copy_http_response", response?.id],
|
||||
async mutationFn() {
|
||||
if (response == null) return;
|
||||
|
||||
const body = await getResponseBodyText({ response, filter: null });
|
||||
copyToClipboard(body);
|
||||
},
|
||||
|
||||
@@ -9,10 +9,12 @@ import { invokeCmd } from "../lib/tauri";
|
||||
import { showToast } from "../lib/toast";
|
||||
import { useFastMutation } from "./useFastMutation";
|
||||
|
||||
export function useSaveResponse(response: HttpResponse) {
|
||||
export function useSaveResponse(response: HttpResponse | null) {
|
||||
return useFastMutation({
|
||||
mutationKey: ["save_response", response.id],
|
||||
mutationKey: ["save_response", response?.id],
|
||||
mutationFn: async () => {
|
||||
if (response == null) return null;
|
||||
|
||||
const request = getModel("http_request", response.requestId);
|
||||
if (request == null) return null;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export function useToggleCommandPalette() {
|
||||
id: "command_palette",
|
||||
size: "dynamic",
|
||||
hideX: true,
|
||||
className: "mb-auto mt-[4rem] !max-h-[min(30rem,calc(100vh-4rem))]",
|
||||
className: "mb-auto mt-16 max-h-[min(30rem,calc(100vh-4rem))]!",
|
||||
vAlign: "top",
|
||||
noPadding: true,
|
||||
noScroll: true,
|
||||
|
||||
@@ -45,7 +45,7 @@ export async function editEnvironment(
|
||||
id: "environment-editor",
|
||||
noPadding: true,
|
||||
size: "lg",
|
||||
className: "h-[90vh] max-h-[60rem]",
|
||||
className: "h-[90vh] max-h-240",
|
||||
render: () => (
|
||||
<EnvironmentEditDialog
|
||||
initialEnvironmentId={environment?.id ?? null}
|
||||
|
||||
@@ -166,7 +166,7 @@ function showUpdateInstalledToast(version: string) {
|
||||
action: ({ hide }) => (
|
||||
<ButtonInfiniteLoading
|
||||
size="xs"
|
||||
className="mr-auto min-w-[5rem]"
|
||||
className="mr-auto min-w-20"
|
||||
color="primary"
|
||||
loadingChildren="Restarting..."
|
||||
onClick={() => {
|
||||
@@ -206,7 +206,7 @@ async function showUpdateAvailableToast(updateInfo: UpdateInfo) {
|
||||
<ButtonInfiniteLoading
|
||||
size="xs"
|
||||
color="info"
|
||||
className="min-w-[10rem]"
|
||||
className="min-w-40"
|
||||
loadingChildren={downloaded ? "Installing..." : "Downloading..."}
|
||||
onClick={async () => {
|
||||
await emit<UpdateResponse>(replyEventId, {
|
||||
@@ -259,7 +259,7 @@ function showPluginUpdatesToast(updateInfo: PluginUpdateNotification) {
|
||||
<ButtonInfiniteLoading
|
||||
size="xs"
|
||||
color="info"
|
||||
className="min-w-[5rem]"
|
||||
className="min-w-20"
|
||||
loadingChildren="Updating..."
|
||||
onClick={async () => {
|
||||
const updated = await updateAllPlugins();
|
||||
@@ -311,7 +311,7 @@ function showNotificationToast(n: YaakNotification) {
|
||||
<Button
|
||||
size="xs"
|
||||
color={stringToColor(n.color) ?? undefined}
|
||||
className="mr-auto min-w-[5rem]"
|
||||
className="mr-auto min-w-20"
|
||||
rightSlot={<Icon icon="external_link" />}
|
||||
onClick={() => {
|
||||
hide();
|
||||
|
||||
@@ -61,7 +61,7 @@ export function showErrorToast<T>({
|
||||
message: (
|
||||
<div className="w-full">
|
||||
<h2 className="text-lg font-bold mb-2">{title}</h2>
|
||||
<div className="whitespace-pre-wrap break-words">{String(message)}</div>
|
||||
<div className="whitespace-pre-wrap wrap-break-word">{String(message)}</div>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
@import "../../packages/tailwind-config/index.css";
|
||||
@source "../../packages/ui/src";
|
||||
|
||||
@layer base {
|
||||
html,
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
a,
|
||||
a[href] * {
|
||||
@apply cursor-pointer !important;
|
||||
@apply cursor-pointer!;
|
||||
}
|
||||
|
||||
table th {
|
||||
@@ -72,14 +72,14 @@
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
@apply opacity-40 !important;
|
||||
@apply opacity-40!;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-scrollbars {
|
||||
&::-webkit-scrollbar-corner,
|
||||
&::-webkit-scrollbar {
|
||||
@apply hidden !important;
|
||||
@apply hidden!;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,8 +79,6 @@
|
||||
"devDependencies": {
|
||||
"@lezer/generator": "^1.8.0",
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
|
||||
"@tanstack/router-plugin": "^1.127.5",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/node": "^24.0.13",
|
||||
@@ -94,14 +92,10 @@
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@yaakapp-internal/theme": "^1.0.0",
|
||||
"@yaakapp-internal/ui": "^1.0.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"decompress": "^4.2.1",
|
||||
"internal-ip": "^8.0.0",
|
||||
"postcss": "^8.5.14",
|
||||
"postcss-nesting": "^13.0.2",
|
||||
"rollup": "^4.60.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "npm:@voidzero-dev/vite-plus-core@^0.2.1",
|
||||
"vite-plugin-static-copy": "^3.3.0",
|
||||
"vite-plugin-svgr": "^4.5.0",
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require("@tailwindcss/nesting")(require("postcss-nesting")),
|
||||
require("tailwindcss"),
|
||||
require("autoprefixer"),
|
||||
],
|
||||
plugins: [require("@tailwindcss/postcss")],
|
||||
};
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
const sharedConfig = require("@yaakapp-internal/tailwind-config");
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
...sharedConfig,
|
||||
content: [
|
||||
"./*.{html,ts,tsx}",
|
||||
"./commands/**/*.{ts,tsx}",
|
||||
"./components/**/*.{ts,tsx}",
|
||||
"./hooks/**/*.{ts,tsx}",
|
||||
"./init/**/*.{ts,tsx}",
|
||||
"./lib/**/*.{ts,tsx}",
|
||||
"./routes/**/*.{ts,tsx}",
|
||||
"../../packages/ui/src/**/*.{ts,tsx}",
|
||||
],
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
@import "../../packages/tailwind-config/index.css";
|
||||
@source "../../packages/ui/src";
|
||||
|
||||
@layer base {
|
||||
html,
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
a,
|
||||
a[href] * {
|
||||
@apply cursor-pointer !important;
|
||||
@apply cursor-pointer!;
|
||||
}
|
||||
|
||||
table th {
|
||||
@@ -68,14 +68,14 @@
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
@apply opacity-40 !important;
|
||||
@apply opacity-40!;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-scrollbars {
|
||||
&::-webkit-scrollbar-corner,
|
||||
&::-webkit-scrollbar {
|
||||
@apply hidden !important;
|
||||
@apply hidden!;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user