mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01:00
Fix strict mode editor blur bug
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Yaak App</title>
|
||||
<!-- <script src="http://localhost:8097"></script>-->
|
||||
<script src="http://localhost:8097"></script>
|
||||
<style>
|
||||
body {
|
||||
background-color: white;
|
||||
|
||||
@@ -14,6 +14,9 @@ const queryClient = new QueryClient({
|
||||
queries: {
|
||||
cacheTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
networkMode: 'offlineFirst',
|
||||
|
||||
// It's a desktop app, so this isn't necessary
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -39,8 +39,9 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeRequestId = activeRequest?.id ?? null;
|
||||
const updateRequest = useUpdateRequest(activeRequestId);
|
||||
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
||||
const [activeTab, setActiveTab] = useActiveTab();
|
||||
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest?.id ?? null);
|
||||
|
||||
const tabs: TabItem[] = useMemo(
|
||||
() =>
|
||||
@@ -60,7 +61,11 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
||||
],
|
||||
onChange: async (bodyType) => {
|
||||
const patch: Partial<HttpRequest> = { bodyType };
|
||||
if (bodyType == BODY_TYPE_GRAPHQL) {
|
||||
if (bodyType === BODY_TYPE_NONE) {
|
||||
patch.headers = activeRequest?.headers.filter(
|
||||
(h) => h.name.toLowerCase() !== 'content-type',
|
||||
);
|
||||
} else if (bodyType == BODY_TYPE_GRAPHQL || bodyType === BODY_TYPE_JSON) {
|
||||
patch.method = 'POST';
|
||||
patch.headers = [
|
||||
...(activeRequest?.headers.filter(
|
||||
@@ -72,14 +77,17 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
setTimeout(() => {
|
||||
setForceUpdateHeaderEditorKey((u) => u + 1);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Force update header editor so any changed headers are reflected
|
||||
setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100);
|
||||
|
||||
await updateRequest.mutate(patch);
|
||||
},
|
||||
},
|
||||
},
|
||||
// { value: 'params', label: 'URL Params' },
|
||||
{ value: 'headers', label: 'Headers' },
|
||||
{
|
||||
value: 'auth',
|
||||
label: 'Auth',
|
||||
@@ -107,8 +115,6 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
||||
},
|
||||
},
|
||||
},
|
||||
// { value: 'params', label: 'URL Params' },
|
||||
{ value: 'headers', label: 'Headers' },
|
||||
],
|
||||
[
|
||||
activeRequest?.bodyType,
|
||||
@@ -124,8 +130,6 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
||||
[],
|
||||
);
|
||||
|
||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest?.id ?? null);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/vie
|
||||
import classnames from 'classnames';
|
||||
import { EditorView } from 'codemirror';
|
||||
import type { MutableRefObject } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { memo, useEffect, useMemo, useRef } from 'react';
|
||||
import { IconButton } from '../IconButton';
|
||||
import './Editor.css';
|
||||
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
|
||||
@@ -20,13 +20,13 @@ export { formatSdl } from 'format-graphql';
|
||||
|
||||
export interface EditorProps {
|
||||
id?: string;
|
||||
forceUpdateKey?: string;
|
||||
readOnly?: boolean;
|
||||
type?: 'text' | 'password';
|
||||
className?: string;
|
||||
heightMode?: 'auto' | 'full';
|
||||
contentType?: string;
|
||||
languageExtension?: Extension;
|
||||
forceUpdateKey?: string;
|
||||
autoFocus?: boolean;
|
||||
defaultValue?: string;
|
||||
placeholder?: string;
|
||||
@@ -39,12 +39,24 @@ export interface EditorProps {
|
||||
autocomplete?: GenericCompletionConfig;
|
||||
}
|
||||
|
||||
export function Editor({
|
||||
export function Editor({ defaultValue, forceUpdateKey, ...props }: EditorProps) {
|
||||
// In order to not have the editor render too much, we combine forceUpdateKey
|
||||
// here with default value so that we only send new props to the editor when
|
||||
// forceUpdateKey changes. The editor can then use the defaultValue to force
|
||||
// update instead of using both forceUpdateKey and defaultValue.
|
||||
//
|
||||
// NOTE: This was originally done to fix a bug where the editor would unmount
|
||||
// and remount after the first change event, something to do with React
|
||||
// StrictMode. This fixes it, though, and actually makes more sense
|
||||
const fixedDefaultValue = useMemo(() => defaultValue, [forceUpdateKey]);
|
||||
return <_Editor defaultValue={fixedDefaultValue} {...props} />;
|
||||
}
|
||||
|
||||
const _Editor = memo(function _Editor({
|
||||
readOnly,
|
||||
type = 'text',
|
||||
heightMode,
|
||||
contentType,
|
||||
forceUpdateKey,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
useTemplating,
|
||||
@@ -56,7 +68,7 @@ export function Editor({
|
||||
singleLine,
|
||||
format,
|
||||
autocomplete,
|
||||
}: EditorProps) {
|
||||
}: Omit<EditorProps, 'forceUpdateKey'>) {
|
||||
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@@ -94,7 +106,7 @@ export function Editor({
|
||||
if (cm.current === null) return;
|
||||
const { view } = cm.current;
|
||||
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: defaultValue ?? '' } });
|
||||
}, [forceUpdateKey]);
|
||||
}, [defaultValue]);
|
||||
|
||||
// Initialize the editor when ref mounts
|
||||
useEffect(() => {
|
||||
@@ -173,7 +185,7 @@ export function Editor({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function getExtensions({
|
||||
container,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { memo } from 'react';
|
||||
import * as editor from './Editor';
|
||||
|
||||
export type { EditorProps } from './Editor';
|
||||
@@ -6,7 +5,7 @@ export type { EditorProps } from './Editor';
|
||||
// showing any content
|
||||
// const editor = await import('./Editor');
|
||||
|
||||
export const Editor = memo(editor.Editor);
|
||||
export const Editor = editor.Editor;
|
||||
export const graphql = editor.graphql;
|
||||
export const getIntrospectionQuery = editor.getIntrospectionQuery;
|
||||
export const buildClientSchema = editor.buildClientSchema;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classnames from 'classnames';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import type { EditorProps } from './Editor';
|
||||
import { Editor } from './Editor';
|
||||
import { IconButton } from './IconButton';
|
||||
@@ -43,6 +43,7 @@ export function Input({
|
||||
defaultValue,
|
||||
validate,
|
||||
require,
|
||||
forceUpdateKey,
|
||||
...props
|
||||
}: InputProps) {
|
||||
const [obscured, setObscured] = useState(type === 'password');
|
||||
@@ -62,10 +63,10 @@ export function Input({
|
||||
return true;
|
||||
}, [currentValue, validate, require]);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
const handleChange = useCallback((value: string) => {
|
||||
setCurrentValue(value);
|
||||
onChange?.(value);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<VStack className="w-full">
|
||||
@@ -96,6 +97,7 @@ export function Input({
|
||||
singleLine
|
||||
type={type === 'password' && !obscured ? 'text' : type}
|
||||
defaultValue={defaultValue}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
className={inputClassName}
|
||||
|
||||
@@ -5,8 +5,9 @@ const useGlobalState = createGlobalState<Record<string, string>>({});
|
||||
|
||||
export function useRequestUpdateKey(requestId: string | null) {
|
||||
const [keys, setKeys] = useGlobalState();
|
||||
const key = keys[requestId ?? 'n/a'];
|
||||
return {
|
||||
updateKey: `${requestId}::${keys[requestId ?? 'n/a']}`,
|
||||
updateKey: `${requestId}::${key ?? 'default'}`,
|
||||
wasUpdatedExternally: (changedRequestId: string) => {
|
||||
setKeys((m) => ({ ...m, [changedRequestId]: generateId() }));
|
||||
},
|
||||
|
||||
@@ -62,7 +62,10 @@ export function useTauriListeners() {
|
||||
: null;
|
||||
|
||||
if (queryKey === null) {
|
||||
throw new Error('Unrecognized updated model ' + payload.model);
|
||||
if (payload.model) {
|
||||
console.log('Unrecognized updated model:', payload);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const skipSync = payload.model === 'key_value' && payload.namespace === NAMESPACE_NO_SYNC;
|
||||
|
||||
@@ -47,13 +47,16 @@
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply hover:bg-gray-300 rounded-full;
|
||||
}
|
||||
|
||||
.scrollbar-thumb,
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-200 hover:bg-gray-300 rounded-full;
|
||||
@apply bg-gray-500/30 hover:bg-gray-500/50 rounded-full;
|
||||
}
|
||||
|
||||
iframe {
|
||||
&::-webkit-scrollbar-corner,
|
||||
&::-webkit-scrollbar {
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user