From edf3b8089f26f0b99c6cc2765cd2a0f07bd5969d Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 12 Jun 2025 00:54:22 +0200 Subject: [PATCH 1/2] upd --- .changeset/ten-peas-carry.md | 5 +++ .../src/components/header-editor.tsx | 6 ++-- .../src/components/provider.tsx | 31 ++++++++++--------- .../src/components/query-editor.tsx | 15 ++++----- .../src/components/variable-editor.tsx | 6 ++-- packages/graphiql-react/src/constants.ts | 10 ++++++ packages/graphiql-react/src/stores/editor.ts | 18 +++++------ packages/graphiql-react/src/stores/index.ts | 4 +++ packages/graphiql-react/src/stores/plugin.ts | 5 ++- .../graphiql-react/src/utility/tabs.spec.ts | 6 ++-- packages/graphiql-react/src/utility/tabs.ts | 9 +++--- 11 files changed, 63 insertions(+), 52 deletions(-) create mode 100644 .changeset/ten-peas-carry.md diff --git a/.changeset/ten-peas-carry.md b/.changeset/ten-peas-carry.md new file mode 100644 index 0000000000..9ef67dcc24 --- /dev/null +++ b/.changeset/ten-peas-carry.md @@ -0,0 +1,5 @@ +--- +'@graphiql/react': minor +--- + +extract storage key constants diff --git a/packages/graphiql-react/src/components/header-editor.tsx b/packages/graphiql-react/src/components/header-editor.tsx index 71f8fdfa27..d0bbabf006 100644 --- a/packages/graphiql-react/src/components/header-editor.tsx +++ b/packages/graphiql-react/src/components/header-editor.tsx @@ -1,7 +1,7 @@ import { FC, useEffect, useRef } from 'react'; import { useGraphiQL, useGraphiQLActions } from './provider'; import { EditorProps } from '../types'; -import { HEADER_URI, KEY_BINDINGS } from '../constants'; +import { HEADER_URI, KEY_BINDINGS, STORAGE_KEY } from '../constants'; import { getOrCreateModel, createEditor, @@ -28,7 +28,7 @@ export const HeaderEditor: FC = ({ onEdit, ...props }) => { const ref = useRef(null!); useChangeHandler( onEdit, - shouldPersistHeaders ? STORAGE_KEY : null, + shouldPersistHeaders ? STORAGE_KEY.headers : null, 'headers', ); useEffect(() => { @@ -56,5 +56,3 @@ export const HeaderEditor: FC = ({ onEdit, ...props }) => { /> ); }; - -export const STORAGE_KEY = 'headers'; diff --git a/packages/graphiql-react/src/components/provider.tsx b/packages/graphiql-react/src/components/provider.tsx index 1e32f905db..e3a88f1f3e 100644 --- a/packages/graphiql-react/src/components/provider.tsx +++ b/packages/graphiql-react/src/components/provider.tsx @@ -14,11 +14,11 @@ import { createExecutionSlice, createPluginSlice, createSchemaSlice, + EditorProps, + ExecutionProps, + PluginProps, + SchemaProps, } from '../stores'; -import { EditorProps, PERSIST_HEADERS_STORAGE_KEY } from '../stores/editor'; -import { ExecutionProps } from '../stores/execution'; -import { PluginProps, STORAGE_KEY_VISIBLE_PLUGIN } from '../stores/plugin'; -import { SchemaProps } from '../stores/schema'; import { StorageStore, useStorage } from '../stores/storage'; import { ThemeStore } from '../stores/theme'; import { SlicesWithActions } from '../types'; @@ -30,10 +30,11 @@ import { isSchema, validateSchema, } from 'graphql'; -import { DEFAULT_PRETTIFY_QUERY, DEFAULT_QUERY } from '../constants'; -import { STORAGE_KEY_QUERY } from './query-editor'; -import { STORAGE_KEY as STORAGE_KEY_VARIABLES } from './variable-editor'; -import { STORAGE_KEY as STORAGE_KEY_HEADERS } from './header-editor'; +import { + DEFAULT_PRETTIFY_QUERY, + DEFAULT_QUERY, + STORAGE_KEY, +} from '../constants'; import { getDefaultTabState } from '../utility/tabs'; interface InnerGraphiQLProviderProps @@ -121,7 +122,7 @@ const InnerGraphiQLProvider: FC = ({ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- false positive if (storeRef.current === null) { function getInitialVisiblePlugin() { - const storedValue = storage.get(STORAGE_KEY_VISIBLE_PLUGIN); + const storedValue = storage.get(STORAGE_KEY.visiblePlugin); const pluginForStoredValue = plugins.find( plugin => plugin.title === storedValue, ); @@ -129,17 +130,17 @@ const InnerGraphiQLProvider: FC = ({ return pluginForStoredValue; } if (storedValue) { - storage.set(STORAGE_KEY_VISIBLE_PLUGIN, ''); + storage.set(STORAGE_KEY.visiblePlugin, ''); } return visiblePlugin; } function getInitialState() { // We only need to compute it lazily during the initial render. - const query = props.query ?? storage.get(STORAGE_KEY_QUERY) ?? null; + const query = props.query ?? storage.get(STORAGE_KEY.query) ?? null; const variables = - props.variables ?? storage.get(STORAGE_KEY_VARIABLES) ?? null; - const headers = props.headers ?? storage.get(STORAGE_KEY_HEADERS) ?? null; + props.variables ?? storage.get(STORAGE_KEY.variables) ?? null; + const headers = props.headers ?? storage.get(STORAGE_KEY.headers) ?? null; const response = props.response ?? ''; const { tabs, activeTabIndex } = getDefaultTabState({ @@ -152,11 +153,11 @@ const InnerGraphiQLProvider: FC = ({ variables, }); - const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; + const isStored = storage.get(STORAGE_KEY.persistHeaders) !== null; const $shouldPersistHeaders = shouldPersistHeaders !== false && isStored - ? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true' + ? storage.get(STORAGE_KEY.persistHeaders) === 'true' : shouldPersistHeaders; const store = create((...args) => { diff --git a/packages/graphiql-react/src/components/query-editor.tsx b/packages/graphiql-react/src/components/query-editor.tsx index 513e6e6298..9ab79f2f65 100644 --- a/packages/graphiql-react/src/components/query-editor.tsx +++ b/packages/graphiql-react/src/components/query-editor.tsx @@ -14,7 +14,12 @@ import { cn, } from '../utility'; import { MonacoEditor, EditorProps, SchemaReference } from '../types'; -import { KEY_BINDINGS, MONACO_GRAPHQL_API, QUERY_URI } from '../constants'; +import { + KEY_BINDINGS, + MONACO_GRAPHQL_API, + QUERY_URI, + STORAGE_KEY, +} from '../constants'; import { type editor as monacoEditor, languages, @@ -210,11 +215,11 @@ export const QueryEditor: FC = ({ // have additional logic that updates the operation facts that we save in `editorStore` const handleChange = debounce(100, () => { const query = editor.getValue(); - storage.set(STORAGE_KEY_QUERY, query); + storage.set(STORAGE_KEY.query, query); const operationFacts = getAndUpdateOperationFacts(editor); if (operationFacts?.operationName !== undefined) { - storage.set(STORAGE_KEY_OPERATION_NAME, operationFacts.operationName); + storage.set(STORAGE_KEY.operationName, operationFacts.operationName); } // Invoke callback props only after the operation facts have been updated @@ -333,7 +338,3 @@ export const QueryEditor: FC = ({ /> ); }; - -export const STORAGE_KEY_QUERY = 'query'; - -const STORAGE_KEY_OPERATION_NAME = 'operationName'; diff --git a/packages/graphiql-react/src/components/variable-editor.tsx b/packages/graphiql-react/src/components/variable-editor.tsx index 4214a65301..b8fc3e49bc 100644 --- a/packages/graphiql-react/src/components/variable-editor.tsx +++ b/packages/graphiql-react/src/components/variable-editor.tsx @@ -1,7 +1,7 @@ import { FC, useEffect, useRef } from 'react'; import { useGraphiQL, useGraphiQLActions } from './provider'; import { EditorProps } from '../types'; -import { KEY_BINDINGS, VARIABLE_URI } from '../constants'; +import { KEY_BINDINGS, STORAGE_KEY, VARIABLE_URI } from '../constants'; import { getOrCreateModel, createEditor, @@ -26,7 +26,7 @@ export const VariableEditor: FC = ({ const { setEditor, run, prettifyEditors, mergeQuery } = useGraphiQLActions(); const initialVariables = useGraphiQL(state => state.initialVariables); const ref = useRef(null!); - useChangeHandler(onEdit, STORAGE_KEY, 'variables'); + useChangeHandler(onEdit, STORAGE_KEY.variables, 'variables'); useEffect(() => { const model = getOrCreateModel({ uri: VARIABLE_URI, @@ -54,5 +54,3 @@ export const VariableEditor: FC = ({ /> ); }; - -export const STORAGE_KEY = 'variables'; diff --git a/packages/graphiql-react/src/constants.ts b/packages/graphiql-react/src/constants.ts index e7ed34f2a7..70b083ed3e 100644 --- a/packages/graphiql-react/src/constants.ts +++ b/packages/graphiql-react/src/constants.ts @@ -42,6 +42,16 @@ export const KEY_MAP = Object.freeze({ }, }); +export const STORAGE_KEY = { + headers: 'headers', + visiblePlugin: 'visiblePlugin', + query: 'query', + variables: 'variables', + tabs: 'tabState', + operationName: 'operationName', + persistHeaders: 'shouldPersistHeaders', +} as const; + export const DEFAULT_QUERY = `# Welcome to GraphiQL # # GraphiQL is an in-browser tool for writing, validating, and testing diff --git a/packages/graphiql-react/src/stores/editor.ts b/packages/graphiql-react/src/stores/editor.ts index 38cfc4cbf6..59b1550e60 100644 --- a/packages/graphiql-react/src/stores/editor.ts +++ b/packages/graphiql-react/src/stores/editor.ts @@ -6,9 +6,8 @@ import type { } from 'graphql'; import { OperationFacts } from 'graphql-language-service'; import { MaybePromise, mergeAst } from '@graphiql/toolkit'; +import { print } from 'graphql'; import { storageStore } from './storage'; -import { STORAGE_KEY as STORAGE_KEY_HEADERS } from '../components/header-editor'; - import { createTab, setPropertiesInActiveTab, @@ -17,11 +16,10 @@ import { TabState, clearHeadersFromTabs, serializeTabState, - STORAGE_KEY as STORAGE_KEY_TABS, } from '../utility/tabs'; import { SlicesWithActions, MonacoEditor } from '../types'; import { debounce, formatJSONC } from '../utility'; -import { print } from 'graphql'; +import { STORAGE_KEY } from '../constants'; export interface EditorSlice extends TabsState { /** @@ -478,24 +476,24 @@ export const createEditorSlice: CreateEditorSlice = initial => (set, get) => { const { headerEditor, tabs, activeTabIndex } = get(); const { storage } = storageStore.getState(); if (persist) { - storage.set(STORAGE_KEY_HEADERS, headerEditor?.getValue() ?? ''); + storage.set(STORAGE_KEY.headers, headerEditor?.getValue() ?? ''); const serializedTabs = serializeTabState( { tabs, activeTabIndex }, true, ); - storage.set(STORAGE_KEY_TABS, serializedTabs); + storage.set(STORAGE_KEY.tabs, serializedTabs); } else { - storage.set(STORAGE_KEY_HEADERS, ''); + storage.set(STORAGE_KEY.headers, ''); clearHeadersFromTabs(); } set({ shouldPersistHeaders: persist }); - storage.set(PERSIST_HEADERS_STORAGE_KEY, persist.toString()); + storage.set(STORAGE_KEY.persistHeaders, persist.toString()); }, storeTabs({ tabs, activeTabIndex }) { const { storage } = storageStore.getState(); const { shouldPersistHeaders } = get(); const store = debounce(500, (value: string) => { - storage.set(STORAGE_KEY_TABS, value); + storage.set(STORAGE_KEY.tabs, value); }); store(serializeTabState({ tabs, activeTabIndex }, shouldPersistHeaders)); }, @@ -587,5 +585,3 @@ export const createEditorSlice: CreateEditorSlice = initial => (set, get) => { actions: $actions, }; }; - -export const PERSIST_HEADERS_STORAGE_KEY = 'shouldPersistHeaders'; diff --git a/packages/graphiql-react/src/stores/index.ts b/packages/graphiql-react/src/stores/index.ts index bb8201c98e..2f4a7c1c96 100644 --- a/packages/graphiql-react/src/stores/index.ts +++ b/packages/graphiql-react/src/stores/index.ts @@ -2,21 +2,25 @@ export { createEditorSlice, type EditorSlice, type EditorActions, + type EditorProps, } from './editor'; export { createExecutionSlice, type ExecutionSlice, type ExecutionActions, + type ExecutionProps, } from './execution'; export { createPluginSlice, type PluginSlice, type PluginActions, + type PluginProps, } from './plugin'; export { createSchemaSlice, type SchemaSlice, type SchemaActions, + type SchemaProps, } from './schema'; export { storageStore, useStorage } from './storage'; export { themeStore, useThemeStore, type Theme } from './theme'; diff --git a/packages/graphiql-react/src/stores/plugin.ts b/packages/graphiql-react/src/stores/plugin.ts index 892974cee4..6344d69e7b 100644 --- a/packages/graphiql-react/src/stores/plugin.ts +++ b/packages/graphiql-react/src/stores/plugin.ts @@ -2,6 +2,7 @@ import { ComponentType } from 'react'; import type { StateCreator } from 'zustand'; import type { SlicesWithActions } from '../types'; import { storageStore } from './storage'; +import { STORAGE_KEY } from '../constants'; export interface GraphiQLPlugin { /** @@ -107,7 +108,7 @@ export const createPluginSlice: CreatePluginSlice = initial => set => ({ } onTogglePluginVisibility?.(newVisiblePlugin); const { storage } = storageStore.getState(); - storage.set(STORAGE_KEY_VISIBLE_PLUGIN, newVisiblePlugin?.title ?? ''); + storage.set(STORAGE_KEY.visiblePlugin, newVisiblePlugin?.title ?? ''); return { visiblePlugin: newVisiblePlugin }; }); }, @@ -129,5 +130,3 @@ export const createPluginSlice: CreatePluginSlice = initial => set => ({ }, }, }); - -export const STORAGE_KEY_VISIBLE_PLUGIN = 'visiblePlugin'; diff --git a/packages/graphiql-react/src/utility/tabs.spec.ts b/packages/graphiql-react/src/utility/tabs.spec.ts index 1ea5931e85..5ccdb96c0e 100644 --- a/packages/graphiql-react/src/utility/tabs.spec.ts +++ b/packages/graphiql-react/src/utility/tabs.spec.ts @@ -5,9 +5,9 @@ import { fuzzyExtractOperationName, getDefaultTabState, clearHeadersFromTabs, - STORAGE_KEY, } from './tabs'; import { storageStore } from '../stores'; +import { STORAGE_KEY } from '../constants'; describe('createTab', () => { it('creates with default title', () => { @@ -179,10 +179,10 @@ describe('clearHeadersFromTabs', () => { }, headers: '{ "authorization": "secret" }', }; - storage.set(STORAGE_KEY, JSON.stringify(stateWithHeaders)); + storage.set(STORAGE_KEY.tabs, JSON.stringify(stateWithHeaders)); clearHeadersFromTabs(); - expect(JSON.parse(storage.get(STORAGE_KEY)!)).toEqual({ + expect(JSON.parse(storage.get(STORAGE_KEY.tabs)!)).toEqual({ ...stateWithHeaders, headers: null, }); diff --git a/packages/graphiql-react/src/utility/tabs.ts b/packages/graphiql-react/src/utility/tabs.ts index 4b8d3f5812..a75e908688 100644 --- a/packages/graphiql-react/src/utility/tabs.ts +++ b/packages/graphiql-react/src/utility/tabs.ts @@ -1,5 +1,6 @@ 'use no memo'; // can't figure why it isn't optimized +import { STORAGE_KEY } from '../constants'; import { storageStore } from '../stores'; export interface TabDefinition { @@ -85,7 +86,7 @@ export function getDefaultTabState({ shouldPersistHeaders?: boolean; }) { const { storage } = storageStore.getState(); - const storedState = storage.get(STORAGE_KEY); + const storedState = storage.get(STORAGE_KEY.tabs); try { if (!storedState) { throw new Error('Storage for tabs is empty'); @@ -266,11 +267,11 @@ export function fuzzyExtractOperationName(str: string): string | null { export function clearHeadersFromTabs() { const { storage } = storageStore.getState(); - const persistedTabs = storage.get(STORAGE_KEY); + const persistedTabs = storage.get(STORAGE_KEY.tabs); if (persistedTabs) { const parsedTabs = JSON.parse(persistedTabs); storage.set( - STORAGE_KEY, + STORAGE_KEY.tabs, JSON.stringify(parsedTabs, (key, value) => key === 'headers' ? null : value, ), @@ -279,5 +280,3 @@ export function clearHeadersFromTabs() { } const DEFAULT_TITLE = ''; - -export const STORAGE_KEY = 'tabState'; From 85e37ae486823672cfef9c6ee7d2af871184c602 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 12 Jun 2025 01:05:31 +0200 Subject: [PATCH 2/2] upd --- packages/graphiql-react/src/constants.ts | 1 + packages/graphiql-react/src/stores/theme.ts | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/graphiql-react/src/constants.ts b/packages/graphiql-react/src/constants.ts index 70b083ed3e..5a3a9a0ffd 100644 --- a/packages/graphiql-react/src/constants.ts +++ b/packages/graphiql-react/src/constants.ts @@ -50,6 +50,7 @@ export const STORAGE_KEY = { tabs: 'tabState', operationName: 'operationName', persistHeaders: 'shouldPersistHeaders', + theme: 'theme', } as const; export const DEFAULT_QUERY = `# Welcome to GraphiQL diff --git a/packages/graphiql-react/src/stores/theme.ts b/packages/graphiql-react/src/stores/theme.ts index a87dad6c47..893b22cf2e 100644 --- a/packages/graphiql-react/src/stores/theme.ts +++ b/packages/graphiql-react/src/stores/theme.ts @@ -1,9 +1,10 @@ import { FC, ReactElement, ReactNode, useEffect } from 'react'; -import { storageStore } from './index'; +import { storageStore } from './storage'; import { createStore } from 'zustand'; import { createBoundedUseStore } from '../utility'; import { EDITOR_THEME } from '../utility/create-editor'; import { editor as monacoEditor } from '../monaco-editor'; +import { STORAGE_KEY } from '../constants'; /** * The value `null` semantically means that the user does not explicitly choose @@ -47,7 +48,7 @@ export const themeStore = createStore(set => ({ theme: null, setTheme(theme) { const { storage } = storageStore.getState(); - storage.set(STORAGE_KEY, theme ?? ''); + storage.set(STORAGE_KEY.theme, theme ?? ''); set({ theme }); }, })); @@ -62,7 +63,7 @@ export const ThemeStore: FC = ({ const { storage } = storageStore.getState(); function getInitialTheme() { - const stored = storage.get(STORAGE_KEY); + const stored = storage.get(STORAGE_KEY.theme); switch (stored) { case 'light': return 'light'; @@ -71,7 +72,7 @@ export const ThemeStore: FC = ({ default: if (typeof stored === 'string') { // Remove the invalid stored value - storage.set(STORAGE_KEY, ''); + storage.set(STORAGE_KEY.theme, ''); } return defaultTheme; } @@ -103,6 +104,4 @@ function getSystemTheme() { return systemTheme; } -const STORAGE_KEY = 'theme'; - export const useThemeStore = createBoundedUseStore(themeStore);