mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 00:58:32 +02:00
Fix graphql and other things
This commit is contained in:
@@ -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 })}
|
||||||
|
|||||||
@@ -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
|
||||||
? [
|
? [
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 }], []);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
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