diff --git a/src-web/components/core/Editor/Editor.css b/src-web/components/core/Editor/Editor.css index 40f739cb..7e08af15 100644 --- a/src-web/components/core/Editor/Editor.css +++ b/src-web/components/core/Editor/Editor.css @@ -35,14 +35,17 @@ } /* Don't show selection on blurred input */ + .cm-selectionBackground { @apply bg-transparent; } + &.cm-focused .cm-selectionBackground { @apply bg-selection; } /* Style gutters */ + .cm-gutters { @apply border-0 text-gray-500/50; @@ -100,10 +103,10 @@ @apply font-mono text-[0.75rem]; /* - * Round corners or they'll stick out of the editor bounds of editor is rounded. - * Could potentially be pushed up from the editor like we do with bg color but this - * is probably fine. - */ + * Round corners or they'll stick out of the editor bounds of editor is rounded. + * Could potentially be pushed up from the editor like we do with bg color but this + * is probably fine. + */ @apply rounded-lg; } } @@ -164,8 +167,9 @@ @apply h-full flex items-center; /* Break characters on line wrapping mode, useful for URL field. - * We can make this dynamic if we need it to be configurable later - */ + * We can make this dynamic if we need it to be configurable later + */ + &.cm-lineWrapping { @apply break-all; } @@ -176,6 +180,58 @@ .cm-tooltip.cm-tooltip { @apply shadow-lg bg-gray-50 rounded text-gray-700 border border-gray-200 z-50 pointer-events-auto text-[0.75rem]; + .cm-completionIcon { + @apply italic font-mono; + + &.cm-completionIcon-class::after { + content: 'o' !important; + } + + &.cm-completionIcon-constant::after { + content: 'c' !important; + } + + &.cm-completionIcon-enum::after { + content: 'e' !important; + } + + &.cm-completionIcon-function::after { + content: 'z' !important; + } + + &.cm-completionIcon-interface::after { + content: 'i' !important; + } + + &.cm-completionIcon-keyword::after { + content: 'k' !important; + } + + &.cm-completionIcon-method::after { + content: 'm' !important; + } + + &.cm-completionIcon-namespace::after { + content: 'n' !important; + } + + &.cm-completionIcon-property::after { + content: 'a' !important; + } + + &.cm-completionIcon-text::after { + content: 'x' !important; + } + + &.cm-completionIcon-type::after { + content: 't' !important; + } + + &.cm-completionIcon-variable::after { + content: 'x' !important; + } + } + &.cm-completionInfo-right { @apply ml-1 -mt-0.5 text-sm; } @@ -232,12 +288,8 @@ } /* Hide the "All" button */ + button[name='select'] { @apply hidden; } } - -/* Add default icon. Needs low priority so it can be overwritten */ -.cm-completionIcon::after { - content: '𝑥'; -} diff --git a/src-web/components/core/Editor/extensions.ts b/src-web/components/core/Editor/extensions.ts index 028a4fc8..39deef4a 100644 --- a/src-web/components/core/Editor/extensions.ts +++ b/src-web/components/core/Editor/extensions.ts @@ -126,7 +126,7 @@ export const baseExtensions = [ // debouncedAutocompletionDisplay({ millis: 1000 }), // autocompletion({ closeOnBlur: true, interactionDelay: 200, activateOnTyping: false }), autocompletion({ - // closeOnBlur: false, + closeOnBlur: false, // For debugging in devtools without closing it compareCompletions: (a, b) => { // Don't sort completions at all, only on boost return (a.boost ?? 0) - (b.boost ?? 0); diff --git a/src-web/hooks/useDuplicateGrpcRequest.ts b/src-web/hooks/useDuplicateGrpcRequest.ts index 3ad45e0d..9a9bc8ee 100644 --- a/src-web/hooks/useDuplicateGrpcRequest.ts +++ b/src-web/hooks/useDuplicateGrpcRequest.ts @@ -1,10 +1,12 @@ import { useMutation } from '@tanstack/react-query'; import { invoke } from '@tauri-apps/api'; import { trackEvent } from '../lib/analytics'; +import { setKeyValue } from '../lib/keyValueStore'; import type { GrpcRequest } from '../lib/models'; import { useActiveEnvironmentId } from './useActiveEnvironmentId'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useAppRoutes } from './useAppRoutes'; +import { protoFilesArgs, useGrpcProtoFiles } from './useGrpcProtoFiles'; export function useDuplicateGrpcRequest({ id, @@ -16,6 +18,7 @@ export function useDuplicateGrpcRequest({ const activeWorkspaceId = useActiveWorkspaceId(); const activeEnvironmentId = useActiveEnvironmentId(); const routes = useAppRoutes(); + const protoFiles = useGrpcProtoFiles(id); return useMutation({ mutationFn: async () => { if (id === null) throw new Error("Can't duplicate a null grpc request"); @@ -23,6 +26,9 @@ export function useDuplicateGrpcRequest({ }, onSettled: () => trackEvent('grpc_request', 'duplicate'), onSuccess: async (request) => { + // Also copy proto files to new request + await setKeyValue({ ...protoFilesArgs(request.id), value: protoFiles.value ?? [] }); + if (navigateAfter && activeWorkspaceId !== null) { routes.navigate('request', { workspaceId: activeWorkspaceId, diff --git a/src-web/hooks/useGrpcProtoFiles.ts b/src-web/hooks/useGrpcProtoFiles.ts index ef5949cc..ec7b5300 100644 --- a/src-web/hooks/useGrpcProtoFiles.ts +++ b/src-web/hooks/useGrpcProtoFiles.ts @@ -1,10 +1,13 @@ import { NAMESPACE_GLOBAL } from '../lib/keyValueStore'; import { useKeyValue } from './useKeyValue'; -export function useGrpcProtoFiles(activeRequestId: string | null) { - return useKeyValue({ +export function protoFilesArgs(requestId: string | null) { + return { namespace: NAMESPACE_GLOBAL, - key: ['proto_files', activeRequestId ?? 'n/a'], - defaultValue: [], - }); + key: ['proto_files', requestId ?? 'n/a'], + }; +} + +export function useGrpcProtoFiles(activeRequestId: string | null) { + return useKeyValue({ ...protoFilesArgs(activeRequestId), fallback: [] }); } diff --git a/src-web/hooks/useKeyValue.ts b/src-web/hooks/useKeyValue.ts index e9025d1b..95c2d776 100644 --- a/src-web/hooks/useKeyValue.ts +++ b/src-web/hooks/useKeyValue.ts @@ -18,16 +18,16 @@ export function keyValueQueryKey({ export function useKeyValue({ namespace = DEFAULT_NAMESPACE, key, - defaultValue, + fallback, }: { namespace?: string; key: string | string[]; - defaultValue: T; + fallback: T; }) { const queryClient = useQueryClient(); const query = useQuery({ queryKey: keyValueQueryKey({ namespace, key }), - queryFn: async () => getKeyValue({ namespace, key, fallback: defaultValue }), + queryFn: async () => getKeyValue({ namespace, key, fallback }), refetchOnWindowFocus: false, }); @@ -40,7 +40,7 @@ export function useKeyValue({ const set = useCallback( async (value: ((v: T) => T) | T) => { if (typeof value === 'function') { - await getKeyValue({ namespace, key, fallback: defaultValue }).then((kv) => { + await getKeyValue({ namespace, key, fallback }).then((kv) => { const newV = value(kv); if (newV === kv) return; return mutate.mutateAsync(newV); @@ -51,10 +51,10 @@ export function useKeyValue({ await mutate.mutateAsync(value); } }, - [defaultValue, key, mutate, namespace], + [fallback, key, mutate, namespace], ); - const reset = useCallback(async () => mutate.mutateAsync(defaultValue), [mutate, defaultValue]); + const reset = useCallback(async () => mutate.mutateAsync(fallback), [mutate, fallback]); return useMemo( () => ({