UNPKG

@craftercms/studio-ui

Version:

Services, components, models & utils to build CrafterCMS authoring extensions.

195 lines (193 loc) 6.76 kB
/* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import React, { useEffect, useMemo, useState } from 'react'; import GraphiQLComponent from 'graphiql'; import 'graphiql/graphiql.min.css'; import { explorerPlugin } from '@graphiql/plugin-explorer'; import { buildClientSchema, getIntrospectionQuery } from 'graphql'; import GlobalAppToolbar from '../GlobalAppToolbar'; import { FormattedMessage } from 'react-intl'; import Box from '@mui/material/Box'; import { toQueryString } from '../../utils/object'; import useUpdateRefs from '../../hooks/useUpdateRefs'; import { isBlank } from '../../utils/string'; import { defaultQuery } from './utils'; import useEnv from '../../hooks/useEnv'; import useActiveSiteId from '../../hooks/useActiveSiteId'; import { GlobalStyles } from '@mui/material'; function GraphiQL(props) { const { guestBase } = useEnv(); const site = useActiveSiteId(); const { storageKey = site, showAppsButton, embedded = false, method = 'post', url = `${guestBase}/api/1/site/graphql${site ? `?crafterSite=${site}` : ''}`, onSubmittingAndOrPendingChange } = props; // We don't want to update the initialQuery. // eslint-disable-next-line react-hooks/exhaustive-deps const initialQuery = useMemo(() => window.localStorage.getItem(`${storageKey}graphiql:query`) ?? defaultQuery, []); const [query, setQuery] = useState(initialQuery); const [schema, setSchema] = useState(null); const storage = useMemo( () => ({ setItem: (key, value) => window.localStorage.setItem(`${storageKey}${key}`, value), getItem: (key) => window.localStorage.getItem(`${storageKey}${key}`), removeItem: (key) => window.localStorage.removeItem(`${storageKey}${key}`) }), [storageKey] ); const graphQLFetcher = useMemo(() => { const lowercaseMethod = method.toLowerCase(); const then = (response) => { try { return response.json(); } catch (error) { return response; } }; if (lowercaseMethod === 'get') { return (graphQLParams, opts) => { return fetch( `${url}${toQueryString({ query: encodeURIComponent(graphQLParams.query), variables: encodeURIComponent(JSON.stringify(graphQLParams.variables ?? '{}')) })}`, { method: 'get', headers: { 'Content-Type': 'application/json' } } ).then(then); }; } else { return (graphQLParams, opts) => { return fetch(url, { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(graphQLParams) }).then(then); }; } }, [url, method]); const refs = useUpdateRefs({ onSubmittingAndOrPendingChange, graphiql: null }); const hasChanges = isBlank(query) ? false : initialQuery !== query; const onEditQuery = (newQuery) => { setQuery(newQuery); window.localStorage.setItem(`${storageKey}graphiql:query`, newQuery); }; const explorer = explorerPlugin({ showAttribution: false }); useEffect(() => { refs.current.onSubmittingAndOrPendingChange?.({ hasPendingChanges: hasChanges }); }, [hasChanges, refs]); useEffect(() => { graphQLFetcher({ query: getIntrospectionQuery() }).then((result) => { setSchema(buildClientSchema(result.data)); }); }, [graphQLFetcher]); return React.createElement( React.Fragment, null, React.createElement(GlobalStyles, { styles: { '[data-radix-popper-content-wrapper], react-portal [data-reach-tooltip], .graphiql-dialog': { zIndex: '1301 !important' } } }), React.createElement( Box, { sx: { display: 'flex', flexDirection: 'column', height: '100%' } }, !embedded && React.createElement(GlobalAppToolbar, { title: React.createElement(FormattedMessage, { id: 'words.graphQL', defaultMessage: 'GraphQL' }), showAppsButton: showAppsButton }), React.createElement( Box, { className: 'graphiql-container', sx: { height: 'calc(100% - 65px)', position: 'relative', '.title': { display: 'none' }, '.doc-explorer-title-bar, .history-title-bar': { height: 'auto!important' }, '.graphiql-plugin': { '.docExplorerWrap': { minWidth: '273px !important', maxWidth: '100%' }, '.doc-explorer-title-bar .doc-explorer-rhs': { display: 'none' }, '.graphiql-explorer-root': { padding: '0 !important', '& > div:first-child': { overflow: 'auto !important' }, '.graphiql-operation-title-bar span input': { width: 'unset !important' } }, '.doc-explorer-contents': { overflowY: 'hidden !important' } } } }, React.createElement(GraphiQLComponent, { fetcher: graphQLFetcher, schema: schema, query: query, storage: storage, onEditQuery: onEditQuery, plugins: [explorer] }) ) ) ); } export default GraphiQL;