Add DevMode panel (trigger: CMD+'
) (#3333)
This commit is contained in:
parent
b31e4522a1
commit
c0b69e7c31
16 changed files with 982 additions and 29 deletions
|
@ -23,6 +23,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
||||||
- Add custom request headers to webhook form - #3107 by @2can
|
- Add custom request headers to webhook form - #3107 by @2can
|
||||||
- Allow subscription query for sync events - #3099 by @2can
|
- Allow subscription query for sync events - #3099 by @2can
|
||||||
- Simplify codegen configuration to generate the graphql schema - #2538 by @abumalick
|
- Simplify codegen configuration to generate the graphql schema - #2538 by @abumalick
|
||||||
|
- Add DevMode panel (trigger: CMD+') - #3333 by @zaiste
|
||||||
|
|
||||||
## 3.4
|
## 3.4
|
||||||
|
|
||||||
|
|
|
@ -7345,6 +7345,10 @@
|
||||||
"context": "stock exceeded dialog title",
|
"context": "stock exceeded dialog title",
|
||||||
"string": "Not enough stock"
|
"string": "Not enough stock"
|
||||||
},
|
},
|
||||||
|
"qa/dDe": {
|
||||||
|
"context": "GraphiQL Playground in the dev mode panel",
|
||||||
|
"string": "Playground"
|
||||||
|
},
|
||||||
"qbFKVI": {
|
"qbFKVI": {
|
||||||
"context": "notification, form submitted",
|
"context": "notification, form submitted",
|
||||||
"string": "Refund for order #{orderNumber} was granted"
|
"string": "Refund for order #{orderNumber} was granted"
|
||||||
|
|
|
@ -2,8 +2,11 @@ import useAppState from "@dashboard/hooks/useAppState";
|
||||||
import { LinearProgress } from "@material-ui/core";
|
import { LinearProgress } from "@material-ui/core";
|
||||||
import { useActionBar } from "@saleor/macaw-ui";
|
import { useActionBar } from "@saleor/macaw-ui";
|
||||||
import { Box } from "@saleor/macaw-ui/next";
|
import { Box } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import { DevModePanel } from "../DevModePanel/DevModePanel";
|
||||||
|
import { useDevModeContext } from "../DevModePanel/hooks";
|
||||||
|
import { useDevModeKeyTrigger } from "../DevModePanel/useDevModeKeyTrigger";
|
||||||
import Navigator from "../Navigator";
|
import Navigator from "../Navigator";
|
||||||
import { Sidebar } from "../Sidebar";
|
import { Sidebar } from "../Sidebar";
|
||||||
import { contentMaxWidth } from "./consts";
|
import { contentMaxWidth } from "./consts";
|
||||||
|
@ -18,10 +21,18 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { anchor: appActionAnchor } = useActionBar();
|
const { anchor: appActionAnchor } = useActionBar();
|
||||||
const [appState] = useAppState();
|
const [appState] = useAppState();
|
||||||
const [isNavigatorVisible, setNavigatorVisibility] = React.useState(false);
|
const [isNavigatorVisible, setNavigatorVisibility] = useState(false);
|
||||||
|
|
||||||
|
const { isDevModeVisible, setDevModeVisibility } = useDevModeContext();
|
||||||
|
|
||||||
|
useDevModeKeyTrigger(() => setDevModeVisibility(!isDevModeVisible));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<DevModePanel
|
||||||
|
isDevModeVisible={isDevModeVisible}
|
||||||
|
setDevModeVisibility={setDevModeVisibility}
|
||||||
|
/>
|
||||||
<Navigator
|
<Navigator
|
||||||
visible={isNavigatorVisible}
|
visible={isNavigatorVisible}
|
||||||
setVisibility={setNavigatorVisibility}
|
setVisibility={setNavigatorVisibility}
|
||||||
|
|
52
src/components/DevModePanel/DevModePanel.tsx
Normal file
52
src/components/DevModePanel/DevModePanel.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { createGraphiQLFetcher } from "@graphiql/toolkit";
|
||||||
|
import { Dialog, DialogContent } from "@material-ui/core";
|
||||||
|
import { DialogHeader } from "@saleor/macaw-ui";
|
||||||
|
import { createFetch } from "@saleor/sdk";
|
||||||
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import GraphiQL from "../GraphiQLPlain";
|
||||||
|
import { useDevModeContext } from "./hooks";
|
||||||
|
import { messages } from "./messages";
|
||||||
|
|
||||||
|
const authorizedFetch = createFetch();
|
||||||
|
|
||||||
|
interface DevModePanelProps {
|
||||||
|
isDevModeVisible: boolean;
|
||||||
|
setDevModeVisibility: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DevModePanel: React.FC<DevModePanelProps> = ({
|
||||||
|
isDevModeVisible,
|
||||||
|
setDevModeVisibility,
|
||||||
|
}) => {
|
||||||
|
const fetcher = createGraphiQLFetcher({
|
||||||
|
url: process.env.API_URI,
|
||||||
|
fetch: authorizedFetch,
|
||||||
|
});
|
||||||
|
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const { devModeContent, variables } = useDevModeContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
maxWidth="xl"
|
||||||
|
fullWidth
|
||||||
|
open={isDevModeVisible}
|
||||||
|
style={{ zIndex: 5 }}
|
||||||
|
PaperProps={{ style: { height: "100%" } }}
|
||||||
|
>
|
||||||
|
<DialogHeader onClose={() => setDevModeVisibility(false)}>
|
||||||
|
{intl.formatMessage(messages.title)}
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogContent style={{ padding: 0, margin: 1, overflowY: "auto" }}>
|
||||||
|
<GraphiQL
|
||||||
|
query={devModeContent}
|
||||||
|
variables={variables}
|
||||||
|
fetcher={fetcher}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
27
src/components/DevModePanel/DevModeProvider.tsx
Normal file
27
src/components/DevModePanel/DevModeProvider.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import { DevModeContext } from "./hooks";
|
||||||
|
|
||||||
|
export function DevModeProvider({ children }) {
|
||||||
|
// stringified variables (as key/value) passed along with the query
|
||||||
|
const [variables, setVariables] = useState("");
|
||||||
|
// stringified GraphQL query; queries can be constructed anywhere in the
|
||||||
|
// dashboard to be passed to the dev mode panel
|
||||||
|
const [devModeContent, setDevModeContent] = useState("");
|
||||||
|
const [isDevModeVisible, setDevModeVisibility] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DevModeContext.Provider
|
||||||
|
value={{
|
||||||
|
variables,
|
||||||
|
setVariables,
|
||||||
|
devModeContent,
|
||||||
|
setDevModeContent,
|
||||||
|
isDevModeVisible,
|
||||||
|
setDevModeVisibility,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DevModeContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
22
src/components/DevModePanel/hooks.ts
Normal file
22
src/components/DevModePanel/hooks.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { createContext, Dispatch, SetStateAction, useContext } from "react";
|
||||||
|
|
||||||
|
interface DevModeContextShape {
|
||||||
|
variables: string;
|
||||||
|
setVariables: Dispatch<SetStateAction<string>>;
|
||||||
|
isDevModeVisible: boolean;
|
||||||
|
setDevModeVisibility: Dispatch<SetStateAction<boolean>>;
|
||||||
|
devModeContent: string;
|
||||||
|
setDevModeContent: Dispatch<SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DevModeContext = createContext<null | DevModeContextShape>(null);
|
||||||
|
|
||||||
|
export const useDevModeContext = () => {
|
||||||
|
const context = useContext(DevModeContext);
|
||||||
|
|
||||||
|
if (context === null) {
|
||||||
|
throw new Error("you are outside of context");
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
0
src/components/DevModePanel/index.ts
Normal file
0
src/components/DevModePanel/index.ts
Normal file
9
src/components/DevModePanel/messages.ts
Normal file
9
src/components/DevModePanel/messages.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
export const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
id: "qa/dDe",
|
||||||
|
defaultMessage: "Playground",
|
||||||
|
description: "GraphiQL Playground in the dev mode panel",
|
||||||
|
},
|
||||||
|
});
|
14
src/components/DevModePanel/useDevModeKeyTrigger.ts
Normal file
14
src/components/DevModePanel/useDevModeKeyTrigger.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export const useDevModeKeyTrigger = (callback?: () => void) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = (event: KeyboardEvent) => {
|
||||||
|
if (event.metaKey && event.code === "Quote") {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("keydown", handler);
|
||||||
|
return () => document.removeEventListener("keydown", handler);
|
||||||
|
}, [callback]);
|
||||||
|
};
|
|
@ -279,10 +279,7 @@ export function GraphiQLInterface(props: GraphiQLInterfaceProps) {
|
||||||
aria-label="Query Editor"
|
aria-label="Query Editor"
|
||||||
style={{ borderBottom: 0 }}
|
style={{ borderBottom: 0 }}
|
||||||
>
|
>
|
||||||
<div
|
<div className="graphiql-query-editor-wrapper">
|
||||||
className="graphiql-query-editor-wrapper"
|
|
||||||
style={{ fontSize: "1.6rem" }}
|
|
||||||
>
|
|
||||||
<QueryEditor
|
<QueryEditor
|
||||||
editorTheme={props.editorTheme}
|
editorTheme={props.editorTheme}
|
||||||
keyMap={props.keyMap}
|
keyMap={props.keyMap}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
useTheme as useGraphiQLTheme,
|
useTheme as useGraphiQLTheme,
|
||||||
} from "@graphiql/react";
|
} from "@graphiql/react";
|
||||||
import { makeStyles, useTheme } from "@saleor/macaw-ui";
|
import { makeStyles, useTheme } from "@saleor/macaw-ui";
|
||||||
|
import { vars } from "@saleor/macaw-ui/next";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export const useStyles = makeStyles(
|
export const useStyles = makeStyles(
|
||||||
|
@ -54,15 +55,15 @@ export const useEditorStyles = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDashboardTheme = () => {
|
export const useDashboardTheme = () => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const rootStyle = {
|
const rootStyle = {
|
||||||
"--font-size-body": theme.typography?.body2.fontSize,
|
"--font-size-body": vars.fontSize.bodyMedium,
|
||||||
"--font-size-h2": theme.typography?.h3.fontSize,
|
"--font-size-h2": vars.fontSize.headingLarge,
|
||||||
"--font-size-h3": theme.typography?.h3.fontSize,
|
"--font-size-h3": vars.fontSize.headingMedium,
|
||||||
"--font-size-h4": theme.typography?.h4.fontSize,
|
"--font-size-h4": vars.fontSize.headingSmall,
|
||||||
"--font-size-hint": theme.typography?.caption.fontSize,
|
"--font-weight-regular": vars.fontWeight.bodyLarge,
|
||||||
"--font-size-inline-code": theme.typography?.caption.fontSize,
|
"--font-size-hint": vars.fontSize.bodyEmpLarge,
|
||||||
|
"--font-size-inline-code": vars.fontSize.bodySmall,
|
||||||
|
"--color-base": vars.colors.background.plain,
|
||||||
} as React.CSSProperties;
|
} as React.CSSProperties;
|
||||||
|
|
||||||
return { rootStyle };
|
return { rootStyle };
|
||||||
|
|
808
src/components/GraphiQLPlain/GraphiQL.tsx
Normal file
808
src/components/GraphiQLPlain/GraphiQL.tsx
Normal file
|
@ -0,0 +1,808 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2020 GraphQL Contributors.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
ChevronDownIcon,
|
||||||
|
ChevronUpIcon,
|
||||||
|
CopyIcon,
|
||||||
|
Dialog,
|
||||||
|
ExecuteButton,
|
||||||
|
GraphiQLProvider,
|
||||||
|
GraphiQLProviderProps,
|
||||||
|
HeaderEditor,
|
||||||
|
MergeIcon,
|
||||||
|
PlusIcon,
|
||||||
|
PrettifyIcon,
|
||||||
|
QueryEditor,
|
||||||
|
ReloadIcon,
|
||||||
|
ResponseEditor,
|
||||||
|
Spinner,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
ToolbarButton,
|
||||||
|
Tooltip,
|
||||||
|
UnStyledButton,
|
||||||
|
useCopyQuery,
|
||||||
|
useDragResize,
|
||||||
|
useEditorContext,
|
||||||
|
useExecutionContext,
|
||||||
|
UseHeaderEditorArgs,
|
||||||
|
useMergeQuery,
|
||||||
|
usePluginContext,
|
||||||
|
usePrettifyEditors,
|
||||||
|
UseQueryEditorArgs,
|
||||||
|
UseResponseEditorArgs,
|
||||||
|
useSchemaContext,
|
||||||
|
useStorageContext,
|
||||||
|
useTheme,
|
||||||
|
UseVariableEditorArgs,
|
||||||
|
VariableEditor,
|
||||||
|
WriteableEditorProps,
|
||||||
|
} from "@graphiql/react";
|
||||||
|
import React, {
|
||||||
|
ComponentType,
|
||||||
|
PropsWithChildren,
|
||||||
|
ReactNode,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useDashboardTheme,
|
||||||
|
useGraphiQLThemeSwitcher,
|
||||||
|
} from "../GraphiQL/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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API docs for this live here:
|
||||||
|
*
|
||||||
|
* https://graphiql-test.netlify.app/typedoc/modules/graphiql.html#graphiqlprops
|
||||||
|
*/
|
||||||
|
export type GraphiQLProps = Omit<GraphiQLProviderProps, "children"> &
|
||||||
|
GraphiQLInterfaceProps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The top-level React component for GraphiQL, intended to encompass the entire
|
||||||
|
* browser viewport.
|
||||||
|
*
|
||||||
|
* @see https://github.com/graphql/graphiql#usage
|
||||||
|
*/
|
||||||
|
|
||||||
|
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
|
||||||
|
showPersistHeadersSettings={shouldPersistHeaders !== false}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</GraphiQLProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
GraphiQL.Toolbar = GraphiQLToolbar;
|
||||||
|
GraphiQL.Footer = GraphiQLFooter;
|
||||||
|
|
||||||
|
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;
|
||||||
|
/**
|
||||||
|
* Set the default state for the editor tools.
|
||||||
|
* - `false` hides the editor tools
|
||||||
|
* - `true` shows the editor tools
|
||||||
|
* - `'variables'` specifically shows the variables editor
|
||||||
|
* - `'headers'` specifically shows the headers editor
|
||||||
|
* By default the editor tools are initially shown when at least one of the
|
||||||
|
* editors has contents.
|
||||||
|
*/
|
||||||
|
defaultEditorToolsVisibility?: boolean | "variables" | "headers";
|
||||||
|
/**
|
||||||
|
* Toggle if the headers editor should be shown inside the editor tools.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
isHeadersEditorEnabled?: boolean;
|
||||||
|
/**
|
||||||
|
* An object that allows configuration of the toolbar next to the query
|
||||||
|
* editor.
|
||||||
|
*/
|
||||||
|
toolbar?: GraphiQLToolbarConfig;
|
||||||
|
/**
|
||||||
|
* Indicates if settings for persisting headers should appear in the
|
||||||
|
* settings modal.
|
||||||
|
*/
|
||||||
|
showPersistHeadersSettings?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function GraphiQLInterface(props: GraphiQLInterfaceProps) {
|
||||||
|
const isHeadersEditorEnabled = props.isHeadersEditorEnabled ?? true;
|
||||||
|
|
||||||
|
const editorContext = useEditorContext({ nonNull: true });
|
||||||
|
const executionContext = useExecutionContext({ nonNull: true });
|
||||||
|
const schemaContext = useSchemaContext({ nonNull: true });
|
||||||
|
const storageContext = useStorageContext();
|
||||||
|
const pluginContext = usePluginContext();
|
||||||
|
|
||||||
|
const copy = useCopyQuery({ onCopyQuery: props.onCopyQuery });
|
||||||
|
const merge = useMergeQuery();
|
||||||
|
const prettify = usePrettifyEditors();
|
||||||
|
const { rootStyle } = useDashboardTheme();
|
||||||
|
|
||||||
|
useGraphiQLThemeSwitcher();
|
||||||
|
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
|
const PluginContent = pluginContext?.visiblePlugin?.content;
|
||||||
|
|
||||||
|
const pluginResize = useDragResize({
|
||||||
|
defaultSizeRelation: 1 / 3,
|
||||||
|
direction: "horizontal",
|
||||||
|
initiallyHidden: pluginContext?.visiblePlugin ? undefined : "first",
|
||||||
|
sizeThresholdSecond: 200,
|
||||||
|
storageKey: "docExplorerFlex",
|
||||||
|
});
|
||||||
|
const editorResize = useDragResize({
|
||||||
|
direction: "horizontal",
|
||||||
|
storageKey: "editorFlex",
|
||||||
|
});
|
||||||
|
const editorToolsResize = useDragResize({
|
||||||
|
defaultSizeRelation: 3,
|
||||||
|
direction: "vertical",
|
||||||
|
sizeThresholdSecond: 60,
|
||||||
|
storageKey: "secondaryEditorFlex",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [activeSecondaryEditor, setActiveSecondaryEditor] = useState<
|
||||||
|
"variables" | "headers"
|
||||||
|
>(() => {
|
||||||
|
if (
|
||||||
|
props.defaultEditorToolsVisibility === "variables" ||
|
||||||
|
props.defaultEditorToolsVisibility === "headers"
|
||||||
|
) {
|
||||||
|
return props.defaultEditorToolsVisibility;
|
||||||
|
}
|
||||||
|
return !editorContext.initialVariables &&
|
||||||
|
editorContext.initialHeaders &&
|
||||||
|
isHeadersEditorEnabled
|
||||||
|
? "headers"
|
||||||
|
: "variables";
|
||||||
|
});
|
||||||
|
const [showDialog, setShowDialog] = useState<
|
||||||
|
"settings" | "short-keys" | null
|
||||||
|
>(null);
|
||||||
|
const [clearStorageStatus, setClearStorageStatus] = useState<
|
||||||
|
"success" | "error" | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
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={() => merge()}
|
||||||
|
label="Merge fragments into query (Shift-Ctrl-M)"
|
||||||
|
>
|
||||||
|
<MergeIcon 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 footer = children.find(child =>
|
||||||
|
isChildComponentType(child, GraphiQL.Footer),
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClickReference = () => {
|
||||||
|
if (pluginResize.hiddenElement === "first") {
|
||||||
|
pluginResize.setHiddenElement(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const modifier =
|
||||||
|
window.navigator.platform.toLowerCase().indexOf("mac") === 0 ? (
|
||||||
|
<code className="graphiql-key">Cmd</code>
|
||||||
|
) : (
|
||||||
|
<code className="graphiql-key">Ctrl</code>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-testid="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">
|
||||||
|
<Tooltip label="Re-fetch GraphQL schema">
|
||||||
|
<UnStyledButton
|
||||||
|
type="button"
|
||||||
|
disabled={schemaContext.isFetching}
|
||||||
|
onClick={() => schemaContext.introspect()}
|
||||||
|
aria-label="Re-fetch GraphQL schema"
|
||||||
|
>
|
||||||
|
<ReloadIcon
|
||||||
|
className={schemaContext.isFetching ? "graphiql-spin" : ""}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</UnStyledButton>
|
||||||
|
</Tooltip>
|
||||||
|
</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="graphiql-plugin">
|
||||||
|
{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 className="graphiql-session-header">
|
||||||
|
<Tabs aria-label="Select active operation">
|
||||||
|
{editorContext.tabs.length > 1 ? (
|
||||||
|
<>
|
||||||
|
{editorContext.tabs.map((tab, index) => (
|
||||||
|
<Tab
|
||||||
|
key={tab.id}
|
||||||
|
isActive={index === editorContext.activeTabIndex}
|
||||||
|
>
|
||||||
|
<Tab.Button
|
||||||
|
aria-controls="graphiql-session"
|
||||||
|
id={`graphiql-session-tab-${index}`}
|
||||||
|
onClick={() => {
|
||||||
|
executionContext.stop();
|
||||||
|
editorContext.changeTab(index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.title}
|
||||||
|
</Tab.Button>
|
||||||
|
<Tab.Close
|
||||||
|
onClick={() => {
|
||||||
|
if (editorContext.activeTabIndex === index) {
|
||||||
|
executionContext.stop();
|
||||||
|
}
|
||||||
|
editorContext.closeTab(index);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
|
))}
|
||||||
|
<div>
|
||||||
|
<Tooltip label="Add tab">
|
||||||
|
<UnStyledButton
|
||||||
|
type="button"
|
||||||
|
className="graphiql-tab-add"
|
||||||
|
onClick={() => editorContext.addTab()}
|
||||||
|
aria-label="Add tab"
|
||||||
|
>
|
||||||
|
<PlusIcon aria-hidden="true" />
|
||||||
|
</UnStyledButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Tabs>
|
||||||
|
<div className="graphiql-session-header-right">
|
||||||
|
{editorContext.tabs.length === 1 ? (
|
||||||
|
<div className="graphiql-add-tab-wrapper">
|
||||||
|
<Tooltip label="Add tab">
|
||||||
|
<UnStyledButton
|
||||||
|
type="button"
|
||||||
|
className="graphiql-tab-add"
|
||||||
|
onClick={() => editorContext.addTab()}
|
||||||
|
aria-label="Add tab"
|
||||||
|
>
|
||||||
|
<PlusIcon aria-hidden="true" />
|
||||||
|
</UnStyledButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
id="graphiql-session"
|
||||||
|
className="graphiql-session"
|
||||||
|
aria-labelledby={`graphiql-session-tab-${editorContext.activeTabIndex}`}
|
||||||
|
>
|
||||||
|
<div ref={editorResize.firstRef}>
|
||||||
|
<div
|
||||||
|
className={`graphiql-editors${
|
||||||
|
editorContext.tabs.length === 1 ? " full-height" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div ref={editorToolsResize.firstRef}>
|
||||||
|
<section
|
||||||
|
className="graphiql-query-editor"
|
||||||
|
aria-label="Query Editor"
|
||||||
|
>
|
||||||
|
<div className="graphiql-query-editor-wrapper">
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<ExecuteButton />
|
||||||
|
{toolbar}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div ref={editorToolsResize.dragBarRef}>
|
||||||
|
<div className="graphiql-editor-tools">
|
||||||
|
<div className="graphiql-editor-tools-tabs">
|
||||||
|
<UnStyledButton
|
||||||
|
type="button"
|
||||||
|
className={
|
||||||
|
activeSecondaryEditor === "variables" &&
|
||||||
|
editorToolsResize.hiddenElement !== "second"
|
||||||
|
? "active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (editorToolsResize.hiddenElement === "second") {
|
||||||
|
editorToolsResize.setHiddenElement(null);
|
||||||
|
}
|
||||||
|
setActiveSecondaryEditor("variables");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Variables
|
||||||
|
</UnStyledButton>
|
||||||
|
{isHeadersEditorEnabled ? (
|
||||||
|
<UnStyledButton
|
||||||
|
type="button"
|
||||||
|
className={
|
||||||
|
activeSecondaryEditor === "headers" &&
|
||||||
|
editorToolsResize.hiddenElement !== "second"
|
||||||
|
? "active"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
editorToolsResize.hiddenElement === "second"
|
||||||
|
) {
|
||||||
|
editorToolsResize.setHiddenElement(null);
|
||||||
|
}
|
||||||
|
setActiveSecondaryEditor("headers");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Headers
|
||||||
|
</UnStyledButton>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
label={
|
||||||
|
editorToolsResize.hiddenElement === "second"
|
||||||
|
? "Show editor tools"
|
||||||
|
: "Hide editor tools"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<UnStyledButton
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
editorToolsResize.setHiddenElement(
|
||||||
|
editorToolsResize.hiddenElement === "second"
|
||||||
|
? null
|
||||||
|
: "second",
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
aria-label={
|
||||||
|
editorToolsResize.hiddenElement === "second"
|
||||||
|
? "Show editor tools"
|
||||||
|
: "Hide editor tools"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{editorToolsResize.hiddenElement === "second" ? (
|
||||||
|
<ChevronUpIcon
|
||||||
|
className="graphiql-chevron-icon"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ChevronDownIcon
|
||||||
|
className="graphiql-chevron-icon"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</UnStyledButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ref={editorToolsResize.secondRef}>
|
||||||
|
<section
|
||||||
|
className="graphiql-editor-tool"
|
||||||
|
aria-label={
|
||||||
|
activeSecondaryEditor === "variables"
|
||||||
|
? "Variables"
|
||||||
|
: "Headers"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<VariableEditor
|
||||||
|
editorTheme={props.editorTheme}
|
||||||
|
isHidden={activeSecondaryEditor !== "variables"}
|
||||||
|
keyMap={props.keyMap}
|
||||||
|
onEdit={props.onEditVariables}
|
||||||
|
onClickReference={onClickReference}
|
||||||
|
readOnly={props.readOnly}
|
||||||
|
/>
|
||||||
|
{isHeadersEditorEnabled && (
|
||||||
|
<HeaderEditor
|
||||||
|
editorTheme={props.editorTheme}
|
||||||
|
isHidden={activeSecondaryEditor !== "headers"}
|
||||||
|
keyMap={props.keyMap}
|
||||||
|
onEdit={props.onEditHeaders}
|
||||||
|
readOnly={props.readOnly}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ref={editorResize.dragBarRef}>
|
||||||
|
<div className="graphiql-horizontal-drag-bar" />
|
||||||
|
</div>
|
||||||
|
<div ref={editorResize.secondRef}>
|
||||||
|
<div className="graphiql-response">
|
||||||
|
{executionContext.isFetching ? <Spinner /> : null}
|
||||||
|
<ResponseEditor
|
||||||
|
editorTheme={props.editorTheme}
|
||||||
|
responseTooltip={props.responseTooltip}
|
||||||
|
keyMap={props.keyMap}
|
||||||
|
/>
|
||||||
|
{footer}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Dialog
|
||||||
|
isOpen={showDialog === "short-keys"}
|
||||||
|
onDismiss={() => setShowDialog(null)}
|
||||||
|
>
|
||||||
|
<div className="graphiql-dialog-header">
|
||||||
|
<div className="graphiql-dialog-title">Short Keys</div>
|
||||||
|
<Dialog.Close onClick={() => setShowDialog(null)} />
|
||||||
|
</div>
|
||||||
|
<div className="graphiql-dialog-section">
|
||||||
|
<div>
|
||||||
|
<table className="graphiql-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Short key</th>
|
||||||
|
<th>Function</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{modifier}
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">F</code>
|
||||||
|
</td>
|
||||||
|
<td>Search in editor</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{modifier}
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">K</code>
|
||||||
|
</td>
|
||||||
|
<td>Search in documentation</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{modifier}
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">Enter</code>
|
||||||
|
</td>
|
||||||
|
<td>Execute query</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code className="graphiql-key">Ctrl</code>
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">Shift</code>
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">P</code>
|
||||||
|
</td>
|
||||||
|
<td>Prettify editors</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code className="graphiql-key">Ctrl</code>
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">Shift</code>
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">M</code>
|
||||||
|
</td>
|
||||||
|
<td>Merge fragments definitions into operation definition</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code className="graphiql-key">Ctrl</code>
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">Shift</code>
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">C</code>
|
||||||
|
</td>
|
||||||
|
<td>Copy query</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code className="graphiql-key">Ctrl</code>
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">Shift</code>
|
||||||
|
{" + "}
|
||||||
|
<code className="graphiql-key">R</code>
|
||||||
|
</td>
|
||||||
|
<td>Re-fetch schema using introspection</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
The editors use{" "}
|
||||||
|
<a
|
||||||
|
href="https://codemirror.net/5/doc/manual.html#keymaps"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
CodeMirror Key Maps
|
||||||
|
</a>{" "}
|
||||||
|
that add more short keys. This instance of Graph<em>i</em>QL uses{" "}
|
||||||
|
<code>{props.keyMap || "sublime"}</code>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog
|
||||||
|
isOpen={showDialog === "settings"}
|
||||||
|
onDismiss={() => {
|
||||||
|
setShowDialog(null);
|
||||||
|
setClearStorageStatus(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="graphiql-dialog-header">
|
||||||
|
<div className="graphiql-dialog-title">Settings</div>
|
||||||
|
<Dialog.Close
|
||||||
|
onClick={() => {
|
||||||
|
setShowDialog(null);
|
||||||
|
setClearStorageStatus(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="graphiql-dialog-section">
|
||||||
|
<div>
|
||||||
|
<div className="graphiql-dialog-section-title">Theme</div>
|
||||||
|
<div className="graphiql-dialog-section-caption">
|
||||||
|
Adjust how the interface looks like.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className={theme === null ? "active" : ""}
|
||||||
|
onClick={() => setTheme(null)}
|
||||||
|
>
|
||||||
|
System
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className={theme === "light" ? "active" : ""}
|
||||||
|
onClick={() => setTheme("light")}
|
||||||
|
>
|
||||||
|
Light
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className={theme === "dark" ? "active" : ""}
|
||||||
|
onClick={() => setTheme("dark")}
|
||||||
|
>
|
||||||
|
Dark
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{storageContext ? (
|
||||||
|
<div className="graphiql-dialog-section">
|
||||||
|
<div>
|
||||||
|
<div className="graphiql-dialog-section-title">Clear storage</div>
|
||||||
|
<div className="graphiql-dialog-section-caption">
|
||||||
|
Remove all locally stored data and start fresh.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
state={clearStorageStatus || undefined}
|
||||||
|
disabled={clearStorageStatus === "success"}
|
||||||
|
onClick={() => {
|
||||||
|
try {
|
||||||
|
setClearStorageStatus("success");
|
||||||
|
} catch {
|
||||||
|
setClearStorageStatus("error");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{clearStorageStatus === "success"
|
||||||
|
? "Cleared data"
|
||||||
|
: clearStorageStatus === "error"
|
||||||
|
? "Failed"
|
||||||
|
: "Clear data"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the UI by providing this Component as a child of GraphiQL.
|
||||||
|
function GraphiQLToolbar<TProps>(props: PropsWithChildren<TProps>) {
|
||||||
|
return <>{props.children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphiQLToolbar.displayName = "GraphiQLToolbar";
|
||||||
|
|
||||||
|
// Configure the UI by providing this Component as a child of GraphiQL.
|
||||||
|
function GraphiQLFooter<TProps>(props: PropsWithChildren<TProps>) {
|
||||||
|
return <div className="graphiql-footer">{props.children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphiQLFooter.displayName = "GraphiQLFooter";
|
||||||
|
|
||||||
|
// Determines if the React child is of the same type of the provided React component
|
||||||
|
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;
|
2
src/components/GraphiQLPlain/index.ts
Normal file
2
src/components/GraphiQLPlain/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from "./GraphiQL";
|
||||||
|
export * from "./GraphiQL";
|
|
@ -33,6 +33,7 @@ import useAppChannel, {
|
||||||
AppChannelProvider,
|
AppChannelProvider,
|
||||||
} from "./components/AppLayout/AppChannelContext";
|
} from "./components/AppLayout/AppChannelContext";
|
||||||
import { DateProvider } from "./components/Date";
|
import { DateProvider } from "./components/Date";
|
||||||
|
import { DevModeProvider } from "./components/DevModePanel/DevModeProvider";
|
||||||
import ErrorPage from "./components/ErrorPage";
|
import ErrorPage from "./components/ErrorPage";
|
||||||
import ExitFormDialogProvider from "./components/Form/ExitFormDialogProvider";
|
import ExitFormDialogProvider from "./components/Form/ExitFormDialogProvider";
|
||||||
import { LocaleProvider } from "./components/Locale";
|
import { LocaleProvider } from "./components/Locale";
|
||||||
|
@ -120,7 +121,9 @@ const App: React.FC = () => (
|
||||||
<AppChannelProvider>
|
<AppChannelProvider>
|
||||||
<ExternalAppProvider>
|
<ExternalAppProvider>
|
||||||
<ExitFormDialogProvider>
|
<ExitFormDialogProvider>
|
||||||
<Routes />
|
<DevModeProvider>
|
||||||
|
<Routes />
|
||||||
|
</DevModeProvider>
|
||||||
</ExitFormDialogProvider>
|
</ExitFormDialogProvider>
|
||||||
</ExternalAppProvider>
|
</ExternalAppProvider>
|
||||||
</AppChannelProvider>
|
</AppChannelProvider>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import CardMenu from "@dashboard/components/CardMenu";
|
import CardMenu from "@dashboard/components/CardMenu";
|
||||||
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
import { CardSpacer } from "@dashboard/components/CardSpacer";
|
||||||
|
import { useDevModeContext } from "@dashboard/components/DevModePanel/hooks";
|
||||||
import Form from "@dashboard/components/Form";
|
import Form from "@dashboard/components/Form";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata, { MetadataFormData } from "@dashboard/components/Metadata";
|
import Metadata, { MetadataFormData } from "@dashboard/components/Metadata";
|
||||||
|
@ -22,7 +23,6 @@ import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
import { defaultGraphiQLQuery } from "@dashboard/orders/queries";
|
import { defaultGraphiQLQuery } from "@dashboard/orders/queries";
|
||||||
import { OrderErrorFragment, OrderSharedType } from "@dashboard/orders/types";
|
import { OrderErrorFragment, OrderSharedType } from "@dashboard/orders/types";
|
||||||
import { orderListUrl } from "@dashboard/orders/urls";
|
import { orderListUrl } from "@dashboard/orders/urls";
|
||||||
import { playgroundOpenHandler } from "@dashboard/utils/graphql";
|
|
||||||
import { mapMetadataItemToInput } from "@dashboard/utils/maps";
|
import { mapMetadataItemToInput } from "@dashboard/utils/maps";
|
||||||
import useMetadataChangeTrigger from "@dashboard/utils/metadata/useMetadataChangeTrigger";
|
import useMetadataChangeTrigger from "@dashboard/utils/metadata/useMetadataChangeTrigger";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
|
@ -196,12 +196,13 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
||||||
order?.id,
|
order?.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const openPlaygroundURL = playgroundOpenHandler({
|
const context = useDevModeContext();
|
||||||
query: defaultGraphiQLQuery,
|
|
||||||
headers: "",
|
const openPlaygroundURL = () => {
|
||||||
operationName: "",
|
context.setDevModeContent(defaultGraphiQLQuery);
|
||||||
variables: `{ "id": "${order?.id}" }`,
|
context.setVariables(`{ "id": "${order?.id}" }`);
|
||||||
});
|
context.setDevModeVisibility(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form confirmLeave initial={initial} onSubmit={handleSubmit}>
|
<Form confirmLeave initial={initial} onSubmit={handleSubmit}>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import Attributes, { AttributeInput } from "@dashboard/components/Attributes";
|
||||||
import CardMenu from "@dashboard/components/CardMenu";
|
import CardMenu from "@dashboard/components/CardMenu";
|
||||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||||
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
import ChannelsAvailabilityCard from "@dashboard/components/ChannelsAvailabilityCard";
|
||||||
|
import { useDevModeContext } from "@dashboard/components/DevModePanel/hooks";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Metadata from "@dashboard/components/Metadata/Metadata";
|
import Metadata from "@dashboard/components/Metadata/Metadata";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
|
@ -46,7 +47,6 @@ import { productImageUrl, productListUrl } from "@dashboard/products/urls";
|
||||||
import { ProductVariantListError } from "@dashboard/products/views/ProductUpdate/handlers/errors";
|
import { ProductVariantListError } from "@dashboard/products/views/ProductUpdate/handlers/errors";
|
||||||
import { UseProductUpdateHandlerError } from "@dashboard/products/views/ProductUpdate/handlers/useProductUpdateHandler";
|
import { UseProductUpdateHandlerError } from "@dashboard/products/views/ProductUpdate/handlers/useProductUpdateHandler";
|
||||||
import { FetchMoreProps, RelayToFlat } from "@dashboard/types";
|
import { FetchMoreProps, RelayToFlat } from "@dashboard/types";
|
||||||
import { playgroundOpenHandler } from "@dashboard/utils/graphql";
|
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
@ -242,12 +242,13 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
productId,
|
productId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const openPlaygroundURL = playgroundOpenHandler({
|
const context = useDevModeContext();
|
||||||
query: defaultGraphiQLQuery,
|
|
||||||
headers: "",
|
const openPlaygroundURL = () => {
|
||||||
operationName: "",
|
context.setDevModeContent(defaultGraphiQLQuery);
|
||||||
variables: `{ "id": "${product?.id}" }`,
|
context.setVariables(`{ "id": "${product?.id}" }`);
|
||||||
});
|
context.setDevModeVisibility(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProductUpdateForm
|
<ProductUpdateForm
|
||||||
|
|
Loading…
Reference in a new issue