Fix graphql and other things

This commit is contained in:
Gregory Schier
2023-03-15 09:06:56 -07:00
parent 321941baab
commit a2982f8b77
15 changed files with 77 additions and 56 deletions

View File

@@ -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 })}

View File

@@ -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
? [

View File

@@ -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;

View File

@@ -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}

View File

@@ -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

View File

@@ -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) => {

View File

@@ -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}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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));
},
});

View File

@@ -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 }], []);
},
});

View File

@@ -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),
);
},

View File

@@ -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,

View 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('');
}