diff options
Diffstat (limited to 'src/graphiql')
| -rwxr-xr-x | src/graphiql/markup.ts | 309 | ||||
| -rwxr-xr-x | src/graphiql/render.ts | 214 |
2 files changed, 523 insertions, 0 deletions
diff --git a/src/graphiql/markup.ts b/src/graphiql/markup.ts new file mode 100755 index 0000000..c000f92 --- /dev/null +++ b/src/graphiql/markup.ts @@ -0,0 +1,309 @@ + + + +/// export + +export const getLoadingMarkup = () => ({ + container: ` + <style> + @keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes fadeOut { + from { + opacity: 1; + transform: translateY(0); + } + + to { + opacity: 0; + transform: translateY(-10px); + } + } + + .fadeOut { + animation: fadeOut 0.5s ease-out forwards; + } + + #loading-wrapper { + width: 100vw; height: 100vh; + + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + position: absolute; + } + + .logo { + width: 75px; height: 75px; + + animation: fadeIn 0.5s ease-out forwards; + margin-bottom: 20px; + opacity: 0; + } + + .text { + animation: fadeIn 0.5s ease-out forwards; + color: rgba(255, 255, 255, 0.6); + font-size: 32px; + font-weight: 200; + opacity: 0; + text-align: center; + } + + .text-inner { + font-weight: 400; + } + </style> + + <div id="loading-wrapper"> + <svg class="logo" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"> + <title>GraphQL Playground Logo</title> + + <style> + @keyframes appearIn { + from { + opacity: 0; + transform: translateY(0px); + } + + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes scaleIn { + from { + transform: scale(0); + } + + to { + transform: scale(1); + } + } + + @keyframes innerDrawIn { + 0% { + stroke-dashoffset: 70; + } + + 50% { + stroke-dashoffset: 140; + } + + 100% { + stroke-dashoffset: 210; + } + } + + @keyframes outerDrawIn { + 0% { + stroke-dashoffset: 76; + } + + 100% { + stroke-dashoffset: 152; + } + } + + .circle-top, + .circle-top-right, + .circle-bottom-right, + .circle-bottom, + .circle-bottom-left, + .circle-top-left { + fill: white; + } + + .circle-top { + animation: scaleIn 0.25s linear forwards 0.2222222222222222s; + transform: scale(0); + transform-origin: 64px 28px; + } + + .circle-top-right { + animation: scaleIn 0.25s linear forwards 0.4222222222222222s; + transform: scale(0); + transform-origin: 95.98500061035156px 46.510000228881836px; + } + + .circle-bottom-right { + animation: scaleIn 0.25s linear forwards 0.6222222222222222s; + transform: scale(0); + transform-origin: 95.97162628173828px 83.4900016784668px; + } + + .circle-bottom { + animation: scaleIn 0.25s linear forwards 0.8222222222222223s; + transform: scale(0); + transform-origin: 64px 101.97999572753906px; + } + + .circle-bottom-left { + animation: scaleIn 0.25s linear forwards 1.0222222222222221s; + transform: scale(0); + transform-origin: 32.03982162475586px 83.4900016784668px; + } + + .circle-top-left { + animation: scaleIn 0.25s linear forwards 1.2222222222222223s; + transform: scale(0); + transform-origin: 32.033552169799805px 46.510000228881836px; + } + + .octoline-top-right, + .octoline-right, + .octoline-bottom-right, + .octoline-bottom-left, + .octoline-left, + .octoline-top-left { + stroke: white; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 4; + } + + .octoline-top-right { + animation: outerDrawIn 0.5s ease-out forwards 0.3333333333333333s, appearIn 0.1s ease-out forwards 0.3333333333333333s; + animation-iteration-count: 1, 1; + opacity: 0; + stroke-dasharray: 76; + } + + .octoline-right { + animation: outerDrawIn 0.5s ease-out forwards 0.5333333333333333s, appearIn 0.1s ease-out forwards 0.5333333333333333s; + animation-iteration-count: 1, 1; + opacity: 0; + stroke-dasharray: 76; + } + + .octoline-bottom-right { + animation: outerDrawIn 0.5s ease-out forwards 0.7333333333333334s, appearIn 0.1s ease-out forwards 0.7333333333333334s; + animation-iteration-count: 1, 1; + opacity: 0; + stroke-dasharray: 76; + } + + .octoline-bottom-left { + animation: outerDrawIn 0.5s ease-out forwards 0.9333333333333333s, appearIn 0.1s ease-out forwards 0.9333333333333333s; + animation-iteration-count: 1, 1; + opacity: 0; + stroke-dasharray: 76; + } + + .octoline-left { + animation: outerDrawIn 0.5s ease-out forwards 1.1333333333333333s, appearIn 0.1s ease-out forwards 1.1333333333333333s; + animation-iteration-count: 1, 1; + opacity: 0; + stroke-dasharray: 76; + } + + .octoline-top-left { + animation: outerDrawIn 0.5s ease-out forwards 1.3333333333333333s, appearIn 0.1s ease-out forwards 1.3333333333333333s; + animation-iteration-count: 1, 1; + opacity: 0; + stroke-dasharray: 76; + } + + .triangle-bottom, + .triangle-left, + .triangle-right { + stroke: white; + stroke-linecap: round; + stroke-width: 4; + } + + .triangle-bottom { + animation: innerDrawIn 1s ease-in-out forwards 1.3666666666666667s, appearIn 0.1s linear forwards 1.3666666666666667s; + animation-iteration-count: infinite, 1; + opacity: 0; + stroke-dasharray: 70; + } + + .triangle-left { + animation: innerDrawIn 1s ease-in-out forwards 1.5333333333333332s, appearIn 0.1s linear forwards 1.5333333333333332s; + animation-iteration-count: infinite, 1; + opacity: 0; + stroke-dasharray: 70; + } + + .triangle-right { + animation: innerDrawIn 1s ease-in-out forwards 1.7000000000000002s, appearIn 0.1s linear forwards 1.7000000000000002s; + animation-iteration-count: infinite, 1; + opacity: 0; + stroke-dasharray: 70; + } + </style> + + <defs> + <linearGradient id="linearGradient" x1="4.86%" x2="96.21%" y1="0%" y2="99.66%"> + <stop stop-color="#e00082" stop-opacity="0.8" offset="0%"></stop> + <stop stop-color="#e00082" offset="100%"></stop> + </linearGradient> + </defs> + + <g> + <rect id="gradient" width="127.96" height="127.96" y="1" fill="url(#linearGradient)" rx="4"></rect> + <path id="border" fill="#e00082" fill-rule="nonzero" d="M4.7 2.84c-1.58 0-2.86 1.28-2.86 2.85v116.57c0 1.57 1.28 2.84 2.85 2.84h116.57c1.57 0 2.84-1.26 2.84-2.83V5.67c0-1.55-1.26-2.83-2.83-2.83H4.67zM4.7 0h116.58c3.14 0 5.68 2.55 5.68 5.7v116.58c0 3.14-2.54 5.68-5.68 5.68H4.68c-3.13 0-5.68-2.54-5.68-5.68V5.68C-1 2.56 1.55 0 4.7 0z"></path> + + <path + class="circle-top" x="64" y="28" + d="M64 36c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8" + style="transform: translate(100px, 100px);"></path> + <path + class="circle-top-right" x="95.98500061035156" y="46.510000228881836" + d="M89.04 50.52c-2.2-3.84-.9-8.73 2.94-10.96 3.83-2.2 8.72-.9 10.95 2.94 2.2 3.84.9 8.73-2.94 10.96-3.85 2.2-8.76.9-10.97-2.94" + style="transform: translate(100px, 100px);"></path> + <path + class="circle-bottom-right" x="95.97162628173828" y="83.4900016784668" + d="M102.9 87.5c-2.2 3.84-7.1 5.15-10.94 2.94-3.84-2.2-5.14-7.12-2.94-10.96 2.2-3.84 7.12-5.15 10.95-2.94 3.86 2.23 5.16 7.12 2.94 10.96" + style="transform: translate(100px, 100px);"></path> + <path + class="circle-bottom" x="64" y="101.97999572753906" + d="M64 110c-4.43 0-8-3.6-8-8.02 0-4.44 3.57-8.02 8-8.02s8 3.58 8 8.02c0 4.4-3.57 8.02-8 8.02" + style="transform: translate(100px, 100px);"></path> + <path + class="circle-bottom-left" x="32.03982162475586" y="83.4900016784668" + d="M25.1 87.5c-2.2-3.84-.9-8.73 2.93-10.96 3.83-2.2 8.72-.9 10.95 2.94 2.2 3.84.9 8.73-2.94 10.96-3.85 2.2-8.74.9-10.95-2.94" + style="transform: translate(100px, 100px);"></path> + <path + class="circle-top-left" x="32.033552169799805" y="46.510000228881836" + d="M38.96 50.52c-2.2 3.84-7.12 5.15-10.95 2.94-3.82-2.2-5.12-7.12-2.92-10.96 2.2-3.84 7.12-5.15 10.95-2.94 3.83 2.23 5.14 7.12 2.94 10.96" + style="transform: translate(100px, 100px);"></path> + + <path class="octoline-top-right" d="M63.55 27.5l32.9 19-32.9-19z"></path> + <path class="octoline-right" d="M96 46v38-38z"></path> + <path class="octoline-bottom-right" d="M96.45 84.5l-32.9 19 32.9-19z"></path> + <path class="octoline-bottom-left" d="M64.45 103.5l-32.9-19 32.9 19z"></path> + <path class="octoline-left" d="M32 84V46v38z"></path> + <path class="octoline-top-left" d="M31.55 46.5l32.9-19-32.9 19z"></path> + + <path class="triangle-bottom" d="M30 84h70"></path> + <path class="triangle-left" d="M65 26L30 87"></path> + <path class="triangle-right" d="M98 87L63 26"></path> + </g> + </svg> + + <div class="text">Loading + <span class="text-inner">GraphQL Playground</span> + </div> + </div> + `, + script: ` + const loadingWrapper = document.getElementById("loading-wrapper"); + + if (loadingWrapper) + loadingWrapper.classList.add("fadeOut"); + ` +}); diff --git a/src/graphiql/render.ts b/src/graphiql/render.ts new file mode 100755 index 0000000..b4af05b --- /dev/null +++ b/src/graphiql/render.ts @@ -0,0 +1,214 @@ + + + +/// import + +import { filterXSS } from "npm:xss@1.0.14"; + +/// util + +import { getLoadingMarkup } from "./markup.ts"; + +const CONFIG_ID = "playground-config"; +const loading = getLoadingMarkup(); + +const filter = (val: string) => { + return filterXSS(val, { + stripIgnoreTag: true, + stripIgnoreTagBody: ["script"], + whiteList: {} + }); +} + +const getCdnMarkup = ({ cdnUrl = "//cdn.jsdelivr.net/npm", faviconUrl, version }: { + cdnUrl?: string + faviconUrl?: string | null + version?: string +}) => { + const buildCDNUrl = (packageName: string, suffix: string) => + filter(`${cdnUrl}/${packageName}${version ? `@${version}` : ""}/${suffix}` || ""); + + return ` + <link rel="stylesheet" href="${buildCDNUrl("graphql-playground-react", "build/static/css/index.css")}"/> + ${typeof faviconUrl === "string" ? `<link rel="shortcut icon" href="${filter(faviconUrl || "")}" />` : ""} + ${faviconUrl === undefined ? `<link rel="shortcut icon" href="${buildCDNUrl("graphql-playground-react", "build/favicon.png")}" />` : ""} + <script src="${buildCDNUrl("graphql-playground-react", "build/static/js/middleware.js")}"></script> + `; +} + +const renderConfig = (config: unknown) => { + return filterXSS(`<div id="${CONFIG_ID}">${JSON.stringify(config)}</div>`, { + whiteList: { div: ["id"] } + }); +}; + + + +/// export + +export interface MiddlewareOptions { + codeTheme?: EditorColours; + config?: any; + endpoint?: string; + env?: any; + schema?: IntrospectionResult; + settings?: ISettings; + subscriptionEndpoint?: string; + tabs?: Tab[]; + workspaceName?: string; +} + +export type CursorShape = "line" | "block" | "underline"; +export type Theme = "dark" | "light"; + +export interface ISettings { + "editor.cursorShape": CursorShape; + "editor.fontFamily": string; + "editor.fontSize": number; + "editor.reuseHeaders": boolean; + "editor.theme": Theme; + "general.betaUpdates": boolean; + "request.credentials": string; + "request.globalHeaders": { [key: string]: string }; + "schema.polling.enable": boolean; + "schema.polling.endpointFilter": string; + "schema.polling.interval": number; + "tracing.hideTracingResponse": boolean; + "tracing.tracingSupported": boolean; +} + +export interface EditorColours { + atom: string; + attribute: string; + builtin: string; + comment: string; + cursorColor: string; + def: string; + editorBackground: string; + keyword: string; + leftDrawerBackground: string; + meta: string; + number: string; + property: string; + punctuation: string; + qualifier: string; + resultBackground: string; + rightDrawerBackground: string; + selection: string; + string: string; + string2: string; + variable: string; + ws: string; +} + +export interface IntrospectionResult { + __schema: any; +} + +export interface RenderPageOptions extends MiddlewareOptions { + cdnUrl?: string; + env?: any; + faviconUrl?: string | null; + title?: string; + version?: string; +} + +export interface Tab { + endpoint: string; + headers?: { [key: string]: string }; + name?: string; + query: string; + responses?: string[]; + variables?: string; +} + +export function renderPlaygroundPage(options: RenderPageOptions) { + const extendedOptions: + & Partial<{ + canSaveConfig: boolean + configString: string + }> + & RenderPageOptions = { + ...options, + canSaveConfig: false + }; + + if (options.config) + extendedOptions.configString = JSON.stringify(options.config, null, 2); + + if (!extendedOptions.endpoint && !extendedOptions.configString) + console.warn("WARNING: You did not provide an endpoint and do not have a .graphqlconfig. Make sure you have at least one of them."); + else if (extendedOptions.endpoint) + extendedOptions.endpoint = filter(extendedOptions.endpoint || ""); + + return ` + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset=utf-8/> + <meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui"/> + <link href="https://brick.a.ssl.fastly.net/Open+Sans:300,400,600,700/Source+Code+Pro:400,700" rel="stylesheet"/> + <title>${extendedOptions.title || "GraphQL Playground"}</title> + ${extendedOptions.env === "react" || extendedOptions.env === "electron" ? "" : getCdnMarkup(extendedOptions)} + </head> + + <body> + <style> + html { + font-family: "Open Sans", sans-serif; + overflow: hidden; + } + + body { + background-color: #172a3a; + margin: 0; + } + + #${CONFIG_ID} { + display: none; + } + + .playgroundIn { + animation: playgroundIn 0.5s ease-out forwards; + } + + @keyframes playgroundIn { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } + } + </style> + + ${loading.container} + ${renderConfig(extendedOptions)} + <div id="root"/> + + <script> + window.addEventListener("load", () => { + ${loading.script} + + const root = document.getElementById("root"); + root.classList.add("playgroundIn"); + const configText = document.getElementById("${CONFIG_ID}").innerText; + + if (configText && configText.length) { + try { + GraphQLPlayground.init(root, JSON.parse(configText)); + } catch(_) { + console.error("could not find config"); + } + } else { + GraphQLPlayground.init(root); + } + }); + </script> + </body> + </html> + `; +} |
