mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 17:48:30 +02:00
Better request delete and formatting
This commit is contained in:
@@ -441,5 +441,5 @@ fn main() {
|
|||||||
|
|
||||||
fn is_dev() -> bool {
|
fn is_dev() -> bool {
|
||||||
let env = option_env!("YAAK_ENV");
|
let env = option_env!("YAAK_ENV");
|
||||||
env.unwrap_or("production") == "development"
|
env.unwrap_or("production") != "production"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import classnames from 'classnames';
|
|||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useSendRequest } from '../hooks/useSendRequest';
|
import { useSendRequest } from '../hooks/useSendRequest';
|
||||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||||
|
import { tryFormatJson } from '../lib/formatters';
|
||||||
import { Editor } from './core/Editor';
|
import { Editor } from './core/Editor';
|
||||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||||
import { GraphQLEditor } from './editors/GraphQLEditor';
|
import { GraphQLEditor } from './editors/GraphQLEditor';
|
||||||
@@ -71,6 +72,7 @@ export function RequestPane({ fullHeight, className }: Props) {
|
|||||||
defaultValue={activeRequest.body ?? ''}
|
defaultValue={activeRequest.body ?? ''}
|
||||||
contentType="application/json"
|
contentType="application/json"
|
||||||
onChange={(body) => updateRequest.mutate({ body })}
|
onChange={(body) => updateRequest.mutate({ body })}
|
||||||
|
format={activeRequest.bodyType === 'json' ? (v) => tryFormatJson(v) : undefined}
|
||||||
/>
|
/>
|
||||||
) : activeRequest.bodyType === 'graphql' ? (
|
) : activeRequest.bodyType === 'graphql' ? (
|
||||||
<GraphQLEditor
|
<GraphQLEditor
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ export function Sidebar({ className }: Props) {
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="end"
|
justifyContent="end"
|
||||||
>
|
>
|
||||||
<IconButton title="Delete request" icon="trash" onClick={() => deleteRequest.mutate()} />
|
|
||||||
<IconButton
|
<IconButton
|
||||||
title={appearance === 'dark' ? 'Enable light mode' : 'Enable dark mode'}
|
title={appearance === 'dark' ? 'Enable light mode' : 'Enable dark mode'}
|
||||||
icon={appearance === 'dark' ? 'moon' : 'sun'}
|
icon={appearance === 'dark' ? 'moon' : 'sun'}
|
||||||
@@ -63,6 +62,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SidebarItem({ request, active }: { request: HttpRequest; active: boolean }) {
|
function SidebarItem({ request, active }: { request: HttpRequest; active: boolean }) {
|
||||||
|
const deleteRequest = useDeleteRequest(request);
|
||||||
const updateRequest = useUpdateRequest(request);
|
const updateRequest = useUpdateRequest(request);
|
||||||
const [editing, setEditing] = useState<boolean>(false);
|
const [editing, setEditing] = useState<boolean>(false);
|
||||||
const handleSubmitNameEdit = async (el: HTMLInputElement) => {
|
const handleSubmitNameEdit = async (el: HTMLInputElement) => {
|
||||||
@@ -76,7 +76,16 @@ function SidebarItem({ request, active }: { request: HttpRequest; active: boolea
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<li className="group/item relative">
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
color="custom"
|
||||||
|
iconSize="sm"
|
||||||
|
title="Delete request"
|
||||||
|
icon="trash"
|
||||||
|
className="absolute right-0 transition-opacity opacity-0 group-hover/item:opacity-100"
|
||||||
|
onClick={() => deleteRequest.mutate()}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
color="custom"
|
color="custom"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -85,6 +94,9 @@ function SidebarItem({ request, active }: { request: HttpRequest; active: boolea
|
|||||||
active
|
active
|
||||||
? 'bg-gray-200/70 text-gray-900'
|
? 'bg-gray-200/70 text-gray-900'
|
||||||
: 'text-gray-600 hover:text-gray-800 active:bg-gray-200/30',
|
: 'text-gray-600 hover:text-gray-800 active:bg-gray-200/30',
|
||||||
|
|
||||||
|
// Move out of the way when trash is shown
|
||||||
|
'group-hover/item:pr-6',
|
||||||
)}
|
)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
// Hitting enter on active request during keyboard nav will start edit
|
// Hitting enter on active request during keyboard nav will start edit
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { Editor } from './core/Editor';
|
|
||||||
import { Heading } from './core/Heading';
|
import { Heading } from './core/Heading';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
|
||||||
|
|
||||||
export default function Workspaces() {
|
export default function Workspaces() {
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useWorkspaces();
|
||||||
@@ -14,7 +13,6 @@ export default function Workspaces() {
|
|||||||
{w.name}
|
{w.name}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
<Editor defaultValue="hello world" />
|
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { defaultKeymap } from '@codemirror/commands';
|
import { defaultKeymap } from '@codemirror/commands';
|
||||||
import { Compartment, EditorState } from '@codemirror/state';
|
import { Compartment, EditorState } from '@codemirror/state';
|
||||||
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
|
import { keymap, placeholder as placeholderExt, tooltips, ViewPlugin } from '@codemirror/view';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { EditorView } from 'codemirror';
|
import { EditorView } from 'codemirror';
|
||||||
import type { MutableRefObject } from 'react';
|
import type { MutableRefObject } from 'react';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
import { useUnmount } from 'react-use';
|
import { useMount, useUnmount } from 'react-use';
|
||||||
|
import { IconButton } from '../IconButton';
|
||||||
import './Editor.css';
|
import './Editor.css';
|
||||||
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
|
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
|
||||||
import { singleLineExt } from './singleLine';
|
import { singleLineExt } from './singleLine';
|
||||||
@@ -24,6 +25,7 @@ export interface _EditorProps {
|
|||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
singleLine?: boolean;
|
singleLine?: boolean;
|
||||||
|
format?: (v: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _Editor({
|
export function _Editor({
|
||||||
@@ -38,6 +40,7 @@ export function _Editor({
|
|||||||
onFocus,
|
onFocus,
|
||||||
className,
|
className,
|
||||||
singleLine,
|
singleLine,
|
||||||
|
format,
|
||||||
}: _EditorProps) {
|
}: _EditorProps) {
|
||||||
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
|
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
|
||||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
@@ -109,18 +112,47 @@ export function _Editor({
|
|||||||
syncGutterBg({ parent: wrapperRef.current, className });
|
syncGutterBg({ parent: wrapperRef.current, className });
|
||||||
}, [className]);
|
}, [className]);
|
||||||
|
|
||||||
|
const cmContainer = useMemo(
|
||||||
|
() => (
|
||||||
|
<div
|
||||||
|
ref={wrapperRef}
|
||||||
|
dangerouslySetInnerHTML={{ __html: '' }}
|
||||||
|
className={classnames(
|
||||||
|
className,
|
||||||
|
'cm-wrapper text-base bg-gray-50',
|
||||||
|
heightMode === 'auto' ? 'cm-auto-height' : 'cm-full-height',
|
||||||
|
singleLine ? 'cm-singleline' : 'cm-multiline',
|
||||||
|
readOnly && 'cm-readonly',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (singleLine) {
|
||||||
|
return cmContainer;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="group relative h-full w-full">
|
||||||
ref={wrapperRef}
|
{cmContainer}
|
||||||
dangerouslySetInnerHTML={{ __html: '' }}
|
{format && (
|
||||||
className={classnames(
|
<IconButton
|
||||||
className,
|
size="sm"
|
||||||
'cm-wrapper text-base bg-gray-50',
|
title="Re-format"
|
||||||
heightMode === 'auto' ? 'cm-auto-height' : 'cm-full-height',
|
icon="magicWand"
|
||||||
singleLine ? 'cm-singleline' : 'cm-multiline',
|
className="absolute bottom-2 right-0 transition-opacity opacity-0 group-hover:opacity-70"
|
||||||
readOnly && 'cm-readonly',
|
onClick={() => {
|
||||||
|
if (cm.current === null) return;
|
||||||
|
const { doc } = cm.current.view.state;
|
||||||
|
const insert = format(doc.toString());
|
||||||
|
// Update editor and blur because the cursor will reset anyway
|
||||||
|
cm.current.view.dispatch({ changes: { from: 0, to: doc.length, insert } });
|
||||||
|
cm.current.view.contentDOM.blur();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
TriangleRightIcon,
|
TriangleRightIcon,
|
||||||
UpdateIcon,
|
UpdateIcon,
|
||||||
RowsIcon,
|
RowsIcon,
|
||||||
|
MagicWandIcon,
|
||||||
} from '@radix-ui/react-icons';
|
} from '@radix-ui/react-icons';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ const icons = {
|
|||||||
triangleLeft: TriangleLeftIcon,
|
triangleLeft: TriangleLeftIcon,
|
||||||
triangleRight: TriangleRightIcon,
|
triangleRight: TriangleRightIcon,
|
||||||
update: UpdateIcon,
|
update: UpdateIcon,
|
||||||
|
magicWand: MagicWandIcon,
|
||||||
x: Cross2Icon,
|
x: Cross2Icon,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,8 +68,8 @@ export function Icon({ icon, spin, size = 'md', className }: IconProps) {
|
|||||||
className,
|
className,
|
||||||
'text-inherit',
|
'text-inherit',
|
||||||
size === 'md' && 'h-4 w-4',
|
size === 'md' && 'h-4 w-4',
|
||||||
size === 'sm' && 'h-3 w-3',
|
size === 'sm' && 'h-3.5 w-3.5',
|
||||||
size === 'xs' && 'h-2 w-2',
|
size === 'xs' && 'h-3 w-3',
|
||||||
spin && 'animate-spin',
|
spin && 'animate-spin',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -46,26 +46,15 @@ export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: P
|
|||||||
|
|
||||||
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)]">
|
||||||
<div className="relative">
|
<Editor
|
||||||
<Editor
|
key={queryKey.key}
|
||||||
key={queryKey.key}
|
heightMode="auto"
|
||||||
heightMode="auto"
|
defaultValue={query ?? ''}
|
||||||
defaultValue={query ?? ''}
|
onChange={handleChangeQuery}
|
||||||
onChange={handleChangeQuery}
|
contentType="application/graphql"
|
||||||
contentType="application/graphql"
|
format={formatSdl}
|
||||||
{...extraEditorProps}
|
{...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
|
||||||
|
|||||||
Reference in New Issue
Block a user