mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-25 19:01:14 +01:00
Switch to BiomeJS (#306)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import mime from 'mime';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
|
||||
@@ -14,7 +14,6 @@ const featureMap: Record<Props['feature'], boolean> = {
|
||||
export function CargoFeature({ children, feature }: Props) {
|
||||
if (featureMap[feature]) {
|
||||
return <>{children}</>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ export function ColorIndicator({ color, onClick, className }: Props) {
|
||||
if (onClick) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
className={classNames(finalClassName, 'hover:border-text')}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <span style={style} className={finalClassName} />;
|
||||
}
|
||||
return <span style={style} className={finalClassName} />;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,15 @@ import { workspacesAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { fuzzyFilter } from 'fuzzbunny';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { KeyboardEvent, ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Fragment,
|
||||
type KeyboardEvent,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { createFolder } from '../commands/commands';
|
||||
import { createSubEnvironmentAndActivate } from '../commands/createEnvironment';
|
||||
import { openSettings } from '../commands/openSettings';
|
||||
@@ -77,6 +84,11 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
const [, setSidebarHidden] = useSidebarHidden();
|
||||
const { mutate: sendRequest } = useSendAnyHttpRequest();
|
||||
|
||||
const handleSetCommand = (command: string) => {
|
||||
setCommand(command);
|
||||
setSelectedItemKey(null);
|
||||
};
|
||||
|
||||
const workspaceCommands = useMemo<CommandPaletteItem[]>(() => {
|
||||
if (workspaceId == null) return [];
|
||||
|
||||
@@ -150,25 +162,23 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
label: 'Send Request',
|
||||
onSelect: () => sendRequest(activeRequest.id),
|
||||
});
|
||||
for (let i = 0; i < httpRequestActions.length; i++) {
|
||||
const a = httpRequestActions[i]!;
|
||||
httpRequestActions.forEach((a, i) => {
|
||||
commands.push({
|
||||
key: `http_request_action.${i}`,
|
||||
label: a.label,
|
||||
onSelect: () => a.call(activeRequest),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (activeRequest?.model === 'grpc_request') {
|
||||
for (let i = 0; i < grpcRequestActions.length; i++) {
|
||||
const a = grpcRequestActions[i]!;
|
||||
grpcRequestActions.forEach((a, i) => {
|
||||
commands.push({
|
||||
key: `grpc_request_action.${i}`,
|
||||
label: a.label,
|
||||
onSelect: () => a.call(activeRequest),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (activeRequest != null) {
|
||||
@@ -210,13 +220,14 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
|
||||
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
|
||||
return aRecentIndex - bRecentIndex;
|
||||
} else if (aRecentIndex >= 0 && bRecentIndex === -1) {
|
||||
return -1;
|
||||
} else if (aRecentIndex === -1 && bRecentIndex >= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
}
|
||||
if (aRecentIndex >= 0 && bRecentIndex === -1) {
|
||||
return -1;
|
||||
}
|
||||
if (aRecentIndex === -1 && bRecentIndex >= 0) {
|
||||
return 1;
|
||||
}
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
});
|
||||
}, [recentRequests, requests]);
|
||||
|
||||
@@ -227,13 +238,14 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
|
||||
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
|
||||
return aRecentIndex - bRecentIndex;
|
||||
} else if (aRecentIndex >= 0 && bRecentIndex === -1) {
|
||||
return -1;
|
||||
} else if (aRecentIndex === -1 && bRecentIndex >= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
}
|
||||
if (aRecentIndex >= 0 && bRecentIndex === -1) {
|
||||
return -1;
|
||||
}
|
||||
if (aRecentIndex === -1 && bRecentIndex >= 0) {
|
||||
return 1;
|
||||
}
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
});
|
||||
}, [subEnvironments, recentEnvironments]);
|
||||
|
||||
@@ -249,13 +261,14 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
|
||||
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
|
||||
return aRecentIndex - bRecentIndex;
|
||||
} else if (aRecentIndex >= 0 && bRecentIndex === -1) {
|
||||
return -1;
|
||||
} else if (aRecentIndex === -1 && bRecentIndex >= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
}
|
||||
if (aRecentIndex >= 0 && bRecentIndex === -1) {
|
||||
return -1;
|
||||
}
|
||||
if (aRecentIndex === -1 && bRecentIndex >= 0) {
|
||||
return 1;
|
||||
}
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
});
|
||||
}, [recentWorkspaces, workspaces]);
|
||||
|
||||
@@ -280,12 +293,10 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
<div className="flex items-center gap-x-0.5">
|
||||
<HttpMethodTag short className="text-xs mr-2" request={r} />
|
||||
{resolvedModelNameWithFoldersArray(r).map((name, i, all) => (
|
||||
<>
|
||||
{i !== 0 && (
|
||||
<Icon icon="chevron_right" className="opacity-80"/>
|
||||
)}
|
||||
<Fragment key={name}>
|
||||
{i !== 0 && <Icon icon="chevron_right" className="opacity-80" />}
|
||||
<div className={classNames(i < all.length - 1 && 'truncate')}>{name}</div>
|
||||
</>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
@@ -341,10 +352,6 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
|
||||
const allItems = useMemo(() => groups.flatMap((g) => g.items), [groups]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedItemKey(null);
|
||||
}, [command]);
|
||||
|
||||
const { filteredGroups, filteredAllItems } = useMemo(() => {
|
||||
const result = command
|
||||
? fuzzyFilter(
|
||||
@@ -424,7 +431,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
placeholder="Search or type a command"
|
||||
className="font-sans !text-base"
|
||||
defaultValue={command}
|
||||
onChange={setCommand}
|
||||
onChange={handleSetCommand}
|
||||
onKeyDownCapture={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import { type ReactNode } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useToggle } from '../hooks/useToggle';
|
||||
import { showConfirm } from '../lib/confirm';
|
||||
import { Banner } from './core/Banner';
|
||||
@@ -54,7 +54,7 @@ export function ConfirmLargeRequestBody({ children, request }: Props) {
|
||||
variant="border"
|
||||
onClick={async () => {
|
||||
const confirm = await showConfirm({
|
||||
id: 'delete-body-' + request.id,
|
||||
id: `delete-body-${request.id}`,
|
||||
confirmText: 'Delete Body',
|
||||
title: 'Delete Body Text',
|
||||
description: 'Are you sure you want to delete the request body text?',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Cookie} from '@yaakapp-internal/models';
|
||||
import type { Cookie } from '@yaakapp-internal/models';
|
||||
import { cookieJarsAtom, patchModel } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { cookieDomain } from '../lib/model_util';
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
cookieJarId: string | null;
|
||||
}
|
||||
|
||||
export const CookieDialog = function ({ cookieJarId }: Props) {
|
||||
export const CookieDialog = ({ cookieJarId }: Props) => {
|
||||
const cookieJars = useAtomValue(cookieJarsAtom);
|
||||
const cookieJar = cookieJars?.find((c) => c.id === cookieJarId);
|
||||
|
||||
@@ -33,7 +33,7 @@ export const CookieDialog = function ({ cookieJarId }: Props) {
|
||||
<tr>
|
||||
<th className="py-2 text-left">Domain</th>
|
||||
<th className="py-2 text-left pl-4">Cookie</th>
|
||||
<th className="py-2 pl-4"></th>
|
||||
<th className="py-2 pl-4" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { ComponentType } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { dialogsAtom, hideDialog } from '../lib/dialog';
|
||||
import { Dialog, type DialogProps } from './core/Dialog';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties} from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
||||
@@ -32,7 +32,6 @@ import { VStack } from './core/Stacks';
|
||||
import { Markdown } from './Markdown';
|
||||
import { SelectFile } from './SelectFile';
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const DYNAMIC_FORM_NULL_ARG = '__NULL__';
|
||||
const INPUT_SIZE = 'sm';
|
||||
|
||||
@@ -59,7 +58,7 @@ export function DynamicForm<T extends Record<string, JsonPrimitive>>({
|
||||
}: Props<T>) {
|
||||
const setDataAttr = useCallback(
|
||||
(name: string, value: JsonPrimitive) => {
|
||||
onChange({ ...data, [name]: value == DYNAMIC_FORM_NULL_ARG ? undefined : value });
|
||||
onChange({ ...data, [name]: value === DYNAMIC_FORM_NULL_ARG ? undefined : value });
|
||||
},
|
||||
[data, onChange],
|
||||
);
|
||||
@@ -128,7 +127,7 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
case 'select':
|
||||
return (
|
||||
<SelectArg
|
||||
key={i}
|
||||
key={i + stateKey}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={
|
||||
@@ -250,6 +249,9 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
);
|
||||
case 'markdown':
|
||||
return <Markdown key={i + stateKey}>{input.content}</Markdown>;
|
||||
default:
|
||||
// @ts-expect-error
|
||||
throw new Error(`Invalid input type: ${input.type}`);
|
||||
}
|
||||
})}
|
||||
</>
|
||||
@@ -293,9 +295,8 @@ function TextArg({
|
||||
};
|
||||
if (autocompleteVariables || autocompleteFunctions) {
|
||||
return <Input {...props} />;
|
||||
} else {
|
||||
return <PlainInput {...props} />;
|
||||
}
|
||||
return <PlainInput {...props} />;
|
||||
}
|
||||
|
||||
function EditorArg({
|
||||
@@ -491,7 +492,7 @@ function HttpRequestArg({
|
||||
return {
|
||||
label:
|
||||
buildRequestBreadcrumbs(r, folders).join(' / ') +
|
||||
(r.id == activeHttpRequest?.id ? ' (current)' : ''),
|
||||
(r.id === activeHttpRequest?.id ? ' (current)' : ''),
|
||||
value: r.id,
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Environment, Workspace } from '@yaakapp-internal/models';
|
||||
import { duplicateModel, patchModel } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createSubEnvironmentAndActivate } from '../commands/createEnvironment';
|
||||
import { activeWorkspaceAtom, activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||
import {
|
||||
@@ -112,11 +112,11 @@ function EnvironmentEditDialogSidebar({
|
||||
const treeRef = useRef<TreeHandle>(null);
|
||||
const { baseEnvironment, baseEnvironments } = useEnvironmentsBreakdown();
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: none
|
||||
useLayoutEffect(() => {
|
||||
if (selectedEnvironmentId == null) return;
|
||||
treeRef.current?.selectItem(selectedEnvironmentId);
|
||||
treeRef.current?.focus();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleDeleteEnvironment = useCallback(
|
||||
@@ -137,7 +137,7 @@ function EnvironmentEditDialogSidebar({
|
||||
enable,
|
||||
allowDefault: true,
|
||||
priority: 100,
|
||||
cb: async function (items: TreeModel[]) {
|
||||
cb: async (items: TreeModel[]) => {
|
||||
const item = items[0];
|
||||
if (items.length === 1 && item != null) {
|
||||
treeRef.current?.renameItem(item.id);
|
||||
@@ -152,9 +152,9 @@ function EnvironmentEditDialogSidebar({
|
||||
'sidebar.selected.duplicate': {
|
||||
priority: 100,
|
||||
enable,
|
||||
cb: async function (items: TreeModel[]) {
|
||||
if (items.length === 1) {
|
||||
const item = items[0]!;
|
||||
cb: async (items: TreeModel[]) => {
|
||||
if (items.length === 1 && items[0]) {
|
||||
const item = items[0];
|
||||
const newId = await duplicateModel(item);
|
||||
setSelectedEnvironmentId(newId);
|
||||
} else {
|
||||
|
||||
@@ -13,13 +13,13 @@ import {
|
||||
setupOrConfigureEncryption,
|
||||
withEncryptionEnabled,
|
||||
} from '../lib/setupOrConfigureEncryption';
|
||||
import { PillButton } from './core/PillButton';
|
||||
import { DismissibleBanner } from './core/DismissibleBanner';
|
||||
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||
import { Heading } from './core/Heading';
|
||||
import type { PairEditorHandle, PairWithId } from './core/PairEditor';
|
||||
import { ensurePairId } from './core/PairEditor.util';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { PillButton } from './core/PillButton';
|
||||
import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
|
||||
import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip';
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { IconTooltip } from './core/IconTooltip';
|
||||
|
||||
export function EnvironmentSharableTooltip() {
|
||||
|
||||
@@ -55,6 +55,7 @@ function ExportDataDialogContent({
|
||||
|
||||
const handleToggleAll = () => {
|
||||
setSelectedWorkspaces(
|
||||
// biome-ignore lint/performance/noAccumulatingSpread: none
|
||||
allSelected ? {} : workspaces.reduce((acc, w) => ({ ...acc, [w.id]: true }), {}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Folder, GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakap
|
||||
import { foldersAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||
import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse';
|
||||
@@ -43,7 +43,14 @@ export function FolderLayout({ folder, style }: Props) {
|
||||
<Icon icon="folder" size="xl" color="secondary" />
|
||||
<Heading level={1}>{resolvedModelName(folder)}</Heading>
|
||||
<HStack className="ml-auto" alignItems="center">
|
||||
<Button rightSlot={<Icon icon="send_horizontal" />} color="secondary" size="sm" variant="border">Send All</Button>
|
||||
<Button
|
||||
rightSlot={<Icon icon="send_horizontal" />}
|
||||
color="secondary"
|
||||
size="sm"
|
||||
variant="border"
|
||||
>
|
||||
Send All
|
||||
</Button>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<Separator className="mt-3 mb-8" />
|
||||
@@ -57,7 +64,7 @@ export function FolderLayout({ folder, style }: Props) {
|
||||
}
|
||||
|
||||
function ChildCard({ child }: { child: Folder | HttpRequest | GrpcRequest | WebsocketRequest }) {
|
||||
let card;
|
||||
let card: ReactNode;
|
||||
if (child.model === 'folder') {
|
||||
card = <FolderCard folder={child} />;
|
||||
} else if (child.model === 'http_request') {
|
||||
@@ -67,7 +74,7 @@ function ChildCard({ child }: { child: Folder | HttpRequest | GrpcRequest | Webs
|
||||
} else if (child.model === 'websocket_request') {
|
||||
card = <RequestCard request={child} />;
|
||||
} else {
|
||||
card = <div>Unknown model {child['model']}</div>;
|
||||
card = <div>Unknown model</div>;
|
||||
}
|
||||
|
||||
const navigate = useCallback(async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createWorkspaceModel, foldersAtom, patchModel } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useAuthTab } from '../hooks/useAuthTab';
|
||||
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
||||
import { useHeadersTab } from '../hooks/useHeadersTab';
|
||||
@@ -28,7 +28,11 @@ const TAB_HEADERS = 'headers';
|
||||
const TAB_VARIABLES = 'variables';
|
||||
const TAB_GENERAL = 'general';
|
||||
|
||||
export type FolderSettingsTab = typeof TAB_AUTH | typeof TAB_HEADERS | typeof TAB_GENERAL | typeof TAB_VARIABLES;
|
||||
export type FolderSettingsTab =
|
||||
| typeof TAB_AUTH
|
||||
| typeof TAB_HEADERS
|
||||
| typeof TAB_GENERAL
|
||||
| typeof TAB_VARIABLES;
|
||||
|
||||
export function FolderSettingsDialog({ folderId, tab }: Props) {
|
||||
const folders = useAtomValue(foldersAtom);
|
||||
|
||||
@@ -49,7 +49,7 @@ export function FormMultipartEditor({ request, forceUpdateKey, onChange }: Props
|
||||
pairs={pairs}
|
||||
onChange={handleChange}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
stateKey={'multipart.' + request.id}
|
||||
stateKey={`multipart.${request.id}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { patchModel } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useGrpc } from '../hooks/useGrpc';
|
||||
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
|
||||
|
||||
@@ -6,11 +6,11 @@ import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { Link } from './core/Link';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import { Icon } from './core/Icon';
|
||||
|
||||
interface Props {
|
||||
onDone: () => void;
|
||||
@@ -20,7 +20,7 @@ export function GrpcProtoSelectionDialog(props: Props) {
|
||||
const request = useActiveRequest();
|
||||
if (request?.model !== 'grpc_request') return null;
|
||||
|
||||
return GrpcProtoSelectionDialogWithRequest({ ...props, request });
|
||||
return <GrpcProtoSelectionDialogWithRequest request={request} {...props} />;
|
||||
}
|
||||
|
||||
function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: GrpcRequest }) {
|
||||
@@ -103,7 +103,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
|
||||
Found services{' '}
|
||||
{services?.slice(0, 5).map((s, i) => {
|
||||
return (
|
||||
<span key={i}>
|
||||
<span key={s.name + s.methods.join(',')}>
|
||||
<InlineCode>{s.name}</InlineCode>
|
||||
{i === services.length - 1 ? '' : i === services.length - 2 ? ' and ' : ', '}
|
||||
</span>
|
||||
@@ -119,7 +119,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
|
||||
Server reflection found services
|
||||
{services?.map((s, i) => {
|
||||
return (
|
||||
<span key={i}>
|
||||
<span key={s.name + s.methods.join(',')}>
|
||||
<InlineCode>{s.name}</InlineCode>
|
||||
{i === services.length - 1 ? '' : i === services.length - 2 ? ' and ' : ', '}
|
||||
</span>
|
||||
@@ -135,13 +135,16 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
|
||||
<table className="w-full divide-y divide-surface-highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-text-subtlest" colSpan={3}>Added File Paths</th>
|
||||
<th className="text-text-subtlest" colSpan={3}>
|
||||
Added File Paths
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
{protoFiles.map((f, i) => {
|
||||
const parts = f.split('/');
|
||||
return (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: none
|
||||
<tr key={f + i} className="group">
|
||||
<td>
|
||||
<Icon icon={f.endsWith('.proto') ? 'file_code' : 'folder_code'} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type GrpcRequest, type HttpRequestHeader, patchModel } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { useAuthTab } from '../hooks/useAuthTab';
|
||||
import { useContainerSize } from '../hooks/useContainerQuery';
|
||||
import type { ReflectResponseService } from '../hooks/useGrpc';
|
||||
@@ -183,7 +183,7 @@ export function GrpcRequestPane({
|
||||
onUrlChange={handleChangeUrl}
|
||||
onCancel={onCancel}
|
||||
isLoading={isStreaming}
|
||||
stateKey={'grpc_url.' + activeRequest.id}
|
||||
stateKey={`grpc_url.${activeRequest.id}`}
|
||||
/>
|
||||
<HStack space={1.5}>
|
||||
<RadioDropdown
|
||||
|
||||
@@ -55,13 +55,13 @@ export function GrpcResponsePane({ style, methodType, activeRequest }: Props) {
|
||||
[activeEventId, events],
|
||||
);
|
||||
|
||||
// Set active message to the first message received if unary
|
||||
// Set the active message to the first message received if unary
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: none
|
||||
useEffect(() => {
|
||||
if (events.length === 0 || activeEvent != null || methodType !== 'unary') {
|
||||
return;
|
||||
}
|
||||
setActiveEventId(events.find((m) => m.eventType === 'server_message')?.id ?? null);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [events.length]);
|
||||
|
||||
return (
|
||||
@@ -226,6 +226,7 @@ function EventRow({
|
||||
return (
|
||||
<div className="px-1" ref={ref}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={classNames(
|
||||
'w-full grid grid-cols-[auto_minmax(0,3fr)_auto] gap-2 items-center text-left',
|
||||
@@ -274,7 +275,7 @@ function EventRow({
|
||||
{error && <span className="text-warning"> ({error})</span>}
|
||||
</div>
|
||||
<div className={classNames('opacity-50 text-xs')}>
|
||||
{format(createdAt + 'Z', 'HH:mm:ss.SSS')}
|
||||
{format(`${createdAt}Z`, 'HH:mm:ss.SSS')}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { type } from "@tauri-apps/plugin-os";
|
||||
import { settingsAtom } from "@yaakapp-internal/models";
|
||||
import classNames from "classnames";
|
||||
import { useAtomValue } from "jotai";
|
||||
import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { useIsFullscreen } from "../hooks/useIsFullscreen";
|
||||
import {
|
||||
HEADER_SIZE_LG,
|
||||
HEADER_SIZE_MD,
|
||||
WINDOW_CONTROLS_WIDTH,
|
||||
} from "../lib/constants";
|
||||
import { WindowControls } from "./WindowControls";
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import { settingsAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useIsFullscreen } from '../hooks/useIsFullscreen';
|
||||
import { HEADER_SIZE_LG, HEADER_SIZE_MD, WINDOW_CONTROLS_WIDTH } from '../lib/constants';
|
||||
import { WindowControls } from './WindowControls';
|
||||
|
||||
interface HeaderSizeProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children?: ReactNode;
|
||||
size: "md" | "lg";
|
||||
size: 'md' | 'lg';
|
||||
ignoreControlsSpacing?: boolean;
|
||||
onlyXWindowControl?: boolean;
|
||||
hideControls?: boolean;
|
||||
@@ -35,10 +31,10 @@ export function HeaderSize({
|
||||
const s = { ...style };
|
||||
|
||||
// Set the height (use min-height because scaling font size may make it larger
|
||||
if (size === "md") s.minHeight = HEADER_SIZE_MD;
|
||||
if (size === "lg") s.minHeight = HEADER_SIZE_LG;
|
||||
if (size === 'md') s.minHeight = HEADER_SIZE_MD;
|
||||
if (size === 'lg') s.minHeight = HEADER_SIZE_LG;
|
||||
|
||||
if (type() === "macos") {
|
||||
if (type() === 'macos') {
|
||||
if (!isFullscreen) {
|
||||
// Add large padding for window controls
|
||||
s.paddingLeft = 72 / settings.interfaceScale;
|
||||
@@ -63,16 +59,16 @@ export function HeaderSize({
|
||||
style={finalStyle}
|
||||
className={classNames(
|
||||
className,
|
||||
"pt-[1px]", // Make up for bottom border
|
||||
"select-none relative",
|
||||
"w-full border-b border-border-subtle min-w-0",
|
||||
'pt-[1px]', // Make up for bottom border
|
||||
'select-none relative',
|
||||
'w-full border-b border-border-subtle min-w-0',
|
||||
)}
|
||||
>
|
||||
{/* NOTE: This needs display:grid or else the element shrinks (even though scrollable) */}
|
||||
<div
|
||||
className={classNames(
|
||||
"pointer-events-none h-full w-full overflow-x-auto hide-scrollbars grid",
|
||||
"px-1", // Give it some space on either end for focus outlines
|
||||
'pointer-events-none h-full w-full overflow-x-auto hide-scrollbars grid',
|
||||
'px-1', // Give it some space on either end for focus outlines
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -48,7 +48,7 @@ export function HeadersEditor({
|
||||
<div className="pb-2">
|
||||
{validInheritedHeaders?.map((pair, i) => (
|
||||
<PairEditorRow
|
||||
key={pair.id + '.' + i}
|
||||
key={`${pair.id}.${i}`}
|
||||
index={i}
|
||||
disabled
|
||||
disableDrag
|
||||
@@ -109,9 +109,8 @@ const valueType = (pair: Pair): InputProps['type'] => {
|
||||
name === 'set-cookie'
|
||||
) {
|
||||
return 'password';
|
||||
} else {
|
||||
return 'text';
|
||||
}
|
||||
return 'text';
|
||||
};
|
||||
|
||||
const valueAutocomplete = (headerName: string): GenericCompletionConfig | undefined => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { openFolderSettings } from '../commands/openFolderSettings';
|
||||
import { openWorkspaceSettings } from '../commands/openWorkspaceSettings';
|
||||
import { useHttpAuthenticationConfig } from '../hooks/useHttpAuthenticationConfig';
|
||||
@@ -66,9 +66,8 @@ export function HttpAuthenticationEditor({ model }: Props) {
|
||||
</Link>
|
||||
</EmptyStateText>
|
||||
);
|
||||
} else {
|
||||
return <EmptyStateText>No authentication</EmptyStateText>;
|
||||
}
|
||||
return <EmptyStateText>No authentication</EmptyStateText>;
|
||||
}
|
||||
|
||||
if (inheritedAuth.authenticationType === 'none') {
|
||||
@@ -84,6 +83,7 @@ export function HttpAuthenticationEditor({ model }: Props) {
|
||||
<p>
|
||||
Inherited from{' '}
|
||||
<button
|
||||
type="submit"
|
||||
className="underline hover:text-text"
|
||||
onClick={() => {
|
||||
if (inheritedAuth.model === 'folder') openFolderSettings(inheritedAuth.id, 'auth');
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React from 'react';
|
||||
import { useCurrentGraphQLSchema } from '../hooks/useIntrospectGraphQL';
|
||||
import { workspaceLayoutAtom } from '../lib/atoms';
|
||||
import type { SlotProps } from './core/SplitLayout';
|
||||
@@ -17,7 +16,6 @@ interface Props {
|
||||
style: CSSProperties;
|
||||
}
|
||||
|
||||
|
||||
export function HttpRequestLayout({ activeRequest, style }: Props) {
|
||||
const showGraphQLDocExplorer = useAtomValue(showGraphQLDocExplorerAtom);
|
||||
const graphQLSchema = useCurrentGraphQLSchema(activeRequest);
|
||||
@@ -56,7 +54,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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React, { lazy, Suspense, useCallback, useMemo, useState } from 'react';
|
||||
import { lazy, Suspense, useCallback, useMemo, useState } from 'react';
|
||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||
import { useAuthTab } from '../hooks/useAuthTab';
|
||||
@@ -37,13 +37,13 @@ import { showToast } from '../lib/toast';
|
||||
import { BinaryFileEditor } from './BinaryFileEditor';
|
||||
import { ConfirmLargeRequestBody } from './ConfirmLargeRequestBody';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
import { Editor } from './core/Editor/LazyEditor';
|
||||
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||
import { Editor } from './core/Editor/LazyEditor';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import type { Pair } from './core/PairEditor';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
import { FormMultipartEditor } from './FormMultipartEditor';
|
||||
import { FormUrlencodedEditor } from './FormUrlencodedEditor';
|
||||
@@ -128,9 +128,9 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
const nonEmptyParameters = activeRequest.urlParameters.filter((p) => p.name || p.value);
|
||||
const items: Pair[] = [...nonEmptyParameters];
|
||||
for (const name of placeholderNames) {
|
||||
const index = items.findIndex((p) => p.name === name);
|
||||
if (index >= 0) {
|
||||
items[index]!.readOnlyName = true;
|
||||
const item = items.find((p) => p.name === name);
|
||||
if (item) {
|
||||
item.readOnlyName = true;
|
||||
} else {
|
||||
items.push({ name, value: '', enabled: true, readOnlyName: true, id: generateId() });
|
||||
}
|
||||
@@ -207,7 +207,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
showMethodToast(patch.method);
|
||||
}
|
||||
newContentType = bodyType === BODY_TYPE_OTHER ? 'text/plain' : bodyType;
|
||||
} else if (bodyType == BODY_TYPE_GRAPHQL) {
|
||||
} else if (bodyType === BODY_TYPE_GRAPHQL) {
|
||||
patch.method = 'POST';
|
||||
newContentType = 'application/json';
|
||||
showMethodToast(patch.method);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import type { ComponentType, CSSProperties } from 'react';
|
||||
import React, { lazy, Suspense, useCallback, useMemo } from 'react';
|
||||
import { lazy, Suspense, useCallback, useMemo } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||
@@ -18,8 +18,8 @@ import { HttpStatusTag } from './core/HttpStatusTag';
|
||||
import { LoadingIcon } from './core/LoadingIcon';
|
||||
import { SizeTag } from './core/SizeTag';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import type { TabItem} from './core/Tabs/Tabs';
|
||||
import { Tabs , TabContent} from './core/Tabs/Tabs';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
import { RecentHttpResponsesDropdown } from './RecentHttpResponsesDropdown';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clear, readText } from '@tauri-apps/plugin-clipboard-manager';
|
||||
import * as m from 'motion/react-m';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useImportCurl } from '../hooks/useImportCurl';
|
||||
import { useWindowFocus } from '../hooks/useWindowFocus';
|
||||
import { Button } from './core/Button';
|
||||
@@ -13,6 +13,7 @@ export function ImportCurlButton() {
|
||||
const importCurl = useImportCurl();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: none
|
||||
useEffect(() => {
|
||||
readText().then(setClipboardText);
|
||||
}, [focused]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { Button } from './core/Button';
|
||||
import { VStack } from './core/Stacks';
|
||||
|
||||
@@ -5,8 +5,8 @@ import { useAtomValue } from 'jotai';
|
||||
import type { ReactNode } from 'react';
|
||||
import { openSettings } from '../commands/openSettings';
|
||||
import { CargoFeature } from './CargoFeature';
|
||||
import { PillButton } from './core/PillButton';
|
||||
import type { ButtonProps } from './core/Button';
|
||||
import { PillButton } from './core/PillButton';
|
||||
|
||||
const details: Record<
|
||||
LicenseCheckStatus['type'],
|
||||
@@ -52,10 +52,7 @@ function LicenseBadgeCmp() {
|
||||
}
|
||||
|
||||
return (
|
||||
<PillButton
|
||||
color={detail.color}
|
||||
onClick={() => openSettings.mutate('license')}
|
||||
>
|
||||
<PillButton color={detail.color} onClick={() => openSettings.mutate('license')}>
|
||||
{detail.label}
|
||||
</PillButton>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { resolveResource } from '@tauri-apps/api/path';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
|
||||
@@ -89,7 +89,7 @@ const markdownComponents: Partial<Components> = {
|
||||
},
|
||||
code(props) {
|
||||
const { children, className, ref, ...extraProps } = props;
|
||||
delete extraProps.node;
|
||||
extraProps.node = undefined;
|
||||
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return match ? (
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import type {
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
WebsocketRequest} from '@yaakapp-internal/models';
|
||||
import {
|
||||
patchModel,
|
||||
workspacesAtom,
|
||||
} from '@yaakapp-internal/models';
|
||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import { patchModel, workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { router } from '../lib/router';
|
||||
import { showToast } from '../lib/toast';
|
||||
|
||||
@@ -2,7 +2,7 @@ import classNames from 'classnames';
|
||||
import { FocusTrap } from 'focus-trap-react';
|
||||
import * as m from 'motion/react-m';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { Portal } from './Portal';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -62,7 +62,8 @@
|
||||
ul {
|
||||
@apply list-disc;
|
||||
|
||||
ul, ol {
|
||||
ul,
|
||||
ol {
|
||||
@apply my-0;
|
||||
}
|
||||
}
|
||||
@@ -70,12 +71,14 @@
|
||||
ol {
|
||||
@apply list-decimal;
|
||||
|
||||
ol, ul {
|
||||
ol,
|
||||
ul {
|
||||
@apply my-0;
|
||||
}
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
ol,
|
||||
ul {
|
||||
@apply pl-6;
|
||||
|
||||
li p {
|
||||
@@ -133,27 +136,27 @@
|
||||
@apply block font-bold mb-1;
|
||||
@apply text-text-subtlest;
|
||||
|
||||
content: 'Note';
|
||||
content: "Note";
|
||||
}
|
||||
|
||||
&.x-theme-banner--secondary::before {
|
||||
content: 'Info';
|
||||
content: "Info";
|
||||
}
|
||||
|
||||
&.x-theme-banner--success::before {
|
||||
content: 'Tip';
|
||||
content: "Tip";
|
||||
}
|
||||
|
||||
&.x-theme-banner--notice::before {
|
||||
content: 'Important';
|
||||
content: "Important";
|
||||
}
|
||||
|
||||
&.x-theme-banner--warning::before {
|
||||
content: 'Warning';
|
||||
content: "Warning";
|
||||
}
|
||||
|
||||
&.x-theme-banner--danger::before {
|
||||
content: 'Caution';
|
||||
content: "Caution";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +204,7 @@
|
||||
|
||||
figcaption::before {
|
||||
@apply border-info absolute left-2 top-0 h-3.5 w-6 rounded-bl border-l border-b border-dotted;
|
||||
content: '';
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function RecentGrpcConnectionsDropdown({
|
||||
...connections.map((c) => ({
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
{formatDistanceToNowStrict(c.createdAt + 'Z')} ago •{' '}
|
||||
{formatDistanceToNowStrict(`${c.createdAt}Z`)} ago •{' '}
|
||||
<span className="font-mono text-sm">{c.elapsed}ms</span>
|
||||
</HStack>
|
||||
),
|
||||
|
||||
@@ -2,10 +2,10 @@ import classNames from 'classnames';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useKeyboardEvent } from '../hooks/useKeyboardEvent';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { router } from '../lib/router';
|
||||
|
||||
@@ -44,7 +44,7 @@ export function RecentWebsocketConnectionsDropdown({
|
||||
...connections.map((c) => ({
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
{formatDistanceToNowStrict(c.createdAt + 'Z')} ago •{' '}
|
||||
{formatDistanceToNowStrict(`${c.createdAt}Z`)} ago •{' '}
|
||||
<span className="font-mono text-sm">{c.elapsed}ms</span>
|
||||
</HStack>
|
||||
),
|
||||
|
||||
@@ -20,7 +20,7 @@ export function RedirectToLatestWorkspace() {
|
||||
return;
|
||||
}
|
||||
|
||||
(async function () {
|
||||
(async () => {
|
||||
const workspaceId = recentWorkspaces[0] ?? workspaces[0]?.id ?? 'n/a';
|
||||
const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? null;
|
||||
const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? null;
|
||||
@@ -37,5 +37,5 @@ export function RedirectToLatestWorkspace() {
|
||||
})();
|
||||
}, [recentWorkspaces, workspaces, workspaces.length]);
|
||||
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
const START_DISTANCE = 7;
|
||||
|
||||
@@ -72,12 +72,12 @@ export function ResizeHandle({
|
||||
onResizeEnd?.();
|
||||
}
|
||||
|
||||
moveState.current = { calledStart: false, xStart: e.clientX, yStart: e.clientY, move, up };
|
||||
moveState.current = { calledStart: false, xStart: e.clientX, yStart: e.clientY, move, up };
|
||||
|
||||
document.documentElement.addEventListener('mousemove', move);
|
||||
document.documentElement.addEventListener('mouseup', up);
|
||||
},
|
||||
[moveState, onResizeEnd, onResizeMove, onResizeStart, vertical],
|
||||
[onResizeEnd, onResizeMove, onResizeStart, vertical],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,6 +15,7 @@ export function ResponseHeaders({ response }: Props) {
|
||||
<div className="overflow-auto h-full pb-4">
|
||||
<KeyValueRows>
|
||||
{sortedHeaders.map((h, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: none
|
||||
<KeyValueRow labelColor="primary" key={i} label={h.name}>
|
||||
{h.value}
|
||||
</KeyValueRow>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { VStack } from './core/Stacks';
|
||||
export default function RouteError({ error }: { error: unknown }) {
|
||||
console.log('Error', error);
|
||||
const stringified = JSON.stringify(error);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
const message = (error as any).message ?? stringified;
|
||||
const stack =
|
||||
typeof error === 'object' && error != null && 'stack' in error ? String(error.stack) : null;
|
||||
@@ -18,7 +18,11 @@ export default function RouteError({ error }: { error: unknown }) {
|
||||
<FormattedError>
|
||||
{message}
|
||||
{stack && (
|
||||
<DetailsBanner color="secondary" className="mt-3 select-auto text-xs" summary="Stack Trace">
|
||||
<DetailsBanner
|
||||
color="secondary"
|
||||
className="mt-3 select-auto text-xs"
|
||||
summary="Stack Trace"
|
||||
>
|
||||
<div className="mt-2 text-xs">{stack}</div>
|
||||
</DetailsBanner>
|
||||
)}
|
||||
|
||||
@@ -120,7 +120,7 @@ export function SelectFile({
|
||||
size={size === 'auto' ? 'md' : size}
|
||||
variant="border"
|
||||
icon="x"
|
||||
title={'Unset ' + itemLabel}
|
||||
title={`Unset ${itemLabel}`}
|
||||
onClick={handleClear}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useSearch } from '@tanstack/react-router';
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import classNames from 'classnames';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useKeyPressEvent } from 'react-use';
|
||||
import { appInfo } from '../../lib/appInfo';
|
||||
import { capitalize } from '../../lib/capitalize';
|
||||
@@ -10,8 +10,8 @@ import { HStack } from '../core/Stacks';
|
||||
import type { TabItem } from '../core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from '../core/Tabs/Tabs';
|
||||
import { HeaderSize } from '../HeaderSize';
|
||||
import { SettingsInterface } from './SettingsInterface';
|
||||
import { SettingsGeneral } from './SettingsGeneral';
|
||||
import { SettingsInterface } from './SettingsInterface';
|
||||
import { SettingsLicense } from './SettingsLicense';
|
||||
import { SettingsPlugins } from './SettingsPlugins';
|
||||
import { SettingsProxy } from './SettingsProxy';
|
||||
|
||||
@@ -2,8 +2,8 @@ import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { appInfo } from '../../lib/appInfo';
|
||||
import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
|
||||
import { appInfo } from '../../lib/appInfo';
|
||||
import { revealInFinderText } from '../../lib/reveal';
|
||||
import { CargoFeature } from '../CargoFeature';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
@@ -108,8 +108,10 @@ export function SettingsGeneral() {
|
||||
placeholder="0"
|
||||
labelPosition="left"
|
||||
defaultValue={`${workspace.settingRequestTimeout}`}
|
||||
validate={(value) => parseInt(value) >= 0}
|
||||
onChange={(v) => patchModel(workspace, { settingRequestTimeout: parseInt(v) || 0 })}
|
||||
validate={(value) => Number.parseInt(value, 10) >= 0}
|
||||
onChange={(v) =>
|
||||
patchModel(workspace, { settingRequestTimeout: Number.parseInt(v, 10) || 0 })
|
||||
}
|
||||
type="number"
|
||||
/>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useLicense } from '@yaakapp-internal/license';
|
||||
import type { EditorKeymap, Settings } from '@yaakapp-internal/models';
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React from 'react';
|
||||
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { clamp } from '../../lib/clamp';
|
||||
import { showConfirm } from '../../lib/confirm';
|
||||
@@ -95,7 +95,7 @@ export function SettingsInterface() {
|
||||
defaultValue="14"
|
||||
value={`${settings.interfaceFontSize}`}
|
||||
options={fontSizeOptions}
|
||||
onChange={(v) => patchModel(settings, { interfaceFontSize: parseInt(v) })}
|
||||
onChange={(v) => patchModel(settings, { interfaceFontSize: Number.parseInt(v, 10) })}
|
||||
/>
|
||||
</HStack>
|
||||
<HStack space={2} alignItems="end">
|
||||
@@ -127,7 +127,7 @@ export function SettingsInterface() {
|
||||
value={`${settings.editorFontSize}`}
|
||||
options={fontSizeOptions}
|
||||
onChange={(v) =>
|
||||
patchModel(settings, { editorFontSize: clamp(parseInt(v) || 14, 8, 30) })
|
||||
patchModel(settings, { editorFontSize: clamp(Number.parseInt(v, 10) || 14, 8, 30) })
|
||||
}
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import { useLicense } from '@yaakapp-internal/license';
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useToggle } from '../../hooks/useToggle';
|
||||
import { pluralizeCount } from '../../lib/pluralize';
|
||||
import { CargoFeature } from '../CargoFeature';
|
||||
@@ -66,8 +66,7 @@ function SettingsLicenseCmp() {
|
||||
<br />
|
||||
<span className="opacity-50">
|
||||
You may continue using Yaak for personal use free, forever.
|
||||
<br />
|
||||
A license is required for commercial use.
|
||||
<br />A license is required for commercial use.
|
||||
</span>
|
||||
<Separator className="my-2" />
|
||||
<div className="flex flex-wrap items-center gap-x-2 text-sm text-notice">
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
uninstallPlugin,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useDebouncedValue } from '../../hooks/useDebouncedValue';
|
||||
import { useInstallPlugin } from '../../hooks/useInstallPlugin';
|
||||
import { usePluginInfo } from '../../hooks/usePluginInfo';
|
||||
@@ -306,7 +306,7 @@ function usePromptUninstall(pluginId: string | null, name: string) {
|
||||
if (pluginId == null) return;
|
||||
|
||||
const confirmed = await showConfirmDelete({
|
||||
id: 'uninstall-plugin-' + pluginId,
|
||||
id: `uninstall-plugin-${pluginId}`,
|
||||
title: 'Uninstall Plugin',
|
||||
confirmText: 'Uninstall',
|
||||
description: (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React from 'react';
|
||||
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { InlineCode } from '../core/InlineCode';
|
||||
import { PlainInput } from '../core/PlainInput';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
|
||||
import { useResolvedTheme } from '../../hooks/useResolvedTheme';
|
||||
@@ -127,7 +127,7 @@ export function SettingsTheme() {
|
||||
color={c}
|
||||
size="2xs"
|
||||
iconSize="xs"
|
||||
icon={icons[i % icons.length]!}
|
||||
icon={icons[i % icons.length] ?? 'info'}
|
||||
iconClassName="text"
|
||||
title={`${c}`}
|
||||
/>
|
||||
@@ -139,7 +139,7 @@ export function SettingsTheme() {
|
||||
variant="border"
|
||||
size="2xs"
|
||||
iconSize="xs"
|
||||
icon={icons[i % icons.length]!}
|
||||
icon={icons[i % icons.length] ?? 'info'}
|
||||
iconClassName="text"
|
||||
title={`${c}`}
|
||||
/>
|
||||
|
||||
@@ -84,7 +84,7 @@ const OPACITY_SUBTLE = 'opacity-80';
|
||||
function Sidebar({ className }: { className?: string }) {
|
||||
const [hidden, setHidden] = useSidebarHidden();
|
||||
const activeWorkspaceId = useAtomValue(activeWorkspaceAtom)?.id;
|
||||
const treeId = 'tree.' + (activeWorkspaceId ?? 'unknown');
|
||||
const treeId = `tree.${activeWorkspaceId ?? 'unknown'}`;
|
||||
const filterText = useAtomValue(sidebarFilterAtom);
|
||||
const [tree, allFields] = useAtomValue(sidebarTreeAtom) ?? [];
|
||||
const wrapperRef = useRef<HTMLElement>(null);
|
||||
@@ -95,8 +95,8 @@ function Sidebar({ className }: { className?: string }) {
|
||||
}, []);
|
||||
const allHidden = useMemo(() => {
|
||||
if (tree?.children?.length === 0) return false;
|
||||
else if (filterText) return tree?.children?.every((c) => c.hidden);
|
||||
else return true;
|
||||
if (filterText) return tree?.children?.every((c) => c.hidden);
|
||||
return true;
|
||||
}, [filterText, tree?.children]);
|
||||
|
||||
const focusActiveItem = useCallback(() => {
|
||||
@@ -119,7 +119,7 @@ function Sidebar({ className }: { className?: string }) {
|
||||
treeRef.current?.selectItem(activeId, true);
|
||||
}
|
||||
});
|
||||
}, [focusActiveItem]);
|
||||
}, []);
|
||||
|
||||
useHotKey(
|
||||
'sidebar.filter',
|
||||
@@ -250,12 +250,13 @@ function Sidebar({ className }: { className?: string }) {
|
||||
if (tree == null) return;
|
||||
|
||||
const next = (node: TreeNode<SidebarModel>, collapsed: Record<string, boolean>) => {
|
||||
let newCollapsed = { ...collapsed };
|
||||
for (const n of node.children ?? []) {
|
||||
if (n.item.model !== 'folder') continue;
|
||||
collapsed[n.item.id] = true;
|
||||
collapsed = next(n, collapsed);
|
||||
newCollapsed[n.item.id] = true;
|
||||
newCollapsed = next(n, collapsed);
|
||||
}
|
||||
return collapsed;
|
||||
return newCollapsed;
|
||||
};
|
||||
const collapsed = next(tree, {});
|
||||
jotaiStore.set(collapsedFamily(treeId), collapsed);
|
||||
@@ -263,14 +264,14 @@ function Sidebar({ className }: { className?: string }) {
|
||||
},
|
||||
'sidebar.selected.delete': {
|
||||
enable,
|
||||
cb: async function (items: SidebarModel[]) {
|
||||
cb: async (items: SidebarModel[]) => {
|
||||
await deleteModelWithConfirm(items);
|
||||
},
|
||||
},
|
||||
'sidebar.selected.rename': {
|
||||
enable,
|
||||
allowDefault: true,
|
||||
cb: async function (items: SidebarModel[]) {
|
||||
cb: async (items: SidebarModel[]) => {
|
||||
const item = items[0];
|
||||
if (items.length === 1 && item != null) {
|
||||
treeRef.current?.renameItem(item.id);
|
||||
@@ -280,9 +281,9 @@ function Sidebar({ className }: { className?: string }) {
|
||||
'sidebar.selected.duplicate': {
|
||||
priority: 10,
|
||||
enable,
|
||||
cb: async function (items: SidebarModel[]) {
|
||||
if (items.length === 1) {
|
||||
const item = items[0]!;
|
||||
cb: async (items: SidebarModel[]) => {
|
||||
if (items.length === 1 && items[0]) {
|
||||
const item = items[0];
|
||||
const newId = await duplicateModel(item);
|
||||
navigateToRequestOrFolderOrWorkspace(newId, item.model);
|
||||
} else {
|
||||
@@ -292,7 +293,7 @@ function Sidebar({ className }: { className?: string }) {
|
||||
},
|
||||
'request.send': {
|
||||
enable,
|
||||
cb: async function (items: SidebarModel[]) {
|
||||
cb: async (items: SidebarModel[]) => {
|
||||
await Promise.all(
|
||||
items
|
||||
.filter((i) => i.model === 'http_request')
|
||||
@@ -355,8 +356,7 @@ function Sidebar({ className }: { className?: string }) {
|
||||
: []
|
||||
).map((a) => ({
|
||||
label: a.label,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
leftSlot: <Icon icon={(a.icon as any) ?? 'empty'} />,
|
||||
leftSlot: <Icon icon={a.icon ?? 'empty'} />,
|
||||
onSelect: async () => {
|
||||
const request = getModel('http_request', child.id);
|
||||
if (request != null) await a.call(request);
|
||||
@@ -368,7 +368,7 @@ function Sidebar({ className }: { className?: string }) {
|
||||
).map((a) => ({
|
||||
label: a.label,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
leftSlot: <Icon icon={(a.icon as any) ?? 'empty'} />,
|
||||
leftSlot: <Icon icon={a.icon ?? 'empty'} />,
|
||||
onSelect: async () => {
|
||||
const request = getModel('grpc_request', child.id);
|
||||
if (request != null) await a.call(request);
|
||||
@@ -621,10 +621,10 @@ const sidebarTreeAtom = atom<[TreeNode<SidebarModel>, FieldDef[]] | null>((get)
|
||||
for (const item of allModels) {
|
||||
if ('folderId' in item && item.folderId == null) {
|
||||
childrenMap[item.workspaceId] = childrenMap[item.workspaceId] ?? [];
|
||||
childrenMap[item.workspaceId]!.push(item);
|
||||
childrenMap[item.workspaceId]?.push(item);
|
||||
} else if ('folderId' in item && item.folderId != null) {
|
||||
childrenMap[item.folderId] = childrenMap[item.folderId] ?? [];
|
||||
childrenMap[item.folderId]!.push(item);
|
||||
childrenMap[item.folderId]?.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,18 +729,18 @@ const SidebarLeftSlot = memo(function SidebarLeftSlot({
|
||||
}) {
|
||||
if (item.model === 'folder') {
|
||||
return <Icon icon="folder" />;
|
||||
} else if (item.model === 'workspace') {
|
||||
return null;
|
||||
} else {
|
||||
const isSelected = jotaiStore.get(isSelectedFamily({ treeId, itemId: item.id }));
|
||||
return (
|
||||
<HttpMethodTag
|
||||
short
|
||||
className={classNames('text-xs pl-1.5', !isSelected && OPACITY_SUBTLE)}
|
||||
request={item}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (item.model === 'workspace') {
|
||||
return null;
|
||||
}
|
||||
const isSelected = jotaiStore.get(isSelectedFamily({ treeId, itemId: item.id }));
|
||||
return (
|
||||
<HttpMethodTag
|
||||
short
|
||||
className={classNames('text-xs pl-1.5', !isSelected && OPACITY_SUBTLE)}
|
||||
request={item}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const SidebarInnerItem = memo(function SidebarInnerItem({
|
||||
|
||||
@@ -2,9 +2,9 @@ import { useMemo } from 'react';
|
||||
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
|
||||
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { CreateDropdown } from './CreateDropdown';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { CreateDropdown } from './CreateDropdown';
|
||||
|
||||
export function SidebarActions() {
|
||||
const floating = useShouldFloatSidebar();
|
||||
|
||||
@@ -50,7 +50,7 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
||||
return;
|
||||
}
|
||||
|
||||
(async function () {
|
||||
(async () => {
|
||||
const initial = collectArgumentValues(initialTokens, templateFunction);
|
||||
|
||||
// HACK: Replace the secure() function's encrypted `value` arg with the decrypted version so
|
||||
@@ -140,6 +140,7 @@ function InitializedTemplateFunctionDialog({
|
||||
);
|
||||
|
||||
const tooLarge = rendered.data ? rendered.data.length > 10000 : false;
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Only update this on rendered data change to keep secrets hidden on input change
|
||||
const dataContainsSecrets = useMemo(() => {
|
||||
for (const [name, value] of Object.entries(argValues)) {
|
||||
const arg = templateFunction.data?.args.find((a) => 'name' in a && a.name === name);
|
||||
@@ -149,8 +150,6 @@ function InitializedTemplateFunctionDialog({
|
||||
}
|
||||
}
|
||||
return false;
|
||||
// Only update this on rendered data change to keep secrets hidden on input change
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [rendered.data]);
|
||||
|
||||
if (templateFunction.data == null || templateFunction.isPending) {
|
||||
@@ -177,7 +176,7 @@ function InitializedTemplateFunctionDialog({
|
||||
name="value"
|
||||
type="password"
|
||||
placeholder="••••••••••••"
|
||||
defaultValue={String(argValues['value'] ?? '')}
|
||||
defaultValue={String(argValues.value ?? '')}
|
||||
onChange={(value) => setArgValues({ ...argValues, value })}
|
||||
/>
|
||||
) : (
|
||||
@@ -211,27 +210,28 @@ function InitializedTemplateFunctionDialog({
|
||||
)}
|
||||
/>
|
||||
</HStack>
|
||||
<InlineCode
|
||||
className={classNames(
|
||||
'relative',
|
||||
'whitespace-pre-wrap !select-text cursor-text max-h-[10rem] overflow-auto hide-scrollbars !border-text-subtlest',
|
||||
tooLarge && 'italic text-danger',
|
||||
)}
|
||||
>
|
||||
{rendered.error || tagText.error ? (
|
||||
<em className="text-danger">
|
||||
{`${rendered.error || tagText.error}`.replace(/^Render Error: /, '')}
|
||||
</em>
|
||||
) : dataContainsSecrets && !showSecretsInPreview ? (
|
||||
<span className="italic text-text-subtle">
|
||||
------ sensitive values hidden ------
|
||||
</span>
|
||||
) : tooLarge ? (
|
||||
'too large to preview'
|
||||
) : (
|
||||
rendered.data || <> </>
|
||||
)}
|
||||
<div className="absolute right-0 top-0 flex items-center">
|
||||
<div className="relative w-full max-h-[10rem]">
|
||||
<InlineCode
|
||||
className={classNames(
|
||||
'block whitespace-pre-wrap !select-text cursor-text max-h-[10rem] overflow-auto hide-scrollbars !border-text-subtlest',
|
||||
tooLarge && 'italic text-danger',
|
||||
)}
|
||||
>
|
||||
{rendered.error || tagText.error ? (
|
||||
<em className="text-danger">
|
||||
{`${rendered.error || tagText.error}`.replace(/^Render Error: /, '')}
|
||||
</em>
|
||||
) : dataContainsSecrets && !showSecretsInPreview ? (
|
||||
<span className="italic text-text-subtle">
|
||||
------ sensitive values hidden ------
|
||||
</span>
|
||||
) : tooLarge ? (
|
||||
'too large to preview'
|
||||
) : (
|
||||
rendered.data || <> </>
|
||||
)}
|
||||
</InlineCode>
|
||||
<div className="absolute right-0.5 top-0 bottom-0 flex items-center">
|
||||
<IconButton
|
||||
size="xs"
|
||||
icon="refresh"
|
||||
@@ -243,7 +243,7 @@ function InitializedTemplateFunctionDialog({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</InlineCode>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<span />
|
||||
@@ -263,22 +263,23 @@ function InitializedTemplateFunctionDialog({
|
||||
);
|
||||
}
|
||||
|
||||
TemplateFunctionDialog.show = function (
|
||||
TemplateFunctionDialog.show = (
|
||||
fn: TemplateFunction,
|
||||
tagValue: string,
|
||||
startPos: number,
|
||||
view: EditorView,
|
||||
) {
|
||||
) => {
|
||||
const initialTokens = parseTemplate(tagValue);
|
||||
showDialog({
|
||||
id: 'template-function-' + Math.random(), // Allow multiple at once
|
||||
id: `template-function-${Math.random()}`, // Allow multiple at once
|
||||
size: 'md',
|
||||
className: 'h-[60rem]',
|
||||
noPadding: true,
|
||||
title: <InlineCode>{fn.name}(…)</InlineCode>,
|
||||
description: fn.description,
|
||||
render: ({ hide }) => {
|
||||
const model = jotaiStore.get(activeWorkspaceAtom)!;
|
||||
const model = jotaiStore.get(activeWorkspaceAtom);
|
||||
if (model == null) return null;
|
||||
return (
|
||||
<TemplateFunctionDialog
|
||||
templateFunction={fn}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { AnimatePresence } from 'motion/react';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { type ReactNode } from 'react';
|
||||
import { AnimatePresence } from 'motion/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { hideToast, toastsAtom } from '../lib/toast';
|
||||
import { Toast, type ToastProps } from './core/Toast';
|
||||
import { Portal } from './Portal';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
import { Portal } from './Portal';
|
||||
|
||||
export type ToastInstance = {
|
||||
id: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import type { FormEvent, ReactNode } from 'react';
|
||||
import { useCallback, memo, useRef, useState } from 'react';
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import type { IconProps } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
|
||||
@@ -15,7 +15,7 @@ type Props = {
|
||||
export function UrlParametersEditor({ pairs, forceUpdateKey, onChange, stateKey }: Props) {
|
||||
const pairEditorRef = useRef<PairEditorHandle>(null);
|
||||
const handleInitPairEditorRef = useCallback((ref: PairEditorHandle) => {
|
||||
return (pairEditorRef.current = ref);
|
||||
pairEditorRef.current = ref;
|
||||
}, []);
|
||||
|
||||
const [{ urlParametersKey }] = useRequestEditor();
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { workspaceLayoutAtom } from '../lib/atoms';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { WebsocketRequestPane } from './WebsocketRequestPane';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { closeWebsocket, connectWebsocket, sendWebsocket } from '@yaakapp-intern
|
||||
import classNames from 'classnames';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { getActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { getActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||
@@ -25,8 +25,8 @@ import { generateId } from '../lib/generateId';
|
||||
import { prepareImportQuerystring } from '../lib/prepareImportQuerystring';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
import { Editor } from './core/Editor/LazyEditor';
|
||||
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||
import { Editor } from './core/Editor/LazyEditor';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import type { Pair } from './core/PairEditor';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
@@ -81,9 +81,9 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
const nonEmptyParameters = activeRequest.urlParameters.filter((p) => p.name || p.value);
|
||||
const items: Pair[] = [...nonEmptyParameters];
|
||||
for (const name of placeholderNames) {
|
||||
const index = items.findIndex((p) => p.name === name);
|
||||
if (index >= 0) {
|
||||
items[index]!.readOnlyName = true;
|
||||
const item = items.find((p) => p.name === name);
|
||||
if (item) {
|
||||
item.readOnlyName = true;
|
||||
} else {
|
||||
items.push({ name, value: '', enabled: true, readOnlyName: true, id: generateId() });
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
||||
? 'Connection open'
|
||||
: `Message ${activeEvent.isServer ? 'Received' : 'Sent'}`}
|
||||
</div>
|
||||
{message != '' && (
|
||||
{message !== '' && (
|
||||
<HStack space={1}>
|
||||
<Button
|
||||
variant="border"
|
||||
@@ -215,6 +215,7 @@ function EventRow({
|
||||
return (
|
||||
<div className="px-1" ref={ref}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={classNames(
|
||||
'w-full grid grid-cols-[auto_minmax(0,3fr)_auto] gap-2 items-center text-left',
|
||||
@@ -252,7 +253,7 @@ function EventRow({
|
||||
{/*{error && <span className="text-warning"> ({error})</span>}*/}
|
||||
</div>
|
||||
<div className={classNames('opacity-50 text-xs')}>
|
||||
{format(createdAt + 'Z', 'HH:mm:ss.SSS')}
|
||||
{format(`${createdAt}Z`, 'HH:mm:ss.SSS')}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { type } from '@tauri-apps/plugin-os';
|
||||
import { settingsAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { WINDOW_CONTROLS_WIDTH } from '../lib/constants';
|
||||
import { Button } from './core/Button';
|
||||
import { HStack } from './core/Stacks';
|
||||
@@ -37,6 +37,7 @@ export function WindowControls({ className, onlyX }: Props) {
|
||||
onClick={() => getCurrentWebviewWindow().minimize()}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>Minimize</title>
|
||||
<path fill="currentColor" d="M14 8v1H3V8z" />
|
||||
</svg>
|
||||
</Button>
|
||||
@@ -57,6 +58,7 @@ export function WindowControls({ className, onlyX }: Props) {
|
||||
>
|
||||
{maximized ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>Unmaximize</title>
|
||||
<g fill="currentColor">
|
||||
<path d="M3 5v9h9V5zm8 8H4V6h7z" />
|
||||
<path fillRule="evenodd" d="M5 5h1V4h7v7h-1v1h2V3H5z" clipRule="evenodd" />
|
||||
@@ -64,6 +66,7 @@ export function WindowControls({ className, onlyX }: Props) {
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>Maximize</title>
|
||||
<path fill="currentColor" d="M3 3v10h10V3zm9 9H4V4h8z" />
|
||||
</svg>
|
||||
)}
|
||||
@@ -76,6 +79,7 @@ export function WindowControls({ className, onlyX }: Props) {
|
||||
onClick={() => getCurrentWebviewWindow().close()}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>Close</title>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
|
||||
@@ -30,12 +30,12 @@ import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTi
|
||||
import { duplicateRequestOrFolderAndNavigate } from '../lib/duplicateRequestOrFolderAndNavigate';
|
||||
import { importData } from '../lib/importData';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { CreateDropdown } from './CreateDropdown';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { HotKeyList } from './core/HotKeyList';
|
||||
import { FeedbackLink } from './core/Link';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { CreateDropdown } from './CreateDropdown';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
import { FolderLayout } from './FolderLayout';
|
||||
import { GrpcConnectionLayout } from './GrpcConnectionLayout';
|
||||
@@ -221,11 +221,14 @@ function WorkspaceBody() {
|
||||
|
||||
if (activeRequest?.model === 'grpc_request') {
|
||||
return <GrpcConnectionLayout style={body} />;
|
||||
} else if (activeRequest?.model === 'websocket_request') {
|
||||
}
|
||||
if (activeRequest?.model === 'websocket_request') {
|
||||
return <WebsocketRequestLayout style={body} activeRequest={activeRequest} />;
|
||||
} else if (activeRequest?.model === 'http_request') {
|
||||
}
|
||||
if (activeRequest?.model === 'http_request') {
|
||||
return <HttpRequestLayout activeRequest={activeRequest} style={body} />;
|
||||
} else if (activeFolder != null) {
|
||||
}
|
||||
if (activeFolder != null) {
|
||||
return <FolderLayout folder={activeFolder} style={body} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,8 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
// Always open a new window if the selected one is already active
|
||||
switchWorkspace.mutate({ workspaceId, inNewWindow: true });
|
||||
return;
|
||||
} else if (typeof settings.openWorkspaceNewWindow === 'boolean') {
|
||||
}
|
||||
if (typeof settings.openWorkspaceNewWindow === 'boolean') {
|
||||
switchWorkspace.mutate({ workspaceId, inNewWindow: settings.openWorkspaceNewWindow });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export function WorkspaceEncryptionSetting({ size, expanded, onDone, onEnabledEn
|
||||
setKey({ key: null, error: `${err}` });
|
||||
},
|
||||
);
|
||||
}, [setKey, workspaceMeta, workspaceMeta?.encryptionKey]);
|
||||
}, [workspaceMeta, workspaceMeta?.encryptionKey]);
|
||||
|
||||
if (key == null || workspace == null || workspaceMeta == null) {
|
||||
return null;
|
||||
@@ -117,7 +117,7 @@ export function WorkspaceEncryptionSetting({ size, expanded, onDone, onEnabledEn
|
||||
await enableEncryption(workspaceMeta.workspaceId);
|
||||
setJustEnabledEncryption(true);
|
||||
} catch (err) {
|
||||
setError('Failed to enable encryption: ' + err);
|
||||
setError(`Failed to enable encryption: ${err}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -242,10 +242,11 @@ function HighlightedKey({ keyText, show }: { keyText: string; show: boolean }) {
|
||||
keyText.split('').map((c, i) => {
|
||||
return (
|
||||
<span
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: it's fine
|
||||
key={i}
|
||||
className={classNames(
|
||||
c.match(/[0-9]/) && 'text-info',
|
||||
c == '-' && 'text-text-subtle',
|
||||
c === '-' && 'text-text-subtle',
|
||||
)}
|
||||
>
|
||||
{c}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import classNames from 'classnames';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import React, { memo } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from '../hooks/useActiveWorkspace';
|
||||
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
|
||||
import { workspaceLayoutAtom } from '../lib/atoms';
|
||||
import { setupOrConfigureEncryption } from '../lib/setupOrConfigureEncryption';
|
||||
import { CookieDropdown } from './CookieDropdown';
|
||||
import { PillButton } from './core/PillButton';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { PillButton } from './core/PillButton';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { EnvironmentActionsDropdown } from './EnvironmentActionsDropdown';
|
||||
import { ImportCurlButton } from './ImportCurlButton';
|
||||
@@ -37,7 +37,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
||||
'grid grid-cols-[auto_minmax(0,1fr)_auto] items-center w-full h-full',
|
||||
)}
|
||||
>
|
||||
<HStack space={0.5} className={classNames("flex-1 pointer-events-none")}>
|
||||
<HStack space={0.5} className={classNames('flex-1 pointer-events-none')}>
|
||||
<SidebarActions />
|
||||
<CookieDropdown />
|
||||
<HStack className="min-w-0">
|
||||
|
||||
@@ -39,6 +39,8 @@ export function AutoScroller<T>({ data, render, header }: Props<T>) {
|
||||
useLayoutEffect(() => {
|
||||
if (!autoScroll) return;
|
||||
|
||||
data.length; // Make linter happy. We want to refresh when length changes
|
||||
|
||||
const el = containerRef.current;
|
||||
if (el == null) return;
|
||||
|
||||
@@ -69,21 +71,26 @@ export function AutoScroller<T>({ data, render, header }: Props<T>) {
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualItem) => (
|
||||
<div
|
||||
key={virtualItem.key}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: `${virtualItem.size}px`,
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}}
|
||||
>
|
||||
{render(data[virtualItem.index]!, virtualItem.index)}
|
||||
</div>
|
||||
))}
|
||||
{rowVirtualizer.getVirtualItems().map((virtualItem) => {
|
||||
const item = data[virtualItem.index];
|
||||
return (
|
||||
item != null && (
|
||||
<div
|
||||
key={virtualItem.key}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: `${virtualItem.size}px`,
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}}
|
||||
>
|
||||
{render(item, virtualItem.index)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import { type ReactNode } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Icon } from './Icon';
|
||||
import { IconTooltip } from './IconTooltip';
|
||||
import { HStack } from './Stacks';
|
||||
@@ -58,7 +58,9 @@ export function Checkbox({
|
||||
</div>
|
||||
</div>
|
||||
{!hideLabel && (
|
||||
<div className={classNames('text-sm', fullWidth && 'w-full', disabled && 'opacity-disabled')}>
|
||||
<div
|
||||
className={classNames('text-sm', fullWidth && 'w-full', disabled && 'opacity-disabled')}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Color } from "@yaakapp-internal/plugins";
|
||||
import type { FormEvent } from "react";
|
||||
import { useState } from "react";
|
||||
import { CopyIconButton } from "../CopyIconButton";
|
||||
import { Button } from "./Button";
|
||||
import { PlainInput } from "./PlainInput";
|
||||
import { HStack } from "./Stacks";
|
||||
import type { Color } from '@yaakapp-internal/plugins';
|
||||
import type { FormEvent } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { CopyIconButton } from '../CopyIconButton';
|
||||
import { Button } from './Button';
|
||||
import { PlainInput } from './PlainInput';
|
||||
import { HStack } from './Stacks';
|
||||
|
||||
export interface ConfirmProps {
|
||||
onHide: () => void;
|
||||
@@ -19,9 +19,9 @@ export function Confirm({
|
||||
onResult,
|
||||
confirmText,
|
||||
requireTyping,
|
||||
color = "primary",
|
||||
color = 'primary',
|
||||
}: ConfirmProps) {
|
||||
const [confirm, setConfirm] = useState<string>("");
|
||||
const [confirm, setConfirm] = useState<string>('');
|
||||
const handleHide = () => {
|
||||
onResult(false);
|
||||
onHide();
|
||||
@@ -61,13 +61,9 @@ export function Confirm({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<HStack
|
||||
space={2}
|
||||
justifyContent="start"
|
||||
className="mt-2 mb-4 flex-row-reverse"
|
||||
>
|
||||
<HStack space={2} justifyContent="start" className="mt-2 mb-4 flex-row-reverse">
|
||||
<Button type="submit" color={color} disabled={!didConfirm}>
|
||||
{confirmText ?? "Confirm"}
|
||||
{confirmText ?? 'Confirm'}
|
||||
</Button>
|
||||
<Button onClick={handleHide} variant="border">
|
||||
Cancel
|
||||
|
||||
@@ -24,9 +24,7 @@ export function DetailsBanner({ className, color, summary, children, ...extraPro
|
||||
/>
|
||||
{summary}
|
||||
</summary>
|
||||
<div className="mt-1.5">
|
||||
{children}
|
||||
</div>
|
||||
<div className="mt-1.5">{children}</div>
|
||||
</details>
|
||||
</Banner>
|
||||
);
|
||||
|
||||
@@ -43,7 +43,6 @@ export function Dialog({
|
||||
|
||||
return (
|
||||
<Overlay open={open} onClose={disableBackdropClose ? undefined : onClose} portalName="dialog">
|
||||
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
|
||||
<div
|
||||
role="dialog"
|
||||
className={classNames(
|
||||
|
||||
@@ -12,7 +12,10 @@ export function DismissibleBanner({
|
||||
id,
|
||||
actions,
|
||||
...props
|
||||
}: BannerProps & { id: string; actions?: { label: string; onClick: () => void; color?: Color }[] }) {
|
||||
}: BannerProps & {
|
||||
id: string;
|
||||
actions?: { label: string; onClick: () => void; color?: Color }[];
|
||||
}) {
|
||||
const { set: setDismissed, value: dismissed } = useKeyValue<boolean>({
|
||||
namespace: 'global',
|
||||
key: ['dismiss-banner', id],
|
||||
@@ -28,9 +31,9 @@ export function DismissibleBanner({
|
||||
>
|
||||
{children}
|
||||
<HStack space={1.5}>
|
||||
{actions?.map((a, i) => (
|
||||
{actions?.map((a) => (
|
||||
<Button
|
||||
key={a.label + i}
|
||||
key={a.label}
|
||||
variant="border"
|
||||
color={a.color ?? props.color}
|
||||
size="xs"
|
||||
|
||||
@@ -3,15 +3,16 @@ import { atom } from 'jotai';
|
||||
import * as m from 'motion/react-m';
|
||||
import type {
|
||||
CSSProperties,
|
||||
FocusEvent as ReactFocusEvent,
|
||||
HTMLAttributes,
|
||||
MouseEvent,
|
||||
ReactElement,
|
||||
FocusEvent as ReactFocusEvent,
|
||||
KeyboardEvent as ReactKeyboardEvent,
|
||||
ReactNode,
|
||||
RefObject,
|
||||
SetStateAction,
|
||||
} from 'react';
|
||||
import React, {
|
||||
import {
|
||||
Children,
|
||||
cloneElement,
|
||||
forwardRef,
|
||||
@@ -30,6 +31,7 @@ import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
||||
import { generateId } from '../../lib/generateId';
|
||||
import { getNodeText } from '../../lib/getNodeText';
|
||||
import { jotaiStore } from '../../lib/jotai';
|
||||
import { ErrorBoundary } from '../ErrorBoundary';
|
||||
import { Overlay } from '../Overlay';
|
||||
import { Button } from './Button';
|
||||
import { HotKey } from './HotKey';
|
||||
@@ -37,7 +39,6 @@ import { Icon } from './Icon';
|
||||
import { LoadingIcon } from './LoadingIcon';
|
||||
import { Separator } from './Separator';
|
||||
import { HStack, VStack } from './Stacks';
|
||||
import { ErrorBoundary } from '../ErrorBoundary';
|
||||
|
||||
export type DropdownItemSeparator = {
|
||||
type: 'separator';
|
||||
@@ -96,24 +97,24 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
|
||||
ref,
|
||||
) {
|
||||
const id = useRef(generateId());
|
||||
const [isOpen, _setIsOpen] = useState<boolean>(false);
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
return jotaiStore.sub(openAtom, () => {
|
||||
const globalOpenId = jotaiStore.get(openAtom);
|
||||
const newIsOpen = globalOpenId === id.current;
|
||||
if (newIsOpen !== isOpen) {
|
||||
_setIsOpen(newIsOpen);
|
||||
setIsOpen(newIsOpen);
|
||||
}
|
||||
});
|
||||
}, [isOpen, _setIsOpen]);
|
||||
}, [isOpen]);
|
||||
|
||||
// const [isOpen, _setIsOpen] = useState<boolean>(false);
|
||||
const [defaultSelectedIndex, setDefaultSelectedIndex] = useState<number | null>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const menuRef = useRef<Omit<DropdownRef, 'open'>>(null);
|
||||
|
||||
const setIsOpen = useCallback(
|
||||
const handleSetIsOpen = useCallback(
|
||||
(o: SetStateAction<boolean>) => {
|
||||
jotaiStore.set(openAtom, (prevId) => {
|
||||
const prevIsOpen = prevId === id.current;
|
||||
@@ -121,9 +122,11 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
|
||||
// Persist background color of button until we close the dropdown
|
||||
if (newIsOpen) {
|
||||
onOpen?.();
|
||||
buttonRef.current!.style.backgroundColor = window
|
||||
.getComputedStyle(buttonRef.current!)
|
||||
.getPropertyValue('background-color');
|
||||
if (buttonRef.current) {
|
||||
buttonRef.current.style.backgroundColor = window
|
||||
.getComputedStyle(buttonRef.current)
|
||||
.getPropertyValue('background-color');
|
||||
}
|
||||
}
|
||||
return newIsOpen ? id.current : null; // Set global atom to current ID to signify open state
|
||||
});
|
||||
@@ -136,7 +139,7 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
// Clear persisted BG
|
||||
buttonRef.current!.style.backgroundColor = '';
|
||||
if (buttonRef.current) buttonRef.current.style.backgroundColor = '';
|
||||
// Set to different value when opened and closed to force it to update. This is to force
|
||||
// <Menu/> to reset its selected-index state, which it does when this prop changes
|
||||
setDefaultSelectedIndex(null);
|
||||
@@ -157,38 +160,38 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
|
||||
else this.close();
|
||||
},
|
||||
open(index?: number) {
|
||||
setIsOpen(true);
|
||||
handleSetIsOpen(true);
|
||||
setDefaultSelectedIndex(index ?? -1);
|
||||
},
|
||||
close() {
|
||||
setIsOpen(false);
|
||||
handleSetIsOpen(false);
|
||||
},
|
||||
}),
|
||||
[isOpen, setIsOpen, menuRefCurrent],
|
||||
[isOpen, handleSetIsOpen, menuRefCurrent],
|
||||
);
|
||||
|
||||
useHotKey(hotKeyAction ?? null, () => {
|
||||
setDefaultSelectedIndex(0);
|
||||
setIsOpen(true);
|
||||
handleSetIsOpen(true);
|
||||
});
|
||||
|
||||
const child = useMemo(() => {
|
||||
const existingChild = Children.only(children);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const props: any = {
|
||||
...existingChild.props,
|
||||
ref: buttonRef,
|
||||
'aria-haspopup': 'true',
|
||||
onClick:
|
||||
existingChild.props?.onClick ??
|
||||
((e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsOpen((o) => !o); // Toggle dropdown
|
||||
}),
|
||||
};
|
||||
const props: HTMLAttributes<HTMLButtonElement> & { ref: RefObject<HTMLButtonElement | null> } =
|
||||
{
|
||||
...existingChild.props,
|
||||
ref: buttonRef,
|
||||
'aria-haspopup': 'true',
|
||||
onClick:
|
||||
existingChild.props?.onClick ??
|
||||
((e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleSetIsOpen((o) => !o); // Toggle dropdown
|
||||
}),
|
||||
};
|
||||
return cloneElement(existingChild, props);
|
||||
}, [children, setIsOpen]);
|
||||
}, [children, handleSetIsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
buttonRef.current?.setAttribute('aria-expanded', isOpen.toString());
|
||||
@@ -204,7 +207,7 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
|
||||
return (
|
||||
<>
|
||||
{child}
|
||||
<ErrorBoundary name={`Dropdown Menu`}>
|
||||
<ErrorBoundary name={'Dropdown Menu'}>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
showTriangle
|
||||
@@ -213,7 +216,7 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
|
||||
defaultSelectedIndex={defaultSelectedIndex}
|
||||
items={items}
|
||||
triggerShape={triggerRect ?? null}
|
||||
onClose={() => setIsOpen(false)}
|
||||
onClose={() => handleSetIsOpen(false)}
|
||||
isOpen={isOpen}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
@@ -303,7 +306,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
}, [onClose]);
|
||||
|
||||
// Close menu on space bar
|
||||
const handleMenuKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
const handleMenuKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>) => {
|
||||
const isCharacter = e.key.length === 1;
|
||||
const isSpecial = e.ctrlKey || e.metaKey || e.altKey;
|
||||
if (isCharacter && !isSpecial) {
|
||||
@@ -348,7 +351,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
);
|
||||
|
||||
const handleNext = useCallback(
|
||||
(incrBy: number = 1) => {
|
||||
(incrBy = 1) => {
|
||||
setSelectedIndex((currIndex) => {
|
||||
let nextIndex = (currIndex ?? -1) + incrBy;
|
||||
const maxTries = items.length;
|
||||
@@ -474,7 +477,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
|
||||
const handleFocus = useCallback(
|
||||
(i: DropdownItem) => {
|
||||
const index = filteredItems.findIndex((item) => item === i) ?? null;
|
||||
const index = filteredItems.indexOf(i) ?? null;
|
||||
setSelectedIndex(index);
|
||||
},
|
||||
[filteredItems, setSelectedIndex],
|
||||
@@ -556,6 +559,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
if (item.type === 'separator') {
|
||||
return (
|
||||
<Separator
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: Nothing else available
|
||||
key={i}
|
||||
className={classNames('my-1.5', item.label ? 'ml-2' : null)}
|
||||
>
|
||||
@@ -565,12 +569,9 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
}
|
||||
if (item.type === 'content') {
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
|
||||
<div
|
||||
key={i}
|
||||
className={classNames('my-1 mx-2 max-w-xs')}
|
||||
onClick={onClose}
|
||||
>
|
||||
// biome-ignore lint/a11y/noStaticElementInteractions: Needs to be clickable but want to support nested buttons
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: index is fine
|
||||
<div key={i} className={classNames('my-1 mx-2 max-w-xs')} onClick={onClose}>
|
||||
{item.label}
|
||||
</div>
|
||||
);
|
||||
@@ -580,7 +581,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
focused={i === selectedIndex}
|
||||
onFocus={handleFocus}
|
||||
onSelect={handleSelect}
|
||||
key={`item_${i}`}
|
||||
key={`item_${item.label}`}
|
||||
item={item}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,8 +7,7 @@ export class BetterMatchDecorator extends MatchDecorator {
|
||||
updateDeco(update: ViewUpdate, deco: DecorationSet): DecorationSet {
|
||||
if (!update.startState.selection.eq(update.state.selection)) {
|
||||
return super.createDeco(update.view);
|
||||
} else {
|
||||
return super.updateDeco(update, deco);
|
||||
}
|
||||
return super.updateDeco(update, deco);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
}
|
||||
|
||||
&:not(.cm-focused) {
|
||||
.cm-cursor, .cm-fat-cursor {
|
||||
.cm-cursor,
|
||||
.cm-fat-cursor {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
@@ -77,7 +78,6 @@
|
||||
.cm-gutterElement {
|
||||
@apply cursor-default;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.cm-gutter-lint {
|
||||
@@ -91,7 +91,7 @@
|
||||
@apply cursor-default opacity-80 hover:opacity-100 transition-opacity;
|
||||
@apply rounded-full w-[0.9em] h-[0.9em];
|
||||
|
||||
content: '';
|
||||
content: "";
|
||||
|
||||
&.cm-lint-marker-error {
|
||||
@apply bg-danger;
|
||||
@@ -168,7 +168,8 @@
|
||||
@apply outline-text;
|
||||
@apply bg-text !important;
|
||||
|
||||
&, * {
|
||||
&,
|
||||
* {
|
||||
@apply text-surface font-semibold !important;
|
||||
}
|
||||
}
|
||||
@@ -199,8 +200,7 @@
|
||||
}
|
||||
|
||||
.cm-editor .fold-gutter-icon::after {
|
||||
@apply block w-1.5 h-1.5 p-0.5 border-transparent
|
||||
border-l border-b border-l-[currentColor] border-b-[currentColor] content-[''];
|
||||
@apply block w-1.5 h-1.5 p-0.5 border-transparent border-l border-b border-l-[currentColor] border-b-[currentColor] content-[''];
|
||||
}
|
||||
|
||||
/* Rotate the fold gutter chevron when open */
|
||||
@@ -270,7 +270,8 @@
|
||||
|
||||
/* Style the tooltip for popping up "open in browser" and other stuff */
|
||||
|
||||
a, button {
|
||||
a,
|
||||
button {
|
||||
@apply text-text hover:bg-surface-highlight w-full h-sm flex items-center px-2 rounded;
|
||||
}
|
||||
|
||||
@@ -279,7 +280,7 @@
|
||||
|
||||
&::after {
|
||||
@apply text-text bg-text h-3 w-3 ml-1;
|
||||
content: '';
|
||||
content: "";
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='black' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E");
|
||||
-webkit-mask-size: contain;
|
||||
display: inline-block;
|
||||
@@ -300,59 +301,59 @@
|
||||
@apply opacity-80 italic;
|
||||
|
||||
&::after {
|
||||
content: 'a' !important; /* Default (eg. for GraphQL) */
|
||||
content: "a" !important; /* Default (eg. for GraphQL) */
|
||||
}
|
||||
|
||||
&.cm-completionIcon-function::after {
|
||||
content: 'f' !important;
|
||||
content: "f" !important;
|
||||
@apply text-info;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-variable::after {
|
||||
content: 'x' !important;
|
||||
content: "x" !important;
|
||||
@apply text-primary;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-namespace::after {
|
||||
content: 'n' !important;
|
||||
content: "n" !important;
|
||||
@apply text-warning;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-constant::after {
|
||||
content: 'c' !important;
|
||||
content: "c" !important;
|
||||
@apply text-notice;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-class::after {
|
||||
content: 'o' !important;
|
||||
content: "o" !important;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-enum::after {
|
||||
content: 'e' !important;
|
||||
content: "e" !important;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-interface::after {
|
||||
content: 'i' !important;
|
||||
content: "i" !important;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-keyword::after {
|
||||
content: 'k' !important;
|
||||
content: "k" !important;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-method::after {
|
||||
content: 'm' !important;
|
||||
content: "m" !important;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-property::after {
|
||||
content: 'a' !important;
|
||||
content: "a" !important;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-text::after {
|
||||
content: 't' !important;
|
||||
content: "t" !important;
|
||||
}
|
||||
|
||||
&.cm-completionIcon-type::after {
|
||||
content: 't' !important;
|
||||
content: "t" !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +407,7 @@
|
||||
@apply appearance-none bg-none cursor-default;
|
||||
}
|
||||
|
||||
button[name='close'] {
|
||||
button[name="close"] {
|
||||
@apply text-text-subtle hocus:text-text px-2 -mr-1.5 !important;
|
||||
}
|
||||
|
||||
@@ -421,7 +422,7 @@
|
||||
|
||||
/* Hide the "All" button */
|
||||
|
||||
button[name='select'] {
|
||||
button[name="select"] {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
import { activeEnvironmentAtom } from '../../../hooks/useActiveEnvironment';
|
||||
import type { WrappedEnvironmentVariable } from '../../../hooks/useEnvironmentVariables';
|
||||
import { useEnvironmentVariables } from '../../../hooks/useEnvironmentVariables';
|
||||
import { useRandomKey } from '../../../hooks/useRandomKey';
|
||||
import { useRequestEditor } from '../../../hooks/useRequestEditor';
|
||||
import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplateFunctions';
|
||||
import { editEnvironment } from '../../../lib/editEnvironment';
|
||||
@@ -113,7 +112,7 @@ export function Editor({
|
||||
disabled,
|
||||
extraExtensions,
|
||||
forcedEnvironmentId,
|
||||
forceUpdateKey: forceUpdateKeyFromAbove,
|
||||
forceUpdateKey,
|
||||
format,
|
||||
heightMode,
|
||||
hideGutter,
|
||||
@@ -144,10 +143,6 @@ export function Editor({
|
||||
? allEnvironmentVariables.filter(autocompleteVariables)
|
||||
: allEnvironmentVariables;
|
||||
}, [allEnvironmentVariables, autocompleteVariables]);
|
||||
// Track a local key for updates. If the default value is changed when the input is not in focus,
|
||||
// regenerate this to force the field to update.
|
||||
const [focusedUpdateKey, regenerateFocusedUpdateKey] = useRandomKey('initial');
|
||||
const forceUpdateKey = `${forceUpdateKeyFromAbove}::${focusedUpdateKey}`;
|
||||
|
||||
if (settings && wrapLines === undefined) {
|
||||
wrapLines = settings.editorSoftWrap;
|
||||
@@ -223,7 +218,7 @@ export function Editor({
|
||||
const effects = placeholderCompartment.current.reconfigure(ext);
|
||||
cm.current?.view.dispatch({ effects });
|
||||
},
|
||||
[placeholder, type],
|
||||
[placeholder],
|
||||
);
|
||||
|
||||
// Update vim
|
||||
@@ -233,12 +228,12 @@ export function Editor({
|
||||
if (cm.current === null) return;
|
||||
const current = keymapCompartment.current.get(cm.current.view.state) ?? [];
|
||||
// PERF: This is expensive with hundreds of editors on screen, so only do it when necessary
|
||||
if (settings.editorKeymap === 'default' && current === keymapExtensions['default']) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'vim' && current === keymapExtensions['vim']) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'vscode' && current === keymapExtensions['vscode']) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'emacs' && current === keymapExtensions['emacs']) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'default' && current === keymapExtensions.default) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'vim' && current === keymapExtensions.vim) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'vscode' && current === keymapExtensions.vscode) return; // Nothing to do
|
||||
if (settings.editorKeymap === 'emacs' && current === keymapExtensions.emacs) return; // Nothing to do
|
||||
|
||||
const ext = keymapExtensions[settings.editorKeymap] ?? keymapExtensions['default'];
|
||||
const ext = keymapExtensions[settings.editorKeymap] ?? keymapExtensions.default;
|
||||
const effects = keymapCompartment.current.reconfigure(ext);
|
||||
cm.current.view.dispatch({ effects });
|
||||
},
|
||||
@@ -324,6 +319,7 @@ export function Editor({
|
||||
);
|
||||
|
||||
// Update the language extension when the language changes
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: none
|
||||
useEffect(() => {
|
||||
if (cm.current === null) return;
|
||||
const { view, languageCompartment } = cm.current;
|
||||
@@ -355,6 +351,7 @@ export function Editor({
|
||||
]);
|
||||
|
||||
// Initialize the editor when ref mounts
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Only reinitialize when necessary
|
||||
const initEditorRef = useCallback(
|
||||
function initEditorRef(container: HTMLDivElement | null) {
|
||||
if (container === null) {
|
||||
@@ -384,7 +381,7 @@ export function Editor({
|
||||
!disableTabIndent ? keymap.of([indentWithTab]) : emptyExtension,
|
||||
),
|
||||
keymapCompartment.current.of(
|
||||
keymapExtensions[settings.editorKeymap] ?? keymapExtensions['default'],
|
||||
keymapExtensions[settings.editorKeymap] ?? keymapExtensions.default,
|
||||
),
|
||||
...getExtensions({
|
||||
container,
|
||||
@@ -434,7 +431,6 @@ export function Editor({
|
||||
console.log('Failed to initialize Codemirror', e);
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[forceUpdateKey],
|
||||
);
|
||||
|
||||
@@ -456,7 +452,7 @@ export function Editor({
|
||||
updateContents(cm.current.view, defaultValue || '');
|
||||
}
|
||||
},
|
||||
[defaultValue, readOnly, regenerateFocusedUpdateKey],
|
||||
[defaultValue],
|
||||
);
|
||||
|
||||
// Add bg classes to actions, so they appear over the text
|
||||
@@ -628,7 +624,7 @@ function saveCachedEditorState(stateKey: string | null, state: EditorState | nul
|
||||
// Save state in sessionStorage by removing doc and saving the hash of it instead.
|
||||
// This will be checked on restore and put back in if it matches.
|
||||
stateObj.docHash = md5(stateObj.doc);
|
||||
delete stateObj.doc;
|
||||
stateObj.doc = undefined;
|
||||
|
||||
try {
|
||||
sessionStorage.setItem(computeFullStateKey(stateKey), JSON.stringify(stateObj));
|
||||
@@ -670,7 +666,9 @@ function updateContents(view: EditorView, text: string) {
|
||||
|
||||
if (currentDoc === text) {
|
||||
return;
|
||||
} else if (text.startsWith(currentDoc)) {
|
||||
}
|
||||
|
||||
if (text.startsWith(currentDoc)) {
|
||||
// If we're just appending, append only the changes. This preserves
|
||||
// things like scroll position.
|
||||
view.dispatch({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
|
||||
import { autocompletion, startCompletion } from '@codemirror/autocomplete';
|
||||
import { LanguageSupport, LRLanguage, syntaxTree } from '@codemirror/language';
|
||||
import type { SyntaxNode } from '@lezer/common';
|
||||
import { parser } from './filter';
|
||||
|
||||
export interface FieldDef {
|
||||
@@ -40,9 +41,10 @@ function wordBefore(doc: string, pos: number): { from: number; to: number; text:
|
||||
|
||||
function inPhrase(ctx: CompletionContext): boolean {
|
||||
// Lezer node names from your grammar: Phrase is the quoted token
|
||||
let n = syntaxTree(ctx.state).resolveInner(ctx.pos, -1);
|
||||
for (; n; n = n.parent!) {
|
||||
let n: SyntaxNode | null = syntaxTree(ctx.state).resolveInner(ctx.pos, -1);
|
||||
while (n) {
|
||||
if (n.name === 'Phrase') return true;
|
||||
n = n.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
// biome-ignore-all lint: Disable for generated file
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser} from "@lezer/lr"
|
||||
import {highlight} from "./highlight"
|
||||
import { LRParser } from '@lezer/lr';
|
||||
import { highlight } from './highlight';
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "%QOVQPOOPeOPOOOVQPO'#CfOjQPO'#ChO!XQPO'#CgOOQO'#Cc'#CcOVQPO'#CaOOQO'#Ca'#CaO!oQPO'#C`O!|QPO'#C_OOQO'#C^'#C^QOQPOOPOOO'#Cp'#CpP#XOPO)C>jO#`QPO,59QO#eQPO,59ROOQO,58{,58{OVQPO'#CqOOQO'#Cq'#CqO#mQPO,58zOVQPO'#CrO#zQPO,58yPOOO-E6n-E6nOOQO1G.l1G.lOOQO'#Cm'#CmOOQO'#Ck'#CkOOQO1G.m1G.mOOQO,59],59]OOQO-E6o-E6oOOQO,59^,59^OOQO-E6p-E6p",
|
||||
stateData: "$]~OiPQ~OUUOXQO]RO`TO~Oi[O~OUaXXaX]aX^[X`aXbaXcaXgaXWaX~O^_O~OUUOXQO]RO`TObaO~OcSXgSXWSX~P!^OcdOgRXWRX~Oi[O~Qh]WgO~O]hO`iO~OcSagSaWSa~P!^OcdOgRaWRa~OUbc]c~",
|
||||
goto: "#hgPPhnryP!YPP!c!c!lPP!uP!xPP#U#[#bQZOR^QTYOQSXOQRmdUWOQdQ`USbWcRka_VOQUWacd_TOQUWacd_SOQUWacdRj_^TOQUWacdRi_Q]PRf]QcWRlcQeXRne",
|
||||
nodeNames: "⚠ Query Expr OrExpr AndExpr Unary Not Primary RParen LParen Group Field FieldName Word Colon FieldValue Phrase Term And Or",
|
||||
states:
|
||||
"%QOVQPOOPeOPOOOVQPO'#CfOjQPO'#ChO!XQPO'#CgOOQO'#Cc'#CcOVQPO'#CaOOQO'#Ca'#CaO!oQPO'#C`O!|QPO'#C_OOQO'#C^'#C^QOQPOOPOOO'#Cp'#CpP#XOPO)C>jO#`QPO,59QO#eQPO,59ROOQO,58{,58{OVQPO'#CqOOQO'#Cq'#CqO#mQPO,58zOVQPO'#CrO#zQPO,58yPOOO-E6n-E6nOOQO1G.l1G.lOOQO'#Cm'#CmOOQO'#Ck'#CkOOQO1G.m1G.mOOQO,59],59]OOQO-E6o-E6oOOQO,59^,59^OOQO-E6p-E6p",
|
||||
stateData:
|
||||
'$]~OiPQ~OUUOXQO]RO`TO~Oi[O~OUaXXaX]aX^[X`aXbaXcaXgaXWaX~O^_O~OUUOXQO]RO`TObaO~OcSXgSXWSX~P!^OcdOgRXWRX~Oi[O~Qh]WgO~O]hO`iO~OcSagSaWSa~P!^OcdOgRaWRa~OUbc]c~',
|
||||
goto: '#hgPPhnryP!YPP!c!c!lPP!uP!xPP#U#[#bQZOR^QTYOQSXOQRmdUWOQdQ`USbWcRka_VOQUWacd_TOQUWacd_SOQUWacdRj_^TOQUWacdRi_Q]PRf]QcWRlcQeXRne',
|
||||
nodeNames:
|
||||
'⚠ Query Expr OrExpr AndExpr Unary Not Primary RParen LParen Group Field FieldName Word Colon FieldValue Phrase Term And Or',
|
||||
maxTerm: 25,
|
||||
nodeProps: [
|
||||
["openedBy", 8,"LParen"],
|
||||
["closedBy", 9,"RParen"]
|
||||
['openedBy', 8, 'LParen'],
|
||||
['closedBy', 9, 'RParen'],
|
||||
],
|
||||
propSources: [highlight],
|
||||
skippedNodes: [0,20],
|
||||
skippedNodes: [0, 20],
|
||||
repeatNodeCount: 3,
|
||||
tokenData: ")f~RgX^!jpq!jrs#_xy${yz%Q}!O%V!Q![%[![!]%m!c!d%r!d!p%[!p!q'V!q!r(j!r!}%[#R#S%[#T#o%[#y#z!j$f$g!j#BY#BZ!j$IS$I_!j$I|$JO!j$JT$JU!j$KV$KW!j&FU&FV!j~!oYi~X^!jpq!j#y#z!j$f$g!j#BY#BZ!j$IS$I_!j$I|$JO!j$JT$JU!j$KV$KW!j&FU&FV!j~#bVOr#_rs#ws#O#_#O#P#|#P;'S#_;'S;=`$u<%lO#_~#|O`~~$PRO;'S#_;'S;=`$Y;=`O#_~$]WOr#_rs#ws#O#_#O#P#|#P;'S#_;'S;=`$u;=`<%l#_<%lO#_~$xP;=`<%l#_~%QOX~~%VOW~~%[OU~~%aS]~!Q![%[!c!}%[#R#S%[#T#o%[~%rO^~~%wU]~!Q![%[!c!p%[!p!q&Z!q!}%[#R#S%[#T#o%[~&`U]~!Q![%[!c!f%[!f!g&r!g!}%[#R#S%[#T#o%[~&ySb~]~!Q![%[!c!}%[#R#S%[#T#o%[~'[U]~!Q![%[!c!q%[!q!r'n!r!}%[#R#S%[#T#o%[~'sU]~!Q![%[!c!v%[!v!w(V!w!}%[#R#S%[#T#o%[~(^SU~]~!Q![%[!c!}%[#R#S%[#T#o%[~(oU]~!Q![%[!c!t%[!t!u)R!u!}%[#R#S%[#T#o%[~)YSc~]~!Q![%[!c!}%[#R#S%[#T#o%[",
|
||||
tokenData:
|
||||
")f~RgX^!jpq!jrs#_xy${yz%Q}!O%V!Q![%[![!]%m!c!d%r!d!p%[!p!q'V!q!r(j!r!}%[#R#S%[#T#o%[#y#z!j$f$g!j#BY#BZ!j$IS$I_!j$I|$JO!j$JT$JU!j$KV$KW!j&FU&FV!j~!oYi~X^!jpq!j#y#z!j$f$g!j#BY#BZ!j$IS$I_!j$I|$JO!j$JT$JU!j$KV$KW!j&FU&FV!j~#bVOr#_rs#ws#O#_#O#P#|#P;'S#_;'S;=`$u<%lO#_~#|O`~~$PRO;'S#_;'S;=`$Y;=`O#_~$]WOr#_rs#ws#O#_#O#P#|#P;'S#_;'S;=`$u;=`<%l#_<%lO#_~$xP;=`<%l#_~%QOX~~%VOW~~%[OU~~%aS]~!Q![%[!c!}%[#R#S%[#T#o%[~%rO^~~%wU]~!Q![%[!c!p%[!p!q&Z!q!}%[#R#S%[#T#o%[~&`U]~!Q![%[!c!f%[!f!g&r!g!}%[#R#S%[#T#o%[~&ySb~]~!Q![%[!c!}%[#R#S%[#T#o%[~'[U]~!Q![%[!c!q%[!q!r'n!r!}%[#R#S%[#T#o%[~'sU]~!Q![%[!c!v%[!v!w(V!w!}%[#R#S%[#T#o%[~(^SU~]~!Q![%[!c!}%[#R#S%[#T#o%[~(oU]~!Q![%[!c!t%[!t!u)R!u!}%[#R#S%[#T#o%[~)YSc~]~!Q![%[!c!}%[#R#S%[#T#o%[",
|
||||
tokenizers: [0],
|
||||
topRules: {"Query":[0,1]},
|
||||
tokenPrec: 145
|
||||
})
|
||||
|
||||
topRules: { Query: [0, 1] },
|
||||
tokenPrec: 145,
|
||||
});
|
||||
|
||||
@@ -255,9 +255,9 @@ type Technique = 'substring' | 'fuzzy' | 'strict';
|
||||
|
||||
function includes(hay: string | undefined, needle: string, technique: Technique): boolean {
|
||||
if (!hay || !needle) return false;
|
||||
else if (technique === 'strict') return hay === needle;
|
||||
else if (technique === 'fuzzy') return !!fuzzyMatch(hay, needle);
|
||||
else return hay.indexOf(needle) !== -1;
|
||||
if (technique === 'strict') return hay === needle;
|
||||
if (technique === 'fuzzy') return !!fuzzyMatch(hay, needle);
|
||||
return hay.indexOf(needle) !== -1;
|
||||
}
|
||||
|
||||
export function evaluate(ast: Ast | null, doc: Doc): boolean {
|
||||
|
||||
@@ -11,9 +11,10 @@ const REGEX =
|
||||
const tooltip = hoverTooltip(
|
||||
(view, pos, side) => {
|
||||
const { from, text } = view.state.doc.lineAt(pos);
|
||||
let match;
|
||||
let match: RegExpExecArray | null;
|
||||
let found: { start: number; end: number } | null = null;
|
||||
|
||||
// biome-ignore lint/suspicious/noAssignInExpressions: none
|
||||
while ((match = REGEX.exec(text))) {
|
||||
const start = from + match.index;
|
||||
const end = start + match[0].length;
|
||||
@@ -28,7 +29,7 @@ const tooltip = hoverTooltip(
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((found.start == pos && side < 0) || (found.end == pos && side > 0)) {
|
||||
if ((found.start === pos && side < 0) || (found.end === pos && side > 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -37,7 +38,7 @@ const tooltip = hoverTooltip(
|
||||
end: found.end,
|
||||
create() {
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
const link = text.substring(found!.start - from, found!.end - from);
|
||||
const link = text.substring(found?.start - from, found?.end - from);
|
||||
const dom = document.createElement('div');
|
||||
|
||||
const $open = document.createElement('a');
|
||||
@@ -77,7 +78,7 @@ const tooltip = hoverTooltip(
|
||||
},
|
||||
);
|
||||
|
||||
const decorator = function () {
|
||||
const decorator = () => {
|
||||
const placeholderMatcher = new MatchDecorator({
|
||||
regexp: REGEX,
|
||||
decoration(match, view, matchStartPos) {
|
||||
|
||||
@@ -12,7 +12,7 @@ export function jsonParseLinter() {
|
||||
// syntax with repeating `1` characters, so it's valid JSON and the position is still correct.
|
||||
const escapedDoc = doc.replace(TEMPLATE_SYNTAX_REGEX, '1');
|
||||
jsonLintParse(escapedDoc);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
} catch (err: any) {
|
||||
if (!('location' in err)) {
|
||||
return [];
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const
|
||||
pairs = 1,
|
||||
export const pairs = 1,
|
||||
Key = 2,
|
||||
Sep = 3,
|
||||
Value = 4
|
||||
Value = 4;
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser} from "@lezer/lr"
|
||||
import {highlight} from "./highlight"
|
||||
import { LRParser } from '@lezer/lr';
|
||||
import { highlight } from './highlight';
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "zQQOPOOOVOQO'#CaQQOPOOO[OSO,58{OOOO-E6_-E6_OaOQO1G.gOOOO7+$R7+$R",
|
||||
stateData: "f~OQPO~ORRO~OSTO~OVUO~O",
|
||||
goto: "]UPPPPPVQQORSQ",
|
||||
nodeNames: "⚠ pairs Key Sep Value",
|
||||
stateData: 'f~OQPO~ORRO~OSTO~OVUO~O',
|
||||
goto: ']UPPPPPVQQORSQ',
|
||||
nodeNames: '⚠ pairs Key Sep Value',
|
||||
maxTerm: 7,
|
||||
propSources: [highlight],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 1,
|
||||
tokenData: "$]VRVOYhYZ#[Z![h![!]#o!];'Sh;'S;=`#U<%lOhToVQPSSOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOhP!ZSQPO![!U!];'S!U;'S;=`!g<%lO!UP!jP;=`<%l!US!rSSSOY!mZ;'S!m;'S;=`#O<%lO!mS#RP;=`<%l!mT#XP;=`<%lhR#cSVQQPO![!U!];'S!U;'S;=`!g<%lO!UV#vVRQSSOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOh",
|
||||
tokenData:
|
||||
"$]VRVOYhYZ#[Z![h![!]#o!];'Sh;'S;=`#U<%lOhToVQPSSOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOhP!ZSQPO![!U!];'S!U;'S;=`!g<%lO!UP!jP;=`<%l!US!rSSSOY!mZ;'S!m;'S;=`#O<%lO!mS#RP;=`<%l!mT#XP;=`<%lhR#cSVQQPO![!U!];'S!U;'S;=`!g<%lO!UV#vVRQSSOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOh",
|
||||
tokenizers: [0, 1, 2],
|
||||
topRules: {"pairs":[0,1]},
|
||||
topRules: { pairs: [0, 1] },
|
||||
tokenPrec: 0,
|
||||
termNames: {"0":"⚠","1":"@top","2":"Key","3":"Sep","4":"Value","5":"(Key Sep Value \"\\n\")+","6":"␄","7":"\"\\n\""}
|
||||
})
|
||||
termNames: {
|
||||
'0': '⚠',
|
||||
'1': '@top',
|
||||
'2': 'Key',
|
||||
'3': 'Sep',
|
||||
'4': 'Value',
|
||||
'5': '(Key Sep Value "\\n")+',
|
||||
'6': '␄',
|
||||
'7': '"\\n"',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const
|
||||
Template = 1,
|
||||
Text = 2
|
||||
export const Template = 1,
|
||||
Text = 2;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser} from "@lezer/lr"
|
||||
import { LRParser } from '@lezer/lr';
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "[OQOPOOQOOOOO",
|
||||
stateData: "V~OQPO~O",
|
||||
goto: "QPP",
|
||||
nodeNames: "⚠ Template Text",
|
||||
states: '[OQOPOOQOOOOO',
|
||||
stateData: 'V~OQPO~O',
|
||||
goto: 'QPP',
|
||||
nodeNames: '⚠ Template Text',
|
||||
maxTerm: 3,
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 0,
|
||||
tokenData: "p~RRO;'S[;'S;=`j<%lO[~aRQ~O;'S[;'S;=`j<%lO[~mP;=`<%l[",
|
||||
tokenizers: [0],
|
||||
topRules: {"Template":[0,1]},
|
||||
tokenPrec: 0
|
||||
})
|
||||
topRules: { Template: [0, 1] },
|
||||
tokenPrec: 0,
|
||||
});
|
||||
|
||||
@@ -44,14 +44,13 @@ export function twigCompletion({ options }: TwigCompletionConfig) {
|
||||
if (toMatch === null) return null;
|
||||
|
||||
const matchLen = toMatch.to - toMatch.from;
|
||||
|
||||
if (toMatch.from >0 && matchLen < MIN_MATCH_NAME) {
|
||||
if (toMatch.from > 0 && matchLen < MIN_MATCH_NAME) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const completions: Completion[] = options
|
||||
.flatMap((o): Completion[] => {
|
||||
const matchSegments = toMatch!.text.replace(/^\$/, '').split('.');
|
||||
const matchSegments = toMatch.text.replace(/^\$/, '').split('.');
|
||||
const optionSegments = o.name.split('.');
|
||||
|
||||
// If not on the last segment, only complete the namespace
|
||||
@@ -59,7 +58,7 @@ export function twigCompletion({ options }: TwigCompletionConfig) {
|
||||
const prefix = optionSegments.slice(0, matchSegments.length).join('.');
|
||||
return [
|
||||
{
|
||||
label: prefix + '.*',
|
||||
label: `${prefix}.*`,
|
||||
type: 'namespace',
|
||||
detail: 'namespace',
|
||||
apply: (view, _completion, from, to) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import type { Range } from '@codemirror/state';
|
||||
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
|
||||
import { Decoration, ViewPlugin, WidgetType, EditorView } from '@codemirror/view';
|
||||
import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
|
||||
|
||||
class PathPlaceholderWidget extends WidgetType {
|
||||
readonly #clickListenerCallback: () => void;
|
||||
@@ -23,7 +23,7 @@ class PathPlaceholderWidget extends WidgetType {
|
||||
|
||||
toDOM() {
|
||||
const elt = document.createElement('span');
|
||||
elt.className = `x-theme-templateTag x-theme-templateTag--secondary template-tag`;
|
||||
elt.className = 'x-theme-templateTag x-theme-templateTag--secondary template-tag';
|
||||
elt.textContent = this.rawText;
|
||||
elt.addEventListener('click', this.#clickListenerCallback);
|
||||
return elt;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import type { Range } from '@codemirror/state';
|
||||
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
|
||||
import { Decoration, ViewPlugin, WidgetType, EditorView } from '@codemirror/view';
|
||||
import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
|
||||
import type { SyntaxNodeRef } from '@lezer/common';
|
||||
import { parseTemplate } from '@yaakapp-internal/templates';
|
||||
import type { TwigCompletionOption } from './completion';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const
|
||||
Template = 1,
|
||||
export const Template = 1,
|
||||
Tag = 2,
|
||||
TagOpen = 3,
|
||||
TagContent = 4,
|
||||
TagClose = 5,
|
||||
Text = 6
|
||||
Text = 6;
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser, LocalTokenGroup} from "@lezer/lr"
|
||||
import {highlight} from "./highlight"
|
||||
import { LocalTokenGroup, LRParser } from '@lezer/lr';
|
||||
import { highlight } from './highlight';
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "!^QQOPOOOOOO'#C_'#C_OYOQO'#C^OOOO'#Cc'#CcQQOPOOOOOO'#Cd'#CdO_OQO,58xOOOO-E6a-E6aOOOO-E6b-E6bOOOO1G.d1G.d",
|
||||
stateData: "g~OUROYPO~OSTO~OSTOTXO~O",
|
||||
goto: "nXPPY^PPPbhTROSTQOSQSORVSQUQRWU",
|
||||
nodeNames: "⚠ Template Tag TagOpen TagContent TagClose Text",
|
||||
states:
|
||||
"!^QQOPOOOOOO'#C_'#C_OYOQO'#C^OOOO'#Cc'#CcQQOPOOOOOO'#Cd'#CdO_OQO,58xOOOO-E6a-E6aOOOO-E6b-E6bOOOO1G.d1G.d",
|
||||
stateData: 'g~OUROYPO~OSTO~OSTOTXO~O',
|
||||
goto: 'nXPPY^PPPbhTROSTQOSQSORVSQUQRWU',
|
||||
nodeNames: '⚠ Template Tag TagOpen TagContent TagClose Text',
|
||||
maxTerm: 10,
|
||||
propSources: [highlight],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 2,
|
||||
tokenData: "#]~RTOtbtu!hu;'Sb;'S;=`!]<%lOb~gTU~Otbtuvu;'Sb;'S;=`!]<%lOb~yUO#ob#p;'Sb;'S;=`!]<%l~b~Ob~~!c~!`P;=`<%lb~!hOU~~!kVO#ob#o#p#Q#p;'Sb;'S;=`!]<%l~b~Ob~~!c~#TP!}#O#W~#]OY~",
|
||||
tokenizers: [1, new LocalTokenGroup("b~RP#P#QU~XP#q#r[~aOT~~", 17, 4)],
|
||||
topRules: {"Template":[0,1]},
|
||||
tokenPrec: 0
|
||||
})
|
||||
tokenData:
|
||||
"#]~RTOtbtu!hu;'Sb;'S;=`!]<%lOb~gTU~Otbtuvu;'Sb;'S;=`!]<%lOb~yUO#ob#p;'Sb;'S;=`!]<%l~b~Ob~~!c~!`P;=`<%lb~!hOU~~!kVO#ob#o#p#Q#p;'Sb;'S;=`!]<%l~b~Ob~~!c~#TP!}#O#W~#]OY~",
|
||||
tokenizers: [1, new LocalTokenGroup('b~RP#P#QU~XP#q#r[~aOT~~', 17, 4)],
|
||||
topRules: { Template: [0, 1] },
|
||||
tokenPrec: 0,
|
||||
});
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const
|
||||
url = 1,
|
||||
export const url = 1,
|
||||
Protocol = 2,
|
||||
Host = 3,
|
||||
Port = 4,
|
||||
Path = 5,
|
||||
Placeholder = 6,
|
||||
PathSegment = 7,
|
||||
Query = 8
|
||||
Query = 8;
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser} from "@lezer/lr"
|
||||
import {highlight} from "./highlight"
|
||||
import { LRParser } from '@lezer/lr';
|
||||
import { highlight } from './highlight';
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "!|OQOPOOQYOPOOOTOPOOObOQO'#CdOjOPO'#C`OuOSO'#CcQOOOOOQ]OPOOOOOO,59O,59OOOOO-E6b-E6bOzOPO,58}O!SOSO'#CeO!XOPO1G.iOOOO,59P,59POOOO-E6c-E6c",
|
||||
stateData: "!g~OQQORPO~OZRO[TO~OTWOUWO~OZROYSX[SX~O]YO~O^ZOYVa~O]]O~O^ZOYVi~OQRTUT~",
|
||||
goto: "nYPPPPZPP^bhRVPTUPVQSPRXSQ[YR^[",
|
||||
nodeNames: "⚠ url Protocol Host Path Placeholder PathSegment Query",
|
||||
states:
|
||||
"!|OQOPOOQYOPOOOTOPOOObOQO'#CdOjOPO'#C`OuOSO'#CcQOOOOOQ]OPOOOOOO,59O,59OOOOO-E6b-E6bOzOPO,58}O!SOSO'#CeO!XOPO1G.iOOOO,59P,59POOOO-E6c-E6c",
|
||||
stateData: '!g~OQQORPO~OZRO[TO~OTWOUWO~OZROYSX[SX~O]YO~O^ZOYVa~O]]O~O^ZOYVi~OQRTUT~',
|
||||
goto: 'nYPPPPZPP^bhRVPTUPVQSPRXSQ[YR^[',
|
||||
nodeNames: '⚠ url Protocol Host Path Placeholder PathSegment Query',
|
||||
maxTerm: 14,
|
||||
propSources: [highlight],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 2,
|
||||
tokenData: ".i~RgOs!jtv!jvw#Xw}!j}!O#r!O!P#r!P!Q%U!Q![%Z![!]'o!]!a!j!a!b+W!b!c!j!c!}+]!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o+]#o;'S!j;'S;=`#R<%lO!jQ!oUUQOs!jt!P!j!Q!a!j!b;'S!j;'S;=`#R<%lO!jQ#UP;=`<%l!jR#`U^PUQOs!jt!P!j!Q!a!j!b;'S!j;'S;=`#R<%lO!jR#ycRPUQOs!jt}!j}!O#r!O!P#r!Q![#r![!]#r!]!a!j!b!c!j!c!}#r!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o#r#o;'S!j;'S;=`#R<%lO!j~%ZOZ~V%de]SRPUQOs!jt}!j}!O#r!O!P#r!Q![%Z![!]#r!]!_!j!_!`&u!`!a!j!b!c!j!c!}%Z!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o%Z#o;'S!j;'S;=`#R<%lO!jU&|Z]SUQOs!jt!P!j!Q![&u![!a!j!b!c!j!c!}&u!}#T!j#T#o&u#o;'S!j;'S;=`#R<%lO!jR'vcRPUQOs)Rt})R}!O)r!O!P)r!Q![)r![!])r!]!a)R!b!c)R!c!})r!}#O)r#O#P)R#P#Q)r#Q#R)R#R#S)r#S#T)R#T#o)r#o;'S)R;'S;=`)l<%lO)RQ)YUTQUQOs)Rt!P)R!Q!a)R!b;'S)R;'S;=`)l<%lO)RQ)oP;=`<%l)RR){cRPTQUQOs)Rt})R}!O)r!O!P)r!Q![)r![!])r!]!a)R!b!c)R!c!})r!}#O)r#O#P)R#P#Q)r#Q#R)R#R#S)r#S#T)R#T#o)r#o;'S)R;'S;=`)l<%lO)R~+]O[~V+fe]SRPUQOs!jt}!j}!O#r!O!P#r!Q![%Z![!],w!]!_!j!_!`&u!`!a!j!b!c!j!c!}+]!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o+]#o;'S!j;'S;=`#R<%lO!jR-OdRPUQOs!jt}!j}!O#r!O!P#r!P!Q.^!Q![#r![!]#r!]!a!j!b!c!j!c!}#r!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o#r#o;'S!j;'S;=`#R<%lO!jP.aP!P!Q.dP.iOQP",
|
||||
tokenData:
|
||||
".i~RgOs!jtv!jvw#Xw}!j}!O#r!O!P#r!P!Q%U!Q![%Z![!]'o!]!a!j!a!b+W!b!c!j!c!}+]!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o+]#o;'S!j;'S;=`#R<%lO!jQ!oUUQOs!jt!P!j!Q!a!j!b;'S!j;'S;=`#R<%lO!jQ#UP;=`<%l!jR#`U^PUQOs!jt!P!j!Q!a!j!b;'S!j;'S;=`#R<%lO!jR#ycRPUQOs!jt}!j}!O#r!O!P#r!Q![#r![!]#r!]!a!j!b!c!j!c!}#r!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o#r#o;'S!j;'S;=`#R<%lO!j~%ZOZ~V%de]SRPUQOs!jt}!j}!O#r!O!P#r!Q![%Z![!]#r!]!_!j!_!`&u!`!a!j!b!c!j!c!}%Z!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o%Z#o;'S!j;'S;=`#R<%lO!jU&|Z]SUQOs!jt!P!j!Q![&u![!a!j!b!c!j!c!}&u!}#T!j#T#o&u#o;'S!j;'S;=`#R<%lO!jR'vcRPUQOs)Rt})R}!O)r!O!P)r!Q![)r![!])r!]!a)R!b!c)R!c!})r!}#O)r#O#P)R#P#Q)r#Q#R)R#R#S)r#S#T)R#T#o)r#o;'S)R;'S;=`)l<%lO)RQ)YUTQUQOs)Rt!P)R!Q!a)R!b;'S)R;'S;=`)l<%lO)RQ)oP;=`<%l)RR){cRPTQUQOs)Rt})R}!O)r!O!P)r!Q![)r![!])r!]!a)R!b!c)R!c!})r!}#O)r#O#P)R#P#Q)r#Q#R)R#R#S)r#S#T)R#T#o)r#o;'S)R;'S;=`)l<%lO)R~+]O[~V+fe]SRPUQOs!jt}!j}!O#r!O!P#r!Q![%Z![!],w!]!_!j!_!`&u!`!a!j!b!c!j!c!}+]!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o+]#o;'S!j;'S;=`#R<%lO!jR-OdRPUQOs!jt}!j}!O#r!O!P#r!P!Q.^!Q![#r![!]#r!]!a!j!b!c!j!c!}#r!}#O#r#O#P!j#P#Q#r#Q#R!j#R#S#r#S#T!j#T#o#r#o;'S!j;'S;=`#R<%lO!jP.aP!P!Q.dP.iOQP",
|
||||
tokenizers: [0, 1, 2],
|
||||
topRules: {"url":[0,1]},
|
||||
tokenPrec: 63
|
||||
})
|
||||
|
||||
topRules: { url: [0, 1] },
|
||||
tokenPrec: 63,
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ export function HotKey({ action, className, variant }: Props) {
|
||||
)}
|
||||
>
|
||||
{labelParts.map((char, index) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: none
|
||||
<div key={index} className="min-w-[1.1em] text-center">
|
||||
{char}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { Fragment } from 'react';
|
||||
import type React from 'react';
|
||||
import { Fragment } from 'react';
|
||||
import type { HotkeyAction } from '../../hooks/useHotKey';
|
||||
import { HotKey } from './HotKey';
|
||||
import { HotKeyLabel } from './HotKeyLabel';
|
||||
|
||||
@@ -14,10 +14,10 @@ export function HttpResponseDurationTag({ response }: Props) {
|
||||
clearInterval(timeout.current);
|
||||
if (response.state === 'closed') return;
|
||||
timeout.current = setInterval(() => {
|
||||
setFallbackElapsed(Date.now() - new Date(response.createdAt + 'Z').getTime());
|
||||
setFallbackElapsed(Date.now() - new Date(`${response.createdAt}Z`).getTime());
|
||||
}, 100);
|
||||
return () => clearInterval(timeout.current);
|
||||
}, [response.createdAt, response.elapsed, response.state]);
|
||||
}, [response.createdAt, response.state]);
|
||||
|
||||
const title = `HEADER: ${formatMillis(response.elapsedHeaders)}\nTOTAL: ${formatMillis(response.elapsed)}`;
|
||||
|
||||
@@ -33,12 +33,12 @@ export function HttpResponseDurationTag({ response }: Props) {
|
||||
function formatMillis(ms: number) {
|
||||
if (ms < 1000) {
|
||||
return `${ms} ms`;
|
||||
} else if (ms < 60_000) {
|
||||
}
|
||||
if (ms < 60_000) {
|
||||
const seconds = (ms / 1000).toFixed(ms < 10_000 ? 1 : 0);
|
||||
return `${seconds} s`;
|
||||
} else {
|
||||
const minutes = Math.floor(ms / 60_000);
|
||||
const seconds = Math.round((ms % 60_000) / 1000);
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
const minutes = Math.floor(ms / 60_000);
|
||||
const seconds = Math.round((ms % 60_000) / 1000);
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ interface Props {
|
||||
export function HttpStatusTag({ response, className, showReason, short }: Props) {
|
||||
const { status, state } = response;
|
||||
|
||||
let colorClass;
|
||||
let colorClass: string;
|
||||
let label = `${status}`;
|
||||
|
||||
if (state === 'initialized') {
|
||||
|
||||
@@ -33,6 +33,7 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(functio
|
||||
showBadge,
|
||||
iconColor,
|
||||
isLoading,
|
||||
type = 'button',
|
||||
...props
|
||||
}: IconButtonProps,
|
||||
ref,
|
||||
@@ -56,6 +57,7 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(functio
|
||||
innerClassName="flex items-center justify-center"
|
||||
size={size}
|
||||
color={color}
|
||||
type={type}
|
||||
className={classNames(
|
||||
className,
|
||||
'group/button relative flex-shrink-0',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import type { IconProps } from './Icon';
|
||||
import { Icon } from './Icon';
|
||||
import type { TooltipProps } from './Tooltip';
|
||||
|
||||
@@ -88,9 +88,8 @@ export function Input({ type, ...props }: InputProps) {
|
||||
// use the encrypted input component.
|
||||
if (type === 'password' && props.autocompleteFunctions) {
|
||||
return <EncryptionInput {...props} />;
|
||||
} else {
|
||||
return <BaseInput type={type} {...props} />;
|
||||
}
|
||||
return <BaseInput type={type} {...props} />;
|
||||
}
|
||||
|
||||
function BaseInput({
|
||||
@@ -145,7 +144,7 @@ function BaseInput({
|
||||
isFocused: () => editorRef.current?.hasFocus ?? false,
|
||||
value: () => editorRef.current?.state.doc.toString() ?? '',
|
||||
dispatch: (...args) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
editorRef.current?.dispatch(...(args as any));
|
||||
},
|
||||
selectAll() {
|
||||
@@ -168,7 +167,9 @@ function BaseInput({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fn = () => (skipNextFocus.current = true);
|
||||
const fn = () => {
|
||||
skipNextFocus.current = true;
|
||||
};
|
||||
window.addEventListener('focus', fn);
|
||||
return () => {
|
||||
window.removeEventListener('focus', fn);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Icon } from './Icon';
|
||||
|
||||
interface Props {
|
||||
depth?: number;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// biome-ignore lint/suspicious/noExplicitAny: none
|
||||
attrValue: any;
|
||||
attrKey?: string | number;
|
||||
attrKeyJsonPath?: string;
|
||||
@@ -47,15 +47,17 @@ export const JsonAttributeTree = ({
|
||||
))
|
||||
: null,
|
||||
isExpandable: Object.keys(attrValue).length > 0,
|
||||
label: isExpanded ? `{${Object.keys(attrValue).length || ' '}}` : `{⋯}`,
|
||||
label: isExpanded ? `{${Object.keys(attrValue).length || ' '}}` : '{⋯}',
|
||||
labelClassName: 'text-text-subtlest',
|
||||
};
|
||||
} else if (jsonType === '[object Array]') {
|
||||
}
|
||||
if (jsonType === '[object Array]') {
|
||||
return {
|
||||
children: isExpanded
|
||||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
? // biome-ignore lint/suspicious/noExplicitAny: none
|
||||
attrValue.flatMap((v: any, i: number) => (
|
||||
<JsonAttributeTree
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: none
|
||||
key={i}
|
||||
depth={depth + 1}
|
||||
attrValue={v}
|
||||
@@ -65,26 +67,27 @@ export const JsonAttributeTree = ({
|
||||
))
|
||||
: null,
|
||||
isExpandable: attrValue.length > 0,
|
||||
label: isExpanded ? `[${attrValue.length || ' '}]` : `[⋯]`,
|
||||
label: isExpanded ? `[${attrValue.length || ' '}]` : '[⋯]',
|
||||
labelClassName: 'text-text-subtlest',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
children: null,
|
||||
isExpandable: false,
|
||||
label: jsonType === '[object String]' ? `"${attrValue}"` : `${attrValue}`,
|
||||
labelClassName: classNames(
|
||||
jsonType === '[object Boolean]' && 'text-primary',
|
||||
jsonType === '[object Number]' && 'text-info',
|
||||
jsonType === '[object String]' && 'text-notice',
|
||||
jsonType === '[object Null]' && 'text-danger',
|
||||
),
|
||||
};
|
||||
}
|
||||
return {
|
||||
children: null,
|
||||
isExpandable: false,
|
||||
label: jsonType === '[object String]' ? `"${attrValue}"` : `${attrValue}`,
|
||||
labelClassName: classNames(
|
||||
jsonType === '[object Boolean]' && 'text-primary',
|
||||
jsonType === '[object Number]' && 'text-info',
|
||||
jsonType === '[object String]' && 'text-notice',
|
||||
jsonType === '[object Null]' && 'text-danger',
|
||||
),
|
||||
};
|
||||
}, [attrValue, attrKeyJsonPath, isExpanded, depth]);
|
||||
|
||||
const labelEl = (
|
||||
<span className={classNames(labelClassName, 'cursor-text select-text group-hover:text-text-subtle')}>
|
||||
<span
|
||||
className={classNames(labelClassName, 'cursor-text select-text group-hover:text-text-subtle')}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
@@ -98,7 +101,11 @@ export const JsonAttributeTree = ({
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{isExpandable ? (
|
||||
<button className="group relative flex items-center pl-4 w-full" onClick={toggleExpanded}>
|
||||
<button
|
||||
type="button"
|
||||
className="group relative flex items-center pl-4 w-full"
|
||||
onClick={toggleExpanded}
|
||||
>
|
||||
<Icon
|
||||
size="xs"
|
||||
icon="chevron_right"
|
||||
@@ -131,7 +138,7 @@ function joinObjectKey(baseKey: string | undefined, key: string): string {
|
||||
const quotedKey = key.match(/^[a-z0-9_]+$/i) ? key : `\`${key}\``;
|
||||
|
||||
if (baseKey == null) return quotedKey;
|
||||
else return `${baseKey}.${quotedKey}`;
|
||||
return `${baseKey}.${quotedKey}`;
|
||||
}
|
||||
|
||||
function joinArrayKey(baseKey: string | undefined, index: number): string {
|
||||
|
||||
@@ -13,6 +13,7 @@ export function KeyValueRows({ children }: Props) {
|
||||
<table className="text-xs font-mono min-w-0 w-full mb-auto">
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
{children.map((child, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: none
|
||||
<tr key={i}>{child}</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -37,6 +37,7 @@ export function Label({
|
||||
{required === true && <span className="text-text-subtlest">*</span>}
|
||||
</span>
|
||||
{tags.map((tag, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: none
|
||||
<span key={i} className="text-xs text-text-subtlest">
|
||||
({tag})
|
||||
</span>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user