
Co-authored-by: Bartłomiej Wiaduch <tukan2can@gmail.com> Co-authored-by: timur <timuric@gmail.com> Co-authored-by: Michał Droń <dron.official@yahoo.com>
298 lines
8.9 KiB
TypeScript
298 lines
8.9 KiB
TypeScript
import {
|
|
CopyIcon,
|
|
GraphiQLProvider,
|
|
GraphiQLProviderProps,
|
|
PrettifyIcon,
|
|
QueryEditor,
|
|
ToolbarButton,
|
|
Tooltip,
|
|
UnStyledButton,
|
|
useCopyQuery,
|
|
useEditorContext,
|
|
UseHeaderEditorArgs,
|
|
usePluginContext,
|
|
usePrettifyEditors,
|
|
UseQueryEditorArgs,
|
|
UseResponseEditorArgs,
|
|
UseVariableEditorArgs,
|
|
WriteableEditorProps,
|
|
} from "@graphiql/react";
|
|
import clsx from "clsx";
|
|
import React, { ComponentType, PropsWithChildren, ReactNode } from "react";
|
|
|
|
import {
|
|
useDashboardTheme,
|
|
useEditorStyles,
|
|
useGraphiQLThemeSwitcher,
|
|
useStyles,
|
|
} from "./styles";
|
|
|
|
export interface GraphiQLToolbarConfig {
|
|
/**
|
|
* This content will be rendered after the built-in buttons of the toolbar.
|
|
* Note that this will not apply if you provide a completely custom toolbar
|
|
* (by passing `GraphiQL.Toolbar` as child to the `GraphiQL` component).
|
|
*/
|
|
additionalContent?: React.ReactNode;
|
|
}
|
|
|
|
export type GraphiQLProps = Omit<GraphiQLProviderProps, "children"> &
|
|
GraphiQLInterfaceProps;
|
|
|
|
export function GraphiQL({
|
|
dangerouslyAssumeSchemaIsValid,
|
|
defaultQuery,
|
|
defaultTabs,
|
|
externalFragments,
|
|
fetcher,
|
|
getDefaultFieldNames,
|
|
headers,
|
|
initialTabs,
|
|
inputValueDeprecation,
|
|
introspectionQueryName,
|
|
maxHistoryLength,
|
|
onEditOperationName,
|
|
onSchemaChange,
|
|
onTabChange,
|
|
onTogglePluginVisibility,
|
|
operationName,
|
|
plugins,
|
|
query,
|
|
response,
|
|
schema,
|
|
schemaDescription,
|
|
shouldPersistHeaders,
|
|
storage,
|
|
validationRules,
|
|
variables,
|
|
visiblePlugin,
|
|
defaultHeaders,
|
|
...props
|
|
}: GraphiQLProps) {
|
|
// Ensure props are correct
|
|
if (typeof fetcher !== "function") {
|
|
throw new TypeError(
|
|
"The `GraphiQL` component requires a `fetcher` function to be passed as prop.",
|
|
);
|
|
}
|
|
|
|
return (
|
|
<GraphiQLProvider
|
|
getDefaultFieldNames={getDefaultFieldNames}
|
|
dangerouslyAssumeSchemaIsValid={dangerouslyAssumeSchemaIsValid}
|
|
defaultQuery={defaultQuery}
|
|
defaultHeaders={defaultHeaders}
|
|
defaultTabs={defaultTabs}
|
|
externalFragments={externalFragments}
|
|
fetcher={fetcher}
|
|
headers={headers}
|
|
initialTabs={initialTabs}
|
|
inputValueDeprecation={inputValueDeprecation}
|
|
introspectionQueryName={introspectionQueryName}
|
|
maxHistoryLength={maxHistoryLength}
|
|
onEditOperationName={onEditOperationName}
|
|
onSchemaChange={onSchemaChange}
|
|
onTabChange={onTabChange}
|
|
onTogglePluginVisibility={onTogglePluginVisibility}
|
|
plugins={plugins}
|
|
visiblePlugin={visiblePlugin}
|
|
operationName={operationName}
|
|
query={query}
|
|
response={response}
|
|
schema={schema}
|
|
schemaDescription={schemaDescription}
|
|
shouldPersistHeaders={shouldPersistHeaders}
|
|
storage={storage}
|
|
validationRules={validationRules}
|
|
variables={variables}
|
|
>
|
|
<GraphiQLInterface {...props} />
|
|
</GraphiQLProvider>
|
|
);
|
|
}
|
|
// Export main windows/panes to be used separately if desired.
|
|
GraphiQL.Toolbar = GraphiQLToolbar;
|
|
|
|
type AddSuffix<Obj extends Record<string, any>, Suffix extends string> = {
|
|
[Key in keyof Obj as `${string & Key}${Suffix}`]: Obj[Key];
|
|
};
|
|
|
|
export type GraphiQLInterfaceProps = WriteableEditorProps &
|
|
AddSuffix<Pick<UseQueryEditorArgs, "onEdit">, "Query"> &
|
|
Pick<UseQueryEditorArgs, "onCopyQuery"> &
|
|
AddSuffix<Pick<UseVariableEditorArgs, "onEdit">, "Variables"> &
|
|
AddSuffix<Pick<UseHeaderEditorArgs, "onEdit">, "Headers"> &
|
|
Pick<UseResponseEditorArgs, "responseTooltip"> & {
|
|
children?: ReactNode;
|
|
defaultEditorToolsVisibility?: boolean | "variables" | "headers";
|
|
isHeadersEditorEnabled?: boolean;
|
|
toolbar?: GraphiQLToolbarConfig;
|
|
};
|
|
|
|
export function GraphiQLInterface(props: GraphiQLInterfaceProps) {
|
|
const editorContext = useEditorContext({ nonNull: true });
|
|
const pluginContext = usePluginContext();
|
|
|
|
const classes = useStyles();
|
|
const { pluginResize, editorResize, editorToolsResize } = useEditorStyles();
|
|
|
|
const copy = useCopyQuery({ onCopyQuery: props.onCopyQuery });
|
|
const prettify = usePrettifyEditors();
|
|
const { rootStyle } = useDashboardTheme();
|
|
|
|
useGraphiQLThemeSwitcher();
|
|
|
|
const PluginContent = pluginContext?.visiblePlugin?.content;
|
|
|
|
const children = React.Children.toArray(props.children);
|
|
|
|
const toolbar = children.find(child =>
|
|
isChildComponentType(child, GraphiQL.Toolbar),
|
|
) || (
|
|
<>
|
|
<ToolbarButton
|
|
onClick={() => prettify()}
|
|
label="Prettify query (Shift-Ctrl-P)"
|
|
>
|
|
<PrettifyIcon className="graphiql-toolbar-icon" aria-hidden="true" />
|
|
</ToolbarButton>
|
|
<ToolbarButton onClick={() => copy()} label="Copy query (Shift-Ctrl-C)">
|
|
<CopyIcon className="graphiql-toolbar-icon" aria-hidden="true" />
|
|
</ToolbarButton>
|
|
{props.toolbar?.additionalContent || null}
|
|
</>
|
|
);
|
|
|
|
const onClickReference = () => {
|
|
if (pluginResize.hiddenElement === "first") {
|
|
pluginResize.setHiddenElement(null);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
data-test-id="graphiql-container"
|
|
className="graphiql-container"
|
|
style={rootStyle}
|
|
>
|
|
<div className="graphiql-sidebar">
|
|
<div className="graphiql-sidebar-section">
|
|
{pluginContext?.plugins.map(plugin => {
|
|
const isVisible = plugin === pluginContext.visiblePlugin;
|
|
const label = `${isVisible ? "Hide" : "Show"} ${plugin.title}`;
|
|
const Icon = plugin.icon;
|
|
return (
|
|
<Tooltip key={plugin.title} label={label}>
|
|
<UnStyledButton
|
|
type="button"
|
|
className={isVisible ? "active" : ""}
|
|
onClick={() => {
|
|
if (isVisible) {
|
|
pluginContext.setVisiblePlugin(null);
|
|
pluginResize.setHiddenElement("first");
|
|
} else {
|
|
pluginContext.setVisiblePlugin(plugin);
|
|
pluginResize.setHiddenElement(null);
|
|
}
|
|
}}
|
|
aria-label={label}
|
|
>
|
|
<Icon aria-hidden="true" />
|
|
</UnStyledButton>
|
|
</Tooltip>
|
|
);
|
|
})}
|
|
</div>
|
|
<div className="graphiql-sidebar-section"></div>
|
|
</div>
|
|
<div className="graphiql-main">
|
|
<div
|
|
ref={pluginResize.firstRef}
|
|
style={{
|
|
// Make sure the container shrinks when containing long
|
|
// non-breaking texts
|
|
minWidth: "200px",
|
|
}}
|
|
>
|
|
<div className={clsx("graphiql-plugin", classes.scrollable)}>
|
|
{PluginContent ? <PluginContent /> : null}
|
|
</div>
|
|
</div>
|
|
<div ref={pluginResize.dragBarRef}>
|
|
{pluginContext?.visiblePlugin ? (
|
|
<div className="graphiql-horizontal-drag-bar" />
|
|
) : null}
|
|
</div>
|
|
<div ref={pluginResize.secondRef} style={{ minWidth: 0 }}>
|
|
<div className="graphiql-sessions">
|
|
<div
|
|
role="tabpanel"
|
|
id="graphiql-session"
|
|
className="graphiql-session"
|
|
style={{ padding: "2rem 0 0 0" }}
|
|
aria-labelledby={`graphiql-session-tab-${editorContext.activeTabIndex}`}
|
|
>
|
|
<div ref={editorResize.firstRef}>
|
|
<div
|
|
className="graphiql-editors full-height"
|
|
style={{ boxShadow: "none" }}
|
|
>
|
|
<div ref={editorToolsResize.firstRef}>
|
|
<section
|
|
className="graphiql-query-editor"
|
|
aria-label="Query Editor"
|
|
style={{ borderBottom: 0 }}
|
|
>
|
|
<div
|
|
className="graphiql-query-editor-wrapper"
|
|
style={{ fontSize: "1.6rem" }}
|
|
>
|
|
<QueryEditor
|
|
editorTheme={props.editorTheme}
|
|
keyMap={props.keyMap}
|
|
onClickReference={onClickReference}
|
|
onCopyQuery={props.onCopyQuery}
|
|
onEdit={props.onEditQuery}
|
|
readOnly={props.readOnly}
|
|
/>
|
|
</div>
|
|
<div
|
|
className="graphiql-toolbar"
|
|
role="toolbar"
|
|
aria-label="Editor Commands"
|
|
>
|
|
{toolbar}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function GraphiQLToolbar<TProps>(props: PropsWithChildren<TProps>) {
|
|
return <>{props.children}</>;
|
|
}
|
|
|
|
GraphiQLToolbar.displayName = "GraphiQLToolbar";
|
|
|
|
function isChildComponentType<T extends ComponentType>(
|
|
child: any,
|
|
component: T,
|
|
): child is T {
|
|
if (
|
|
child?.type?.displayName &&
|
|
child.type.displayName === component.displayName
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return child.type === component;
|
|
}
|
|
|
|
export default GraphiQL;
|