Fix graphql and other things

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

View File

@@ -75,7 +75,6 @@ export function RequestPane({ fullHeight, className }: Props) {
) : activeRequest.bodyType === 'graphql' ? ( ) : activeRequest.bodyType === 'graphql' ? (
<GraphQLEditor <GraphQLEditor
key={activeRequest.id} key={activeRequest.id}
useTemplating
className="!bg-gray-50" className="!bg-gray-50"
defaultValue={activeRequest?.body ?? ''} defaultValue={activeRequest?.body ?? ''}
onChange={(body) => updateRequest.mutate({ body })} onChange={(body) => updateRequest.mutate({ body })}

View File

@@ -93,8 +93,6 @@ export function _Editor({
onFocus: handleFocus, onFocus: handleFocus,
readOnly, readOnly,
singleLine, singleLine,
contentType,
useTemplating,
}), }),
], ],
}); });
@@ -132,15 +130,11 @@ function getExtensions({
singleLine, singleLine,
onChange, onChange,
onFocus, onFocus,
contentType, }: Pick<_EditorProps, 'singleLine' | 'readOnly'> & {
useTemplating,
}: Pick<_EditorProps, 'singleLine' | 'contentType' | 'useTemplating' | 'readOnly'> & {
container: HTMLDivElement | null; container: HTMLDivElement | null;
onChange: MutableRefObject<_EditorProps['onChange']>; onChange: MutableRefObject<_EditorProps['onChange']>;
onFocus: MutableRefObject<_EditorProps['onFocus']>; onFocus: MutableRefObject<_EditorProps['onFocus']>;
}) { }) {
const ext = getLanguageExtension({ contentType, useTemplating });
// TODO: Ensure tooltips render inside the dialog if we are in one. // TODO: Ensure tooltips render inside the dialog if we are in one.
const parent = const parent =
container?.closest<HTMLDivElement>('[role="dialog"]') ?? container?.closest<HTMLDivElement>('[role="dialog"]') ??
@@ -153,7 +147,6 @@ function getExtensions({
keymap.of(singleLine ? defaultKeymap.filter((k) => k.key !== 'Enter') : defaultKeymap), keymap.of(singleLine ? defaultKeymap.filter((k) => k.key !== 'Enter') : defaultKeymap),
...(singleLine ? [singleLineExt()] : []), ...(singleLine ? [singleLineExt()] : []),
...(!singleLine ? [multiLineExtensions] : []), ...(!singleLine ? [multiLineExtensions] : []),
...(ext ? [ext] : []),
...(readOnly ? [EditorState.readOnly.of(true)] : []), ...(readOnly ? [EditorState.readOnly.of(true)] : []),
...(singleLine ...(singleLine
? [ ? [

View File

@@ -42,13 +42,17 @@ export const myHighlightStyle = HighlightStyle.define([
color: '#757b93', color: '#757b93',
fontStyle: 'italic', fontStyle: 'italic',
}, },
{
tag: [t.paren],
color: 'hsl(var(--color-gray-900))',
},
{ {
tag: [t.name, t.tagName, t.angleBracket, t.docString, t.number], tag: [t.name, t.tagName, t.angleBracket, t.docString, t.number],
color: 'hsl(var(--color-blue-600))', color: 'hsl(var(--color-blue-600))',
}, },
{ tag: [t.variableName], color: 'hsl(var(--color-green-600))' }, { tag: [t.variableName], color: 'hsl(var(--color-green-600))' },
{ tag: [t.bool], color: 'hsl(var(--color-pink-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.attributeValue], color: 'hsl(var(--color-orange-600))' },
{ tag: [t.string], color: 'hsl(var(--color-yellow-600))' }, { tag: [t.string], color: 'hsl(var(--color-yellow-600))' },
{ tag: [t.keyword, t.meta, t.operator], color: 'hsl(var(--color-red-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({ export function getLanguageExtension({
contentType, contentType,
useTemplating, useTemplating = false,
}: { }: {
contentType?: string; contentType?: string;
useTemplating?: boolean; useTemplating?: boolean;

View File

@@ -20,7 +20,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
'text-gray-700 hover:text-gray-1000', 'text-gray-700 hover:text-gray-1000',
'!px-0', '!px-0',
size === 'md' && 'w-9', size === 'md' && 'w-9',
size === 'sm' && 'w-9', size === 'sm' && 'w-8',
)} )}
size={size} size={size}
{...props} {...props}

View File

@@ -105,7 +105,7 @@ function FormRow({
onChange={(name) => onChange({ id, pair: { name, value: pairContainer.pair.value } })} onChange={(name) => onChange({ id, pair: { name, value: pairContainer.pair.value } })}
onFocus={onFocus} onFocus={onFocus}
placeholder={isLast ? 'new name' : 'name'} placeholder={isLast ? 'new name' : 'name'}
useEditor={{ useTemplating: true }} useEditor={{ useTemplating: true, contentType: 'text/plain' }}
/> />
<Input <Input
hideLabel hideLabel
@@ -116,7 +116,7 @@ function FormRow({
onChange={(value) => onChange({ id, pair: { name: pairContainer.pair.name, value } })} onChange={(value) => onChange({ id, pair: { name: pairContainer.pair.name, value } })}
onFocus={onFocus} onFocus={onFocus}
placeholder={isLast ? 'new value' : 'value'} placeholder={isLast ? 'new value' : 'value'}
useEditor={{ useTemplating: true }} useEditor={{ useTemplating: true, contentType: 'text/plain' }}
/> />
{onDelete && ( {onDelete && (
<IconButton <IconButton

View File

@@ -37,10 +37,7 @@ export function Tabs({ defaultValue, label, children, tabs, className, tabListCl
> >
<T.List <T.List
aria-label={label} aria-label={label}
className={classnames( className={classnames(tabListClassName, 'h-auto flex items-center overflow-x-auto pb-1')}
tabListClassName,
'h-auto flex items-center overflow-x-auto mb-1 pb-1',
)}
> >
<HStack space={1}> <HStack space={1}>
{tabs.map((t) => { {tabs.map((t) => {

View File

@@ -1,12 +1,12 @@
import { formatSdl } from 'format-graphql';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useUniqueKey } from '../../hooks/useUniqueKey';
import { Divider } from '../core/Divider'; import { Divider } from '../core/Divider';
import type { EditorProps } from '../core/Editor'; import type { EditorProps } from '../core/Editor';
import { Editor } from '../core/Editor'; import { Editor } from '../core/Editor';
import { IconButton } from '../core/IconButton';
type Props = Pick< type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'defaultValue' | 'className'>;
EditorProps,
'heightMode' | 'onChange' | 'defaultValue' | 'className' | 'useTemplating'
>;
interface GraphQLBody { interface GraphQLBody {
query: string; query: string;
@@ -15,6 +15,7 @@ interface GraphQLBody {
} }
export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: Props) { export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: Props) {
const queryKey = useUniqueKey();
const { query, variables } = useMemo<GraphQLBody>(() => { const { query, variables } = useMemo<GraphQLBody>(() => {
try { try {
const p = JSON.parse(defaultValue ?? '{}'); const p = JSON.parse(defaultValue ?? '{}');
@@ -36,21 +37,39 @@ export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: P
}; };
const handleChangeVariables = (variables: string) => { 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 ( return (
<div className="pb-1 h-full grid grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]"> <div className="pb-1 h-full grid grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
<Editor <div className="relative">
heightMode="auto" <Editor
defaultValue={query ?? ''} key={queryKey.key}
onChange={handleChangeQuery} heightMode="auto"
contentType="application/graphql" defaultValue={query ?? ''}
{...extraEditorProps} 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 /> <Divider />
<p className="pt-1 text-gray-500 text-sm">Variables</p> <p className="pt-1 text-gray-500 text-sm">Variables</p>
<Editor <Editor
useTemplating
heightMode="auto" heightMode="auto"
defaultValue={JSON.stringify(variables, null, 2)} defaultValue={JSON.stringify(variables, null, 2)}
onChange={handleChangeVariables} onChange={handleChangeVariables}

View File

@@ -4,17 +4,13 @@ import type { HttpRequest } from '../lib/models';
import { useRequests } from './useRequests'; import { useRequests } from './useRequests';
export function useActiveRequest(): HttpRequest | null { export function useActiveRequest(): HttpRequest | null {
const params = useParams<{ requestId?: string }>();
const requests = useRequests(); const requests = useRequests();
const { requestId } = useParams<{ requestId?: string }>();
const [activeRequest, setActiveRequest] = useState<HttpRequest | null>(null); const [activeRequest, setActiveRequest] = useState<HttpRequest | null>(null);
useEffect(() => { useEffect(() => {
if (requests.length === 0) { setActiveRequest(requests.find((r) => r.id === requestId) ?? null);
setActiveRequest(null); }, [requests, requestId]);
} else {
setActiveRequest(requests.find((r) => r.id === params.requestId) ?? null);
}
}, [requests, params.requestId]);
return activeRequest; return activeRequest;
} }

View File

@@ -4,17 +4,13 @@ import type { Workspace } from '../lib/models';
import { useWorkspaces } from './useWorkspaces'; import { useWorkspaces } from './useWorkspaces';
export function useActiveWorkspace(): Workspace | null { export function useActiveWorkspace(): Workspace | null {
const params = useParams<{ workspaceId?: string }>();
const workspaces = useWorkspaces(); const workspaces = useWorkspaces();
const { workspaceId } = useParams<{ workspaceId?: string }>();
const [activeWorkspace, setActiveWorkspace] = useState<Workspace | null>(null); const [activeWorkspace, setActiveWorkspace] = useState<Workspace | null>(null);
useEffect(() => { useEffect(() => {
if (workspaces.length === 0) { setActiveWorkspace(workspaces.find((w) => w.id === workspaceId) ?? null);
setActiveWorkspace(null); }, [workspaces, workspaceId]);
} else {
setActiveWorkspace(workspaces.find((w) => w.id === params.workspaceId) ?? null);
}
}, [workspaces, params.workspaceId]);
return activeWorkspace; return activeWorkspace;
} }

View File

@@ -12,7 +12,7 @@ export function useCreateRequest({ navigateAfter }: { navigateAfter: boolean })
if (workspace === null) { if (workspace === null) {
throw new Error("Cannot create request when there's no active workspace"); 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) => { onSuccess: async (requestId) => {
if (navigateAfter) { if (navigateAfter) {

View File

@@ -7,11 +7,11 @@ export function useDeleteRequest(request: HttpRequest | null) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<void, string>({ return useMutation<void, string>({
mutationFn: async () => { mutationFn: async () => {
if (request == null) return; if (!request) return;
await invoke('delete_request', { requestId: request.id }); await invoke('delete_request', { requestId: request.id });
}, },
onSuccess: async () => { onSuccess: async () => {
if (request == null) return; if (!request) return;
await queryClient.invalidateQueries(requestsQueryKey(request.workspaceId)); await queryClient.invalidateQueries(requestsQueryKey(request.workspaceId));
}, },
}); });

View File

@@ -5,11 +5,11 @@ export function useDeleteResponses(requestId?: string) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async () => { mutationFn: async () => {
if (requestId == null) return; if (!requestId) return;
await invoke('delete_all_responses', { requestId }); await invoke('delete_all_responses', { requestId });
}, },
onSuccess: () => { onSuccess: () => {
if (requestId == null) return; if (!requestId) return;
queryClient.setQueryData(['responses', { requestId: requestId }], []); queryClient.setQueryData(['responses', { requestId: requestId }], []);
}, },
}); });

View File

@@ -1,6 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import type { HttpResponse } from '../lib/models'; import type { HttpResponse } from '../lib/models';
import { responsesQueryKey } from './useResponses';
export function useDeleteResponse(response: HttpResponse | null) { export function useDeleteResponse(response: HttpResponse | null) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -12,7 +13,7 @@ export function useDeleteResponse(response: HttpResponse | null) {
onSuccess: () => { onSuccess: () => {
if (response === null) return; if (response === null) return;
queryClient.setQueryData( queryClient.setQueryData(
['responses', { requestId: response.requestId }], responsesQueryKey(response.requestId),
(responses: HttpResponse[] = []) => responses.filter((r) => r.id !== response.id), (responses: HttpResponse[] = []) => responses.filter((r) => r.id !== response.id),
); );
}, },

View File

@@ -8,12 +8,14 @@ import {
toggleAppearance, toggleAppearance,
} from '../lib/theme/window'; } from '../lib/theme/window';
const appearanceQueryKey = ['theme', 'appearance']; export function appearanceQueryKey() {
return ['theme', 'appearance'];
}
export function useTheme() { export function useTheme() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const appearance = useQuery({ const appearance = useQuery({
queryKey: appearanceQueryKey, queryKey: appearanceQueryKey(),
queryFn: getAppearance, queryFn: getAppearance,
initialData: getAppearance(), initialData: getAppearance(),
}).data; }).data;
@@ -24,12 +26,10 @@ export function useTheme() {
const handleToggleAppearance = async () => { const handleToggleAppearance = async () => {
const newAppearance = toggleAppearance(); const newAppearance = toggleAppearance();
await queryClient.setQueryData(appearanceQueryKey, newAppearance); await queryClient.setQueryData(appearanceQueryKey(), newAppearance);
}; };
useEffect(() => { useEffect(() => subscribeToPreferredAppearanceChange(themeChange), []);
return subscribeToPreferredAppearanceChange(themeChange);
}, []);
return { return {
appearance, 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('');
}