Fix strict mode editor blur bug

This commit is contained in:
Gregory Schier
2023-03-30 10:38:33 -07:00
parent bb2ba21bbd
commit 801b29402d
9 changed files with 56 additions and 29 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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