diff options
| author | netop://ウィビ <paul@webb.page> | 2026-04-26 20:18:30 -0700 |
|---|---|---|
| committer | netop://ウィビ <paul@webb.page> | 2026-04-26 20:18:30 -0700 |
| commit | 3c06c95f396b6e911076bc3291d5855ed01b5caa (patch) | |
| tree | 17cd218339c52fbeee93d931303b04a3ff294f8b /source/library/GraphiQL.svelte | |
| parent | f059d97ab7f6d74d61139ac698cb871be7cb632e (diff) | |
| download | graphiql-3c06c95f396b6e911076bc3291d5855ed01b5caa.tar.gz graphiql-3c06c95f396b6e911076bc3291d5855ed01b5caa.zip | |
cleanup and ready for launch
Diffstat (limited to 'source/library/GraphiQL.svelte')
| -rw-r--r-- | source/library/GraphiQL.svelte | 392 |
1 files changed, 243 insertions, 149 deletions
diff --git a/source/library/GraphiQL.svelte b/source/library/GraphiQL.svelte index 0f1e0fd..3ffc372 100644 --- a/source/library/GraphiQL.svelte +++ b/source/library/GraphiQL.svelte @@ -1,28 +1,28 @@ <script lang="ts"> /*** IMPORT ------------------------------------------- ***/ - + import "@inc/uchu/css"; import { onMount, type Snippet } from "svelte"; + import type { Extension } from "@codemirror/state"; /*** UTILITY ------------------------------------------ ***/ - import DocExplorer from "./components/DocExplorer.svelte"; import Editor from "./components/Editor.svelte"; - import HeadersEditor from "./components/HeadersEditor.svelte"; import HistoryPanel from "./components/HistoryPanel.svelte"; import ResultViewer from "./components/ResultViewer.svelte"; import Splitter from "./components/Splitter.svelte"; import TabBar from "./components/TabBar.svelte"; import Toolbar from "./components/Toolbar.svelte"; - import type { Extension } from "@codemirror/state"; - import type { Fetcher } from "./fetcher/types.ts"; + + import { createLocalStorage, createMemoryStorage } from "./state/storage.ts"; import { HistoryStore } from "./state/history.svelte.ts"; import { matchShortcut } from "./state/keyboard.ts"; import { SchemaStore } from "./state/schema.svelte.ts"; import { SessionStore } from "./state/session.svelte.ts"; - import type { SubscriptionMode, Tab } from "./state/session.svelte.ts"; import { validateSessionExport } from "./state/session-io.ts"; - import { createLocalStorage, createMemoryStorage } from "./state/storage.ts"; + + import type { Fetcher } from "./fetcher/types.ts"; import type { Storage } from "./state/storage.ts"; + import type { SubscriptionMode, Tab } from "./state/session.svelte.ts"; type Props = { fetcher: Fetcher; @@ -36,6 +36,9 @@ toolbarExtras?: Snippet; }; + const MINIMUM_HISTORY_WIDTH = 300; + const PERSIST_DEBOUNCE_MS = 300; + let { fetcher, initialQuery = "", @@ -55,38 +58,59 @@ createLocalStorage(namespace) : createMemoryStorage()); - const PERSIST_DEBOUNCE_MS = 300; const history = new HistoryStore(resolvedStorage); const schema = new SchemaStore(); const session = new SessionStore(resolvedStorage); // svelte-ignore state_referenced_locally - if (initialQuery && session.active && session.active.query === "") + if (initialQuery && session.active && session.active.query === "") { // svelte-ignore state_referenced_locally session.updateQuery(session.active.id, initialQuery); + } let bottomPane = $state<"variables" | "headers">("variables"); let bottomHeight = $state(resolvedStorage.get<number>("layout.bottomHeight") ?? 35); let centerEl = $state<HTMLDivElement | null>(null); let docsOpen = $state(resolvedStorage.get<boolean>("docExplorer") ?? false); let docsWidth = $state(resolvedStorage.get<number>("layout.docsWidth") ?? 320); + let dragStartBottomHeight = 0; + let dragStartBottomHeightPx = 0; + let dragStartDocsWidth = 0; + let dragStartHistoryWidth = 0; + let dragStartLeftWidth = 0; + let dragStartLeftWidthPx = 0; let historyNotice = $state<string | null>(null); let historyOpen = $state(resolvedStorage.get<boolean>("historyPanel") ?? false); - let historyWidth = $state(resolvedStorage.get<number>("layout.historyWidth") ?? 260); + let historyWidth = $state(resolvedStorage.get<number>("layout.historyWidth") ?? MINIMUM_HISTORY_WIDTH); let leftEl = $state<HTMLDivElement | null>(null); let leftWidth = $state(resolvedStorage.get<number>("layout.leftWidth") ?? 50); + let runAbort: AbortController | null = null; let running = $state(false); + let windowWidth = $state(0); + let windowHeight = $state(0); + let windowResizeTimeout: ReturnType<typeof setTimeout>; + + - let dragStartHistoryWidth = 0; - let dragStartDocsWidth = 0; - let dragStartLeftWidth = 0; - let dragStartLeftWidthPx = 0; - let dragStartBottomHeight = 0; - let dragStartBottomHeightPx = 0; - function clamp(value: number, min: number, max: number): number { - return Math.min(Math.max(value, min), max); - } + + $effect(() => { + const updateSize = () => { + clearTimeout(windowResizeTimeout); + + windowResizeTimeout = setTimeout(() => { + windowWidth = globalThis.innerWidth; + windowHeight = globalThis.innerHeight; + }, 100); + }; + + updateSize(); + globalThis.addEventListener("resize", updateSize); + + return () => { + globalThis.removeEventListener("resize", updateSize); + }; + }); $effect(() => { void session.tabs; @@ -137,7 +161,10 @@ schema.introspect(fetcher); }); - let runAbort: AbortController | null = null; + /*** HELPER ------------------------------------------- ***/ + function clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); + } async function run() { if (running) { @@ -190,6 +217,39 @@ session.overwriteActive(seed); } + function onBeforeUnload() { + session.persist(); + } + + function onBottomDrag(_dx: number, dy: number) { + if (dragStartBottomHeightPx === 0) + return; + + const percentDelta = (dy / dragStartBottomHeightPx) * 100; + bottomHeight = clamp(dragStartBottomHeight - percentDelta, 15, 70); + } + + function onBottomDragStart() { + dragStartBottomHeight = bottomHeight; + dragStartBottomHeightPx = leftEl?.getBoundingClientRect().height ?? 0; + } + + function onBottomKeyAdjust(delta: number) { + bottomHeight = clamp(bottomHeight - delta, 15, 70); + } + + function onDocsDrag(dx: number) { + docsWidth = clamp(dragStartDocsWidth - dx, 240, 600); + } + + function onDocsDragStart() { + dragStartDocsWidth = docsWidth; + } + + function onDocsKeyAdjust(delta: number) { + docsWidth = clamp(docsWidth - delta, 240, 600); + } + function onExportSession() { if (typeof globalThis.URL === "undefined") return; @@ -201,12 +261,31 @@ const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = globalThis.document.createElement("a"); + a.download = `graphiql-session-${data.exportedAt}.json`; a.href = url; a.click(); + URL.revokeObjectURL(url); } + function onHeadersChange(value: string) { + if (session.active) + session.active.headers = value; + } + + function onHistoryDrag(dx: number) { + historyWidth = clamp(dragStartHistoryWidth + dx, MINIMUM_HISTORY_WIDTH, 500); + } + + function onHistoryDragStart() { + dragStartHistoryWidth = historyWidth; + } + + function onHistoryKeyAdjust(delta: number) { + historyWidth = clamp(historyWidth + delta, MINIMUM_HISTORY_WIDTH, 500); + } + async function onImportSession(file: File) { let text: string; @@ -245,21 +324,6 @@ historyNotice = parts.join(", "); } - function onQueryChange(value: string) { - if (session.active) - session.updateQuery(session.active.id, value); - } - - function onVariablesChange(value: string) { - if (session.active) - session.active.variables = value; - } - - function onHeadersChange(value: string) { - if (session.active) - session.active.headers = value; - } - function onKeydown(event: KeyboardEvent) { const action = matchShortcut(event); @@ -269,60 +333,38 @@ event.preventDefault(); switch (action.type) { - case "closeTab": + case "closeTab": { session.closeTab(session.activeId); break; - case "format": + } + + case "format": { session.formatActive(); break; - case "newTab": + } + + case "newTab": { session.addTab(); break; - case "nextTab": + } + + case "nextTab": { session.nextTab(); break; - case "prevTab": + } + + case "prevTab": { session.prevTab(); break; - case "run": + } + + case "run": { run(); break; + } } } - function onBeforeUnload() { - session.persist(); - } - - function onHistoryDragStart() { - dragStartHistoryWidth = historyWidth; - } - - function onHistoryDrag(dx: number) { - historyWidth = clamp(dragStartHistoryWidth + dx, 200, 500); - } - - function onHistoryKeyAdjust(delta: number) { - historyWidth = clamp(historyWidth + delta, 200, 500); - } - - function onDocsDragStart() { - dragStartDocsWidth = docsWidth; - } - - function onDocsDrag(dx: number) { - docsWidth = clamp(dragStartDocsWidth - dx, 240, 600); - } - - function onDocsKeyAdjust(delta: number) { - docsWidth = clamp(docsWidth - delta, 240, 600); - } - - function onLeftDragStart() { - dragStartLeftWidth = leftWidth; - dragStartLeftWidthPx = centerEl?.getBoundingClientRect().width ?? 0; - } - function onLeftDrag(dx: number) { if (dragStartLeftWidthPx === 0) return; @@ -331,25 +373,23 @@ leftWidth = clamp(dragStartLeftWidth + percentDelta, 20, 80); } - function onLeftKeyAdjust(delta: number) { - leftWidth = clamp(leftWidth + delta, 20, 80); + function onLeftDragStart() { + dragStartLeftWidth = leftWidth; + dragStartLeftWidthPx = centerEl?.getBoundingClientRect().width ?? 0; } - function onBottomDragStart() { - dragStartBottomHeight = bottomHeight; - dragStartBottomHeightPx = leftEl?.getBoundingClientRect().height ?? 0; + function onLeftKeyAdjust(delta: number) { + leftWidth = clamp(leftWidth + delta, 20, 80); } - function onBottomDrag(_dx: number, dy: number) { - if (dragStartBottomHeightPx === 0) - return; - - const percentDelta = (dy / dragStartBottomHeightPx) * 100; - bottomHeight = clamp(dragStartBottomHeight - percentDelta, 15, 70); + function onQueryChange(value: string) { + if (session.active) + session.updateQuery(session.active.id, value); } - function onBottomKeyAdjust(delta: number) { - bottomHeight = clamp(bottomHeight - delta, 15, 70); + function onVariablesChange(value: string) { + if (session.active) + session.active.variables = value; } </script> @@ -357,11 +397,22 @@ .graphiql { width: 100%; height: 100%; - background-color: var(--graphiql-bg, #1e1e1e); - color: var(--graphiql-fg, #d4d4d4); - display: grid; - font-family: var(--graphiql-font, ui-monospace, SFMono-Regular, monospace); - grid-template-rows: auto auto 1fr; + background-color: var(--uchu-yang); + color: var(--uchu-yin-9); + font-family: "Berkeley Mono", ui-monospace, SFMono-Regular, monospace; + font-size: 16px; + overflow: hidden; + overscroll-behavior: none; + + @media (min-width: 1025px) { + display: grid; + grid-template-rows: auto auto 1fr; + } + + @media (max-width: 1024px) { + display: flex; + flex-direction: column; + } } .panes { @@ -369,6 +420,10 @@ grid-template-columns: 1fr; min-height: 0; + @media (max-width: 1024px) { + height: stretch; + } + &.history-open { grid-template-columns: var(--graphiql-history-width) 6px 1fr; } @@ -382,61 +437,91 @@ } } - .center { - display: grid; - grid-template-columns: var(--graphiql-left-width) 6px calc(100% - var(--graphiql-left-width) - 6px); - min-height: 0; - min-width: 0; - } - + .center, .left { - display: grid; - grid-template-rows: 1fr auto 6px var(--graphiql-bottom-height); + margin-right: -6px; min-height: 0; min-width: 0; } - .query { - min-height: 0; - } + .center { + @media (min-width: 1025px) { + display: grid; + grid-template-columns: var(--graphiql-left-width) 6px calc(100% - var(--graphiql-left-width) - 6px); + } - .switcher { - background-color: var(--graphiql-panel, #252526); - border-top: 1px solid var(--graphiql-border, #333); - display: flex; - } + @media (max-width: 1024px) { + display: flex; + flex-direction: column; + } - .switch { - background: none; - border: none; - cursor: pointer; - font-size: 0.75rem; - letter-spacing: 0.05em; - padding: 0.375rem 0.75rem; - text-transform: uppercase; + .query { + flex: 2; + margin-bottom: -6px; + min-height: 0; + } - &:not(.active) { - color: var(--graphiql-muted, #858585); + .bottom { + flex: 1; + min-height: 0; } - &.active { - color: var(--graphiql-fg, #d4d4d4); + .left { + display: flex; + flex-direction: column; + overflow-x: hidden; + + @media (max-width: 1024px) { + flex: 2; + } } - } - .bottom { - min-height: 0; + .right { + min-height: 0; + + @media (max-width: 1024px) { + border-top: 2px solid var(--uchu-gray-3); + flex: 1; + } + } } - .right { - min-height: 0; + .switcher { + background-color: var(--uchu-gray-2); + display: flex; + flex-direction: row; + + .switch { + background: none; + border: none; + cursor: pointer; + font-family: "Berkeley Mono", ui-monospace, SFMono-Regular, monospace; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.05rem; + padding: 0.375rem 0.75rem; + text-transform: uppercase; + + &:not(.active) { + color: var(--uchu-yin-5); + } + + &.active { + background-color: var(--uchu-gray-3); + } + } } .status { - background-color: var(--graphiql-panel, #252526); - border-top: 1px solid var(--graphiql-border, #333); + background-color: var(--uchu-yellow-2); + border-top: 1px solid var(--uchu-yellow-3); + color: var(--uchu-yellow-9); font-size: 0.75rem; padding: 0.25rem 0.75rem; + + span { + text-transform: uppercase; + } } </style> @@ -473,8 +558,7 @@ class="panes" class:docs-open={docsOpen && schema.schema} class:history-open={historyOpen} - style="--graphiql-history-width: {historyWidth}px; --graphiql-docs-width: {docsWidth}px;" - > + style="--graphiql-history-width: {historyWidth}px; --graphiql-docs-width: {docsWidth}px;"> {#if historyOpen} <HistoryPanel entries={history.entries} @@ -492,11 +576,11 @@ onKeyAdjust={onHistoryKeyAdjust} orientation="horizontal"/> {/if} + <div bind:this={centerEl} class="center" - style="--graphiql-left-width: {leftWidth}%;" - > + style="--graphiql-left-width: {leftWidth}%;"> <div bind:this={leftEl} class="left" style="--graphiql-bottom-height: {bottomHeight}%;"> <div class="query"> <Editor @@ -506,22 +590,25 @@ {theme} value={session.active?.query ?? ""}/> </div> - <div class="switcher"> - <button - class="switch" - class:active={bottomPane === "variables"} - onclick={() => (bottomPane = "variables")}>Variables</button> - <button - class="switch" - class:active={bottomPane === "headers"} - onclick={() => (bottomPane = "headers")}>Headers</button> - </div> + <Splitter onDrag={onBottomDrag} onDragStart={onBottomDragStart} onKeyAdjust={onBottomKeyAdjust} orientation="vertical"/> + <div class="bottom"> + <div class="switcher"> + <button + class="switch" + class:active={bottomPane === "variables"} + onclick={() => (bottomPane = "variables")}>Variables</button> + <button + class="switch" + class:active={bottomPane === "headers"} + onclick={() => (bottomPane = "headers")}>Headers</button> + </div> + {#if bottomPane === "variables"} <Editor language="json" @@ -529,18 +616,23 @@ {theme} value={session.active?.variables ?? "{}"}/> {:else} - <HeadersEditor + <Editor + language="json" onChange={onHeadersChange} {theme} value={session.active?.headers ?? "{}"}/> {/if} </div> </div> - <Splitter - onDrag={onLeftDrag} - onDragStart={onLeftDragStart} - onKeyAdjust={onLeftKeyAdjust} - orientation="horizontal"/> + + {#if windowWidth >= 1025} + <Splitter + onDrag={onLeftDrag} + onDragStart={onLeftDragStart} + onKeyAdjust={onLeftKeyAdjust} + orientation="horizontal"/> + {/if} + <div class="right"> <ResultViewer footer={resultFooter} @@ -550,6 +642,7 @@ value={session.active?.result ?? ""}/> </div> </div> + {#if docsOpen && schema.schema} <Splitter onDrag={onDocsDrag} @@ -559,7 +652,8 @@ <DocExplorer schema={schema.schema}/> {/if} </div> + {#if schema.error} - <div class="status">Schema error: {schema.error}</div> + <div class="status"><span>Schema error</span> {schema.error}</div> {/if} </div> |