mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-19 07:54:23 +01:00
Fix graphql and other things
This commit is contained in:
@@ -75,7 +75,6 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
) : activeRequest.bodyType === 'graphql' ? (
|
||||
<GraphQLEditor
|
||||
key={activeRequest.id}
|
||||
useTemplating
|
||||
className="!bg-gray-50"
|
||||
defaultValue={activeRequest?.body ?? ''}
|
||||
onChange={(body) => updateRequest.mutate({ body })}
|
||||
|
||||
@@ -93,8 +93,6 @@ export function _Editor({
|
||||
onFocus: handleFocus,
|
||||
readOnly,
|
||||
singleLine,
|
||||
contentType,
|
||||
useTemplating,
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -132,15 +130,11 @@ function getExtensions({
|
||||
singleLine,
|
||||
onChange,
|
||||
onFocus,
|
||||
contentType,
|
||||
useTemplating,
|
||||
}: Pick<_EditorProps, 'singleLine' | 'contentType' | 'useTemplating' | 'readOnly'> & {
|
||||
}: Pick<_EditorProps, 'singleLine' | 'readOnly'> & {
|
||||
container: HTMLDivElement | null;
|
||||
onChange: MutableRefObject<_EditorProps['onChange']>;
|
||||
onFocus: MutableRefObject<_EditorProps['onFocus']>;
|
||||
}) {
|
||||
const ext = getLanguageExtension({ contentType, useTemplating });
|
||||
|
||||
// TODO: Ensure tooltips render inside the dialog if we are in one.
|
||||
const parent =
|
||||
container?.closest<HTMLDivElement>('[role="dialog"]') ??
|
||||
@@ -153,7 +147,6 @@ function getExtensions({
|
||||
keymap.of(singleLine ? defaultKeymap.filter((k) => k.key !== 'Enter') : defaultKeymap),
|
||||
...(singleLine ? [singleLineExt()] : []),
|
||||
...(!singleLine ? [multiLineExtensions] : []),
|
||||
...(ext ? [ext] : []),
|
||||
...(readOnly ? [EditorState.readOnly.of(true)] : []),
|
||||
...(singleLine
|
||||
? [
|
||||
|
||||
@@ -42,13 +42,17 @@ export const myHighlightStyle = HighlightStyle.define([
|
||||
color: '#757b93',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
{
|
||||
tag: [t.paren],
|
||||
color: 'hsl(var(--color-gray-900))',
|
||||
},
|
||||
{
|
||||
tag: [t.name, t.tagName, t.angleBracket, t.docString, t.number],
|
||||
color: 'hsl(var(--color-blue-600))',
|
||||
},
|
||||
{ tag: [t.variableName], color: 'hsl(var(--color-green-600))' },
|
||||
{ tag: [t.bool], color: 'hsl(var(--color-pink-600))' },
|
||||
{ tag: [t.attributeName], color: 'hsl(var(--color-violet-600))' },
|
||||
{ tag: [t.attributeName, t.propertyName], color: 'hsl(var(--color-violet-600))' },
|
||||
{ tag: [t.attributeValue], color: 'hsl(var(--color-orange-600))' },
|
||||
{ tag: [t.string], color: 'hsl(var(--color-yellow-600))' },
|
||||
{ tag: [t.keyword, t.meta, t.operator], color: 'hsl(var(--color-red-600))' },
|
||||
@@ -88,7 +92,7 @@ const syntaxExtensions: Record<string, LanguageSupport> = {
|
||||
|
||||
export function getLanguageExtension({
|
||||
contentType,
|
||||
useTemplating,
|
||||
useTemplating = false,
|
||||
}: {
|
||||
contentType?: string;
|
||||
useTemplating?: boolean;
|
||||
|
||||
@@ -20,7 +20,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
||||
'text-gray-700 hover:text-gray-1000',
|
||||
'!px-0',
|
||||
size === 'md' && 'w-9',
|
||||
size === 'sm' && 'w-9',
|
||||
size === 'sm' && 'w-8',
|
||||
)}
|
||||
size={size}
|
||||
{...props}
|
||||
|
||||
@@ -105,7 +105,7 @@ function FormRow({
|
||||
onChange={(name) => onChange({ id, pair: { name, value: pairContainer.pair.value } })}
|
||||
onFocus={onFocus}
|
||||
placeholder={isLast ? 'new name' : 'name'}
|
||||
useEditor={{ useTemplating: true }}
|
||||
useEditor={{ useTemplating: true, contentType: 'text/plain' }}
|
||||
/>
|
||||
<Input
|
||||
hideLabel
|
||||
@@ -116,7 +116,7 @@ function FormRow({
|
||||
onChange={(value) => onChange({ id, pair: { name: pairContainer.pair.name, value } })}
|
||||
onFocus={onFocus}
|
||||
placeholder={isLast ? 'new value' : 'value'}
|
||||
useEditor={{ useTemplating: true }}
|
||||
useEditor={{ useTemplating: true, contentType: 'text/plain' }}
|
||||
/>
|
||||
{onDelete && (
|
||||
<IconButton
|
||||
|
||||
@@ -37,10 +37,7 @@ export function Tabs({ defaultValue, label, children, tabs, className, tabListCl
|
||||
>
|
||||
<T.List
|
||||
aria-label={label}
|
||||
className={classnames(
|
||||
tabListClassName,
|
||||
'h-auto flex items-center overflow-x-auto mb-1 pb-1',
|
||||
)}
|
||||
className={classnames(tabListClassName, 'h-auto flex items-center overflow-x-auto pb-1')}
|
||||
>
|
||||
<HStack space={1}>
|
||||
{tabs.map((t) => {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { formatSdl } from 'format-graphql';
|
||||
import { useMemo } from 'react';
|
||||
import { useUniqueKey } from '../../hooks/useUniqueKey';
|
||||
import { Divider } from '../core/Divider';
|
||||
import type { EditorProps } from '../core/Editor';
|
||||
import { Editor } from '../core/Editor';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
|
||||
type Props = Pick<
|
||||
EditorProps,
|
||||
'heightMode' | 'onChange' | 'defaultValue' | 'className' | 'useTemplating'
|
||||
>;
|
||||
type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'defaultValue' | 'className'>;
|
||||
|
||||
interface GraphQLBody {
|
||||
query: string;
|
||||
@@ -15,6 +15,7 @@ interface GraphQLBody {
|
||||
}
|
||||
|
||||
export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: Props) {
|
||||
const queryKey = useUniqueKey();
|
||||
const { query, variables } = useMemo<GraphQLBody>(() => {
|
||||
try {
|
||||
const p = JSON.parse(defaultValue ?? '{}');
|
||||
@@ -36,21 +37,39 @@ export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: P
|
||||
};
|
||||
|
||||
const handleChangeVariables = (variables: string) => {
|
||||
handleChange({ query, variables: JSON.parse(variables) });
|
||||
try {
|
||||
handleChange({ query, variables: JSON.parse(variables) });
|
||||
} catch (e) {
|
||||
// Meh, not much we can do here
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="pb-1 h-full grid grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
|
||||
<Editor
|
||||
heightMode="auto"
|
||||
defaultValue={query ?? ''}
|
||||
onChange={handleChangeQuery}
|
||||
contentType="application/graphql"
|
||||
{...extraEditorProps}
|
||||
/>
|
||||
<div className="relative">
|
||||
<Editor
|
||||
key={queryKey.key}
|
||||
heightMode="auto"
|
||||
defaultValue={query ?? ''}
|
||||
onChange={handleChangeQuery}
|
||||
contentType="application/graphql"
|
||||
{...extraEditorProps}
|
||||
/>
|
||||
<IconButton
|
||||
size="sm"
|
||||
title="Re-format GraphQL Query"
|
||||
icon="eye"
|
||||
className="absolute bottom-2 right-0"
|
||||
onClick={() => {
|
||||
handleChangeQuery(formatSdl(query));
|
||||
setTimeout(queryKey.regenerate, 200);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Divider />
|
||||
<p className="pt-1 text-gray-500 text-sm">Variables</p>
|
||||
<Editor
|
||||
useTemplating
|
||||
heightMode="auto"
|
||||
defaultValue={JSON.stringify(variables, null, 2)}
|
||||
onChange={handleChangeVariables}
|
||||
|
||||
@@ -4,17 +4,13 @@ import type { HttpRequest } from '../lib/models';
|
||||
import { useRequests } from './useRequests';
|
||||
|
||||
export function useActiveRequest(): HttpRequest | null {
|
||||
const params = useParams<{ requestId?: string }>();
|
||||
const requests = useRequests();
|
||||
const { requestId } = useParams<{ requestId?: string }>();
|
||||
const [activeRequest, setActiveRequest] = useState<HttpRequest | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (requests.length === 0) {
|
||||
setActiveRequest(null);
|
||||
} else {
|
||||
setActiveRequest(requests.find((r) => r.id === params.requestId) ?? null);
|
||||
}
|
||||
}, [requests, params.requestId]);
|
||||
setActiveRequest(requests.find((r) => r.id === requestId) ?? null);
|
||||
}, [requests, requestId]);
|
||||
|
||||
return activeRequest;
|
||||
}
|
||||
|
||||
@@ -4,17 +4,13 @@ import type { Workspace } from '../lib/models';
|
||||
import { useWorkspaces } from './useWorkspaces';
|
||||
|
||||
export function useActiveWorkspace(): Workspace | null {
|
||||
const params = useParams<{ workspaceId?: string }>();
|
||||
const workspaces = useWorkspaces();
|
||||
const { workspaceId } = useParams<{ workspaceId?: string }>();
|
||||
const [activeWorkspace, setActiveWorkspace] = useState<Workspace | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaces.length === 0) {
|
||||
setActiveWorkspace(null);
|
||||
} else {
|
||||
setActiveWorkspace(workspaces.find((w) => w.id === params.workspaceId) ?? null);
|
||||
}
|
||||
}, [workspaces, params.workspaceId]);
|
||||
setActiveWorkspace(workspaces.find((w) => w.id === workspaceId) ?? null);
|
||||
}, [workspaces, workspaceId]);
|
||||
|
||||
return activeWorkspace;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function useCreateRequest({ navigateAfter }: { navigateAfter: boolean })
|
||||
if (workspace === null) {
|
||||
throw new Error("Cannot create request when there's no active workspace");
|
||||
}
|
||||
return invoke('create_request', { ...patch, workspaceId: workspace?.id });
|
||||
return invoke('create_request', { ...patch, workspaceId: workspace.id });
|
||||
},
|
||||
onSuccess: async (requestId) => {
|
||||
if (navigateAfter) {
|
||||
|
||||
@@ -7,11 +7,11 @@ export function useDeleteRequest(request: HttpRequest | null) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<void, string>({
|
||||
mutationFn: async () => {
|
||||
if (request == null) return;
|
||||
if (!request) return;
|
||||
await invoke('delete_request', { requestId: request.id });
|
||||
},
|
||||
onSuccess: async () => {
|
||||
if (request == null) return;
|
||||
if (!request) return;
|
||||
await queryClient.invalidateQueries(requestsQueryKey(request.workspaceId));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,11 +5,11 @@ export function useDeleteResponses(requestId?: string) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
if (requestId == null) return;
|
||||
if (!requestId) return;
|
||||
await invoke('delete_all_responses', { requestId });
|
||||
},
|
||||
onSuccess: () => {
|
||||
if (requestId == null) return;
|
||||
if (!requestId) return;
|
||||
queryClient.setQueryData(['responses', { requestId: requestId }], []);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { responsesQueryKey } from './useResponses';
|
||||
|
||||
export function useDeleteResponse(response: HttpResponse | null) {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -12,7 +13,7 @@ export function useDeleteResponse(response: HttpResponse | null) {
|
||||
onSuccess: () => {
|
||||
if (response === null) return;
|
||||
queryClient.setQueryData(
|
||||
['responses', { requestId: response.requestId }],
|
||||
responsesQueryKey(response.requestId),
|
||||
(responses: HttpResponse[] = []) => responses.filter((r) => r.id !== response.id),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -8,12 +8,14 @@ import {
|
||||
toggleAppearance,
|
||||
} from '../lib/theme/window';
|
||||
|
||||
const appearanceQueryKey = ['theme', 'appearance'];
|
||||
export function appearanceQueryKey() {
|
||||
return ['theme', 'appearance'];
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
const queryClient = useQueryClient();
|
||||
const appearance = useQuery({
|
||||
queryKey: appearanceQueryKey,
|
||||
queryKey: appearanceQueryKey(),
|
||||
queryFn: getAppearance,
|
||||
initialData: getAppearance(),
|
||||
}).data;
|
||||
@@ -24,12 +26,10 @@ export function useTheme() {
|
||||
|
||||
const handleToggleAppearance = async () => {
|
||||
const newAppearance = toggleAppearance();
|
||||
await queryClient.setQueryData(appearanceQueryKey, newAppearance);
|
||||
await queryClient.setQueryData(appearanceQueryKey(), newAppearance);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return subscribeToPreferredAppearanceChange(themeChange);
|
||||
}, []);
|
||||
useEffect(() => subscribeToPreferredAppearanceChange(themeChange), []);
|
||||
|
||||
return {
|
||||
appearance,
|
||||
|
||||
16
src-web/hooks/useUniqueKey.ts
Normal file
16
src-web/hooks/useUniqueKey.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
export function useUniqueKey(len = 10): { key: string; regenerate: () => void } {
|
||||
const [key, setKey] = useState<string>(() => generate(len));
|
||||
return { key, regenerate: () => setKey(generate(len)) };
|
||||
}
|
||||
|
||||
const CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
function generate(len: number): string {
|
||||
const chars = [];
|
||||
for (let i = 0; i < len; i++) {
|
||||
chars.push(CHARS[Math.floor(Math.random() * CHARS.length)]);
|
||||
}
|
||||
return chars.join('');
|
||||
}
|
||||
Reference in New Issue
Block a user