@xyd-js/apidocs-demo 0.0.0-build
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +4 -0
- package/CHANGELOG.md +43 -0
- package/Dockerfile +22 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/apis.txt +9 -0
- package/app/api/pluginOasOpenAPI.ts +538 -0
- package/app/api/try.ts +26 -0
- package/app/const.ts +2 -0
- package/app/context.tsx +52 -0
- package/app/index.css +58 -0
- package/app/root.tsx +106 -0
- package/app/routes/layout.tsx +722 -0
- package/app/routes/url.tsx +80 -0
- package/app/routes.ts +12 -0
- package/app/settings.ts +47 -0
- package/app/utils/toUniform.ts +123 -0
- package/package.json +50 -0
- package/public/favicon.svg +10 -0
- package/react-router.config.ts +7 -0
- package/tsconfig.json +27 -0
- package/vite.config.ts +39 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
import React, { useState, useRef, createContext, useContext, useEffect, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Outlet,
|
|
4
|
+
useFetcher,
|
|
5
|
+
useLocation,
|
|
6
|
+
useNavigate,
|
|
7
|
+
useNavigation,
|
|
8
|
+
} from "react-router";
|
|
9
|
+
import { Box, Button, Dropdown, Flex, TextField, Text, Spinner, FixedZIndex } from 'gestalt';
|
|
10
|
+
import GitHubButton from 'react-github-btn'
|
|
11
|
+
|
|
12
|
+
import { Badge } from "@xyd-js/components/writer"
|
|
13
|
+
import { ReactContent } from "@xyd-js/components/content";
|
|
14
|
+
import { Atlas, AtlasContext, type VariantToggleConfig } from "@xyd-js/atlas";
|
|
15
|
+
import { Surfaces } from "@xyd-js/framework";
|
|
16
|
+
import { Framework, FwLink } from "@xyd-js/framework/react";
|
|
17
|
+
import ThemePoetry from "@xyd-js/theme-poetry";
|
|
18
|
+
import ThemeOpener from "@xyd-js/theme-opener";
|
|
19
|
+
import ThemeCosmo from "@xyd-js/theme-cosmo";
|
|
20
|
+
import ThemePicasso from "@xyd-js/theme-picasso";
|
|
21
|
+
import ThemeGusto from "@xyd-js/theme-gusto";
|
|
22
|
+
import ThemeSolar from "@xyd-js/theme-solar";
|
|
23
|
+
|
|
24
|
+
import poetryCss from '@xyd-js/theme-poetry/index.css?url';
|
|
25
|
+
import openerCss from '@xyd-js/theme-opener/index.css?url';
|
|
26
|
+
import cosmoCss from '@xyd-js/theme-cosmo/index.css?url';
|
|
27
|
+
import picassoCss from '@xyd-js/theme-picasso/index.css?url';
|
|
28
|
+
import gustoCss from '@xyd-js/theme-gusto/index.css?url';
|
|
29
|
+
import solarCss from '@xyd-js/theme-solar/index.css?url';
|
|
30
|
+
|
|
31
|
+
import { SETTINGS } from '../settings';
|
|
32
|
+
import { useGlobalState } from '../context';
|
|
33
|
+
|
|
34
|
+
import { DOCS_PREFIX } from '~/const';
|
|
35
|
+
import { UrlContext } from '~/context';
|
|
36
|
+
import { toUniform } from '~/utils/toUniform';
|
|
37
|
+
|
|
38
|
+
const surfaces = new Surfaces()
|
|
39
|
+
|
|
40
|
+
function SidebarItemRight(props: any) {
|
|
41
|
+
const openapi = props?.pageMeta?.openapi || ""
|
|
42
|
+
const [_, region = ""] = openapi.includes("#") ? openapi.split("#") : ["", openapi]
|
|
43
|
+
const [method = ""] = region.split(" ")
|
|
44
|
+
|
|
45
|
+
if (!method) {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
let methodText = method.toUpperCase()
|
|
49
|
+
if (method === "DELETE") {
|
|
50
|
+
methodText = "DEL"
|
|
51
|
+
}
|
|
52
|
+
[]
|
|
53
|
+
return <div data-active={props?.active ? "true" : undefined} data-atlas-oas-method={method}>
|
|
54
|
+
<Badge size="xs">
|
|
55
|
+
{methodText}
|
|
56
|
+
</Badge>
|
|
57
|
+
</div>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
surfaces.define("sidebar.item.right", SidebarItemRight);
|
|
61
|
+
|
|
62
|
+
const reactContent = new ReactContent(SETTINGS, {
|
|
63
|
+
Link: FwLink,
|
|
64
|
+
components: {
|
|
65
|
+
Atlas,
|
|
66
|
+
},
|
|
67
|
+
useLocation, // // TODO: !!!! BETTER API !!!!!
|
|
68
|
+
useNavigate,
|
|
69
|
+
useNavigation
|
|
70
|
+
})
|
|
71
|
+
// TODO: !!! for demo it cannot be globalThis cuz its globally for whole server !!!
|
|
72
|
+
globalThis.__xydThemeSettings = SETTINGS?.theme
|
|
73
|
+
globalThis.__xydReactContent = reactContent
|
|
74
|
+
globalThis.__xydSurfaces = surfaces
|
|
75
|
+
|
|
76
|
+
let theme: any | null = null
|
|
77
|
+
|
|
78
|
+
switch (SETTINGS?.theme?.name) {
|
|
79
|
+
case "poetry":
|
|
80
|
+
theme = new ThemePoetry()
|
|
81
|
+
break
|
|
82
|
+
case "opener":
|
|
83
|
+
theme = new ThemeOpener()
|
|
84
|
+
break
|
|
85
|
+
case "cosmo":
|
|
86
|
+
theme = new ThemeCosmo()
|
|
87
|
+
break
|
|
88
|
+
case "picasso":
|
|
89
|
+
theme = new ThemePicasso()
|
|
90
|
+
break
|
|
91
|
+
case "gusto":
|
|
92
|
+
theme = new ThemeGusto()
|
|
93
|
+
break
|
|
94
|
+
case "solar":
|
|
95
|
+
theme = new ThemeSolar()
|
|
96
|
+
break
|
|
97
|
+
default:
|
|
98
|
+
theme = null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface DemoContextType {
|
|
102
|
+
example: any;
|
|
103
|
+
setExample: (example: any) => void;
|
|
104
|
+
settings: any;
|
|
105
|
+
fetcher: any;
|
|
106
|
+
isThemeSwitching: boolean;
|
|
107
|
+
setIsThemeSwitching: (switching: boolean) => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const DemoContext = createContext<DemoContextType>({
|
|
111
|
+
example: null,
|
|
112
|
+
setExample: () => {
|
|
113
|
+
},
|
|
114
|
+
settings: {},
|
|
115
|
+
fetcher: null,
|
|
116
|
+
isThemeSwitching: false,
|
|
117
|
+
setIsThemeSwitching: () => {
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export async function loader() {
|
|
122
|
+
return {
|
|
123
|
+
defaultExample: await toUniform(
|
|
124
|
+
"/docs/api",
|
|
125
|
+
"https://raw.githubusercontent.com/livesession/livesession-openapi/master/openapi.yaml",
|
|
126
|
+
"",
|
|
127
|
+
""
|
|
128
|
+
),
|
|
129
|
+
// defaultExample: {}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default function Layout({ loaderData }: { loaderData: any }) {
|
|
134
|
+
const [example, setExample] = useState<any>(null);
|
|
135
|
+
const [isThemeSwitching, setIsThemeSwitching] = useState(false);
|
|
136
|
+
const { actionData: globalActionData, setActionData } = useGlobalState();
|
|
137
|
+
const fetcher = useFetcher();
|
|
138
|
+
const navigate = useNavigate()
|
|
139
|
+
|
|
140
|
+
let effectiveActionData = globalActionData || null;
|
|
141
|
+
if (!globalActionData?.references?.length && loaderData?.defaultExample?.references?.length) {
|
|
142
|
+
effectiveActionData = {
|
|
143
|
+
...globalActionData,
|
|
144
|
+
...loaderData.defaultExample
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!globalActionData?.references?.length && loaderData?.defaultExample?.references?.length) {
|
|
150
|
+
setActionData(loaderData?.defaultExample)
|
|
151
|
+
|
|
152
|
+
let canonical = loaderData?.defaultExample?.references?.[0]?.canonical
|
|
153
|
+
if (canonical) {
|
|
154
|
+
canonical = canonical.startsWith("/") ? canonical : `/${canonical}`
|
|
155
|
+
if (canonical.endsWith("/")) {
|
|
156
|
+
canonical = canonical.slice(0, -1)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
navigate(`${DOCS_PREFIX}${canonical}`)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}, [])
|
|
163
|
+
|
|
164
|
+
const settings = effectiveActionData?.settings || SETTINGS;
|
|
165
|
+
|
|
166
|
+
let currentTheme = null;
|
|
167
|
+
switch (settings?.theme?.name) {
|
|
168
|
+
case "poetry":
|
|
169
|
+
currentTheme = new ThemePoetry();
|
|
170
|
+
break;
|
|
171
|
+
case "opener":
|
|
172
|
+
currentTheme = new ThemeOpener();
|
|
173
|
+
break;
|
|
174
|
+
case "cosmo":
|
|
175
|
+
currentTheme = new ThemeCosmo();
|
|
176
|
+
break;
|
|
177
|
+
case "picasso":
|
|
178
|
+
currentTheme = new ThemePicasso();
|
|
179
|
+
break;
|
|
180
|
+
case "gusto":
|
|
181
|
+
currentTheme = new ThemeGusto();
|
|
182
|
+
break;
|
|
183
|
+
case "solar":
|
|
184
|
+
currentTheme = new ThemeSolar();
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
currentTheme = null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const {
|
|
191
|
+
Page: BaseThemePage,
|
|
192
|
+
Layout: BaseThemeLayout,
|
|
193
|
+
} = currentTheme || {};
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<DemoContext.Provider value={{ example, setExample, settings: effectiveActionData?.settings || SETTINGS, fetcher, isThemeSwitching, setIsThemeSwitching }}>
|
|
197
|
+
<Layout2 effectiveActionData={effectiveActionData} BaseThemePage={BaseThemePage}
|
|
198
|
+
BaseThemeLayout={BaseThemeLayout} />
|
|
199
|
+
</DemoContext.Provider>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const Layout2 = React.memo(function Layout2({
|
|
204
|
+
effectiveActionData,
|
|
205
|
+
BaseThemeLayout,
|
|
206
|
+
BaseThemePage
|
|
207
|
+
}: {
|
|
208
|
+
effectiveActionData: any;
|
|
209
|
+
BaseThemeLayout: any;
|
|
210
|
+
BaseThemePage: any;
|
|
211
|
+
}) {
|
|
212
|
+
|
|
213
|
+
let atlasVariantToggles: VariantToggleConfig[] = [];
|
|
214
|
+
if (effectiveActionData.exampleType === "openapi") {
|
|
215
|
+
atlasVariantToggles = [
|
|
216
|
+
{ key: "status", defaultValue: "200" },
|
|
217
|
+
{ key: "contentType", defaultValue: "application/json" }
|
|
218
|
+
];
|
|
219
|
+
} else {
|
|
220
|
+
atlasVariantToggles = [
|
|
221
|
+
{ key: "symbolName", defaultValue: "" }
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return <Framework
|
|
226
|
+
settings={effectiveActionData.settings || {}}
|
|
227
|
+
sidebarGroups={effectiveActionData.groups || []}
|
|
228
|
+
metadata={{
|
|
229
|
+
layout: "wide",
|
|
230
|
+
uniform: "1",
|
|
231
|
+
title: "OpenAPI Demo"
|
|
232
|
+
}}
|
|
233
|
+
surfaces={surfaces}
|
|
234
|
+
// BannerContent={MemoizedActionDropdownExample}
|
|
235
|
+
>
|
|
236
|
+
<AtlasContext
|
|
237
|
+
value={{
|
|
238
|
+
syntaxHighlight: effectiveActionData.settings?.theme?.coder?.syntaxHighlight || null,
|
|
239
|
+
baseMatch: "/docs/api",
|
|
240
|
+
variantToggles: atlasVariantToggles,
|
|
241
|
+
Link: FwLink,
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<MemoizedActionDropdownExample />
|
|
245
|
+
<BaseThemeLayout>
|
|
246
|
+
<UrlContext.Provider value={{ BaseThemePage }}>
|
|
247
|
+
<Outlet />
|
|
248
|
+
</UrlContext.Provider>
|
|
249
|
+
</BaseThemeLayout>
|
|
250
|
+
<Loader />
|
|
251
|
+
</AtlasContext>
|
|
252
|
+
</Framework>
|
|
253
|
+
}, (prevProps, nextProps) => {
|
|
254
|
+
return JSON.stringify(prevProps.effectiveActionData) === JSON.stringify(nextProps.effectiveActionData);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
function Loader() {
|
|
258
|
+
const { fetcher, isThemeSwitching } = useContext(DemoContext)
|
|
259
|
+
const location = useLocation()
|
|
260
|
+
const [inProgress, setInProgress] = useState(false)
|
|
261
|
+
|
|
262
|
+
const previousPathname = useRef(location.pathname)
|
|
263
|
+
|
|
264
|
+
const submitting = fetcher.state === "submitting" || fetcher.state === "loading"
|
|
265
|
+
const loading =
|
|
266
|
+
submitting ||
|
|
267
|
+
isThemeSwitching ||
|
|
268
|
+
inProgress
|
|
269
|
+
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
if (submitting) {
|
|
272
|
+
setInProgress(true)
|
|
273
|
+
}
|
|
274
|
+
}, [fetcher])
|
|
275
|
+
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
if (previousPathname.current !== location.pathname) {
|
|
278
|
+
setInProgress(false)
|
|
279
|
+
}
|
|
280
|
+
previousPathname.current = location.pathname
|
|
281
|
+
}, [location.pathname])
|
|
282
|
+
|
|
283
|
+
if (!loading) {
|
|
284
|
+
return null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return <div style={{
|
|
288
|
+
position: "fixed", // or absolute depending on your needs
|
|
289
|
+
top: 0,
|
|
290
|
+
left: 0,
|
|
291
|
+
right: 0,
|
|
292
|
+
bottom: 0,
|
|
293
|
+
width: "100%",
|
|
294
|
+
height: "100%",
|
|
295
|
+
display: "flex",
|
|
296
|
+
justifyContent: "center",
|
|
297
|
+
alignItems: "center",
|
|
298
|
+
zIndex: 1000,
|
|
299
|
+
background: "var(--white)"
|
|
300
|
+
}}>
|
|
301
|
+
<Spinner
|
|
302
|
+
accessibilityLabel="Loading..."
|
|
303
|
+
label={isThemeSwitching ? "Switching theme..." : "Loading..."}
|
|
304
|
+
show={true}
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function MemoizedActionDropdownExample() {
|
|
310
|
+
const { settings: globalSettings } = useContext(DemoContext)
|
|
311
|
+
return <ActionDropdownExample settings={globalSettings} />
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function ActionDropdownExample({ settings }: { settings: any }) {
|
|
315
|
+
const { example } = useContext(DemoContext)
|
|
316
|
+
const { setActionData } = useGlobalState();
|
|
317
|
+
const { actionData: globalActionData } = useGlobalState();
|
|
318
|
+
const navigate = useNavigate()
|
|
319
|
+
const { fetcher } = useContext(DemoContext)
|
|
320
|
+
const formRef = useRef(null)
|
|
321
|
+
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
if (fetcher.data && fetcher.state === "idle") {
|
|
324
|
+
// Preserve current theme settings when API returns data
|
|
325
|
+
const apiDataWithCurrentTheme = {
|
|
326
|
+
...fetcher.data,
|
|
327
|
+
settings: {
|
|
328
|
+
...fetcher.data.settings,
|
|
329
|
+
theme: settings.theme // Preserve current theme
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
setActionData(apiDataWithCurrentTheme)
|
|
333
|
+
let canonical = fetcher?.data?.references?.[0]?.canonical
|
|
334
|
+
if (canonical) {
|
|
335
|
+
canonical = canonical.startsWith("/") ? canonical : `/${canonical}`
|
|
336
|
+
if (canonical.endsWith("/")) {
|
|
337
|
+
canonical = canonical.slice(0, -1)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
navigate(`${DOCS_PREFIX}${canonical}`)
|
|
341
|
+
}
|
|
342
|
+
} else if (fetcher.state === "loading") {
|
|
343
|
+
// setActionData(data => ({
|
|
344
|
+
// references: [],
|
|
345
|
+
// groups: [],
|
|
346
|
+
// exampleType: "",
|
|
347
|
+
// settings: {
|
|
348
|
+
// ...settings,
|
|
349
|
+
// }
|
|
350
|
+
// // ...data,
|
|
351
|
+
// }))
|
|
352
|
+
// setActionData({
|
|
353
|
+
// references: [],
|
|
354
|
+
// settings: settings, // Use current settings instead of default SETTINGS
|
|
355
|
+
// groups: [],
|
|
356
|
+
// exampleType: ""
|
|
357
|
+
// })
|
|
358
|
+
}
|
|
359
|
+
}, [fetcher])
|
|
360
|
+
|
|
361
|
+
const loading = fetcher.state === "submitting"
|
|
362
|
+
const disabled = loading || !example?.url
|
|
363
|
+
|
|
364
|
+
function onSelect(example: any) {
|
|
365
|
+
fetcher.submit(
|
|
366
|
+
{
|
|
367
|
+
type: example?.type,
|
|
368
|
+
value: example?.value,
|
|
369
|
+
example: example?.url
|
|
370
|
+
},
|
|
371
|
+
{ action: "/api/try", method: "post" }
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<div className="banner-container">
|
|
377
|
+
<div className="banner-left">
|
|
378
|
+
<GithubStars settings={settings} />
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
<fetcher.Form method="POST" action="/api/try" ref={formRef}>
|
|
382
|
+
<input type="hidden" name="type" value={example?.type} />
|
|
383
|
+
<input type="hidden" name="value" value={example?.value} />
|
|
384
|
+
<input type="hidden" name="currentSettings" value={JSON.stringify(settings)} />
|
|
385
|
+
<Flex alignItems="center" gap={2}>
|
|
386
|
+
<UniformURLInput />
|
|
387
|
+
|
|
388
|
+
<Flex>
|
|
389
|
+
<Button type="submit" size="sm" text="Try!" disabled={disabled} />
|
|
390
|
+
</Flex>
|
|
391
|
+
|
|
392
|
+
<Flex width="100%">
|
|
393
|
+
<SelectPredefinedUniformURL onSelect={onSelect} />
|
|
394
|
+
</Flex>
|
|
395
|
+
{/* TODO: in the futures */}
|
|
396
|
+
<Flex width="100%">
|
|
397
|
+
<SelectTheme />
|
|
398
|
+
</Flex>
|
|
399
|
+
</Flex>
|
|
400
|
+
</fetcher.Form>
|
|
401
|
+
<div />
|
|
402
|
+
</div>
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function GithubStars({ settings }: { settings: any }) {
|
|
407
|
+
return <>
|
|
408
|
+
{
|
|
409
|
+
settings?.integrations?.apps?.githubStar && <>
|
|
410
|
+
<Text weight="bold">
|
|
411
|
+
Star us on GitHub ⭐️
|
|
412
|
+
</Text>
|
|
413
|
+
|
|
414
|
+
<GitHubButton
|
|
415
|
+
href={settings?.integrations?.apps?.githubStar?.href}
|
|
416
|
+
data-icon={settings?.integrations?.apps?.githubStar?.dataIcon || "octicon-star"}
|
|
417
|
+
data-size={settings?.integrations?.apps?.githubStar?.dataSize || "large"}
|
|
418
|
+
data-show-count={settings?.integrations?.apps?.githubStar?.dataShowCount || true}
|
|
419
|
+
aria-label={settings?.integrations?.apps?.githubStar?.ariaLabel}
|
|
420
|
+
>
|
|
421
|
+
{settings?.integrations?.apps?.githubStar?.title}
|
|
422
|
+
</GitHubButton>
|
|
423
|
+
</>
|
|
424
|
+
}
|
|
425
|
+
</>
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function SelectPredefinedUniformURL({
|
|
429
|
+
onSelect: onSelectCb
|
|
430
|
+
}: any) {
|
|
431
|
+
const { setExample, example } = useContext(DemoContext)
|
|
432
|
+
|
|
433
|
+
const exmaples = {
|
|
434
|
+
// openai: {
|
|
435
|
+
// value: "openai",
|
|
436
|
+
// // url: "https://raw.githubusercontent.com/openai/openai-openapi/refs/heads/master/openapi.yaml",
|
|
437
|
+
// // url: "https://app.stainless.com/api/spec/documented/openai/openapi.documented.yml",
|
|
438
|
+
// url: "https://raw.githubusercontent.com/openai/openai-openapi/refs/heads/manual_spec/openapi2.yaml",
|
|
439
|
+
// label: "OpenAI",
|
|
440
|
+
// type: "openapi"
|
|
441
|
+
// },
|
|
442
|
+
livesession: {
|
|
443
|
+
value: "livesession",
|
|
444
|
+
url: "https://raw.githubusercontent.com/livesession/livesession-openapi/master/openapi.yaml",
|
|
445
|
+
label: "Livesession",
|
|
446
|
+
type: "openapi"
|
|
447
|
+
},
|
|
448
|
+
vercel: {
|
|
449
|
+
value: "vercel",
|
|
450
|
+
url: "https://openapi.vercel.sh",
|
|
451
|
+
label: "Vercel",
|
|
452
|
+
type: "openapi"
|
|
453
|
+
},
|
|
454
|
+
intercom: {
|
|
455
|
+
value: "intercom",
|
|
456
|
+
url: "https://developers.intercom.com/_spec/docs/references/@2.11/rest-api/api.intercom.io.json",
|
|
457
|
+
label: "Intercom",
|
|
458
|
+
type: "openapi"
|
|
459
|
+
},
|
|
460
|
+
box: {
|
|
461
|
+
value: "box",
|
|
462
|
+
url: "https://raw.githubusercontent.com/box/box-openapi/main/openapi.json",
|
|
463
|
+
label: "Box",
|
|
464
|
+
type: "openapi"
|
|
465
|
+
},
|
|
466
|
+
// github: {
|
|
467
|
+
// value: "github",
|
|
468
|
+
// url: "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/ghes-3.0/ghes-3.0.json",
|
|
469
|
+
// label: "GitHub",
|
|
470
|
+
// type: "openapi"
|
|
471
|
+
// },
|
|
472
|
+
// digitalocean: {
|
|
473
|
+
// value: "digitalocean",
|
|
474
|
+
// url: "https://raw.githubusercontent.com/digitalocean/openapi/main/specification/DigitalOcean-public.v2.yaml",
|
|
475
|
+
// label: "DigitalOcean",
|
|
476
|
+
// type: "openapi"
|
|
477
|
+
// },
|
|
478
|
+
monday: {
|
|
479
|
+
value: "monday",
|
|
480
|
+
url: "https://api.monday.com/v2/get_schema?format=sdl",
|
|
481
|
+
label: "Monday.com",
|
|
482
|
+
type: "graphql"
|
|
483
|
+
},
|
|
484
|
+
braintree: {
|
|
485
|
+
value: "braintree",
|
|
486
|
+
url: "https://raw.githubusercontent.com/braintree/graphql-api/master/schema.graphql",
|
|
487
|
+
label: "Braintree",
|
|
488
|
+
type: "graphql"
|
|
489
|
+
},
|
|
490
|
+
githubgraphql: {
|
|
491
|
+
value: "githubgraphql",
|
|
492
|
+
url: "https://docs.github.com/public/fpt/schema.docs.graphql",
|
|
493
|
+
label: "GitHub",
|
|
494
|
+
type: "graphql"
|
|
495
|
+
},
|
|
496
|
+
artsy: {
|
|
497
|
+
value: "artsy",
|
|
498
|
+
url: "https://raw.githubusercontent.com/artsy/metaphysics/main/_schemaV2.graphql",
|
|
499
|
+
label: "Artsy",
|
|
500
|
+
type: "graphql"
|
|
501
|
+
}
|
|
502
|
+
} as any
|
|
503
|
+
|
|
504
|
+
const [open, setOpen] = useState(false);
|
|
505
|
+
const anchorRef = useRef(null);
|
|
506
|
+
// const [selected, setSelected] = useState(null)
|
|
507
|
+
|
|
508
|
+
const selected = Object.values(exmaples).find((entry: any) => entry.url === example?.url) as any
|
|
509
|
+
|
|
510
|
+
const onSelect = ({ item }: any) => {
|
|
511
|
+
// setSelected(item)
|
|
512
|
+
setOpen(false)
|
|
513
|
+
|
|
514
|
+
const example = exmaples[item?.value]
|
|
515
|
+
if (!example) return
|
|
516
|
+
|
|
517
|
+
setExample(example)
|
|
518
|
+
onSelectCb?.(example)
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
return <>
|
|
522
|
+
<Button
|
|
523
|
+
ref={anchorRef}
|
|
524
|
+
accessibilityControls="choose-example"
|
|
525
|
+
accessibilityExpanded={open}
|
|
526
|
+
accessibilityHaspopup
|
|
527
|
+
iconEnd="arrow-down"
|
|
528
|
+
onClick={() => setOpen((prevVal) => !prevVal)}
|
|
529
|
+
selected={open}
|
|
530
|
+
size="sm"
|
|
531
|
+
text={'Pick Example'}
|
|
532
|
+
/>
|
|
533
|
+
{open && (
|
|
534
|
+
<Dropdown
|
|
535
|
+
anchor={anchorRef.current}
|
|
536
|
+
id="choose-example"
|
|
537
|
+
onDismiss={() => setOpen(false)}
|
|
538
|
+
zIndex={new FixedZIndex(9999)}
|
|
539
|
+
>
|
|
540
|
+
|
|
541
|
+
{Object.values(exmaples).map((example: any) => (
|
|
542
|
+
<Dropdown.Item
|
|
543
|
+
onSelect={onSelect}
|
|
544
|
+
option={{ value: example.value, label: example.label }}
|
|
545
|
+
selected={{
|
|
546
|
+
value: selected?.value || "",
|
|
547
|
+
label: selected?.label || ""
|
|
548
|
+
}}
|
|
549
|
+
badge={{
|
|
550
|
+
text: example.type === "openapi" ? "OpenAPI" : "GraphQL",
|
|
551
|
+
type: example.type === "openapi" ? "success" : "recommendation"
|
|
552
|
+
}}
|
|
553
|
+
/>
|
|
554
|
+
))}
|
|
555
|
+
</Dropdown>
|
|
556
|
+
)}
|
|
557
|
+
</>
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function SelectTheme() {
|
|
561
|
+
const themes = [
|
|
562
|
+
{
|
|
563
|
+
value: "poetry",
|
|
564
|
+
label: "Poetry",
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
value: "opener",
|
|
568
|
+
label: "Opener",
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
value: "cosmo",
|
|
572
|
+
label: "Cosmo",
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
value: "picasso",
|
|
576
|
+
label: "Picasso",
|
|
577
|
+
},
|
|
578
|
+
]
|
|
579
|
+
const [open, setOpen] = useState(false);
|
|
580
|
+
const [selected, setSelected] = useState(null);
|
|
581
|
+
const anchorRef = useRef(null);
|
|
582
|
+
const { setActionData } = useGlobalState();
|
|
583
|
+
const { setIsThemeSwitching } = useContext(DemoContext);
|
|
584
|
+
|
|
585
|
+
const onSelect = async ({ item }: { item: any }) => {
|
|
586
|
+
setSelected(item);
|
|
587
|
+
setOpen(false);
|
|
588
|
+
setIsThemeSwitching(true);
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
// Update theme in settings
|
|
592
|
+
const newSettings = JSON.parse(JSON.stringify(SETTINGS));
|
|
593
|
+
newSettings.theme = {
|
|
594
|
+
...newSettings.theme,
|
|
595
|
+
name: item.value
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// Update global theme settings
|
|
599
|
+
(globalThis as any).__xydThemeSettings = newSettings.theme;
|
|
600
|
+
|
|
601
|
+
// Dynamically import theme CSS
|
|
602
|
+
const themeName = item.value;
|
|
603
|
+
|
|
604
|
+
// Create new theme style link first (but don't append yet)
|
|
605
|
+
const themeStyles = document.createElement('link');
|
|
606
|
+
themeStyles.rel = 'stylesheet';
|
|
607
|
+
let themeCss = poetryCss
|
|
608
|
+
if (themeName === "picasso") {
|
|
609
|
+
themeCss = picassoCss
|
|
610
|
+
} else if (themeName === "cosmo") {
|
|
611
|
+
themeCss = cosmoCss
|
|
612
|
+
} else if (themeName === "opener") {
|
|
613
|
+
themeCss = openerCss
|
|
614
|
+
} else if (themeName === "gusto") {
|
|
615
|
+
themeCss = gustoCss
|
|
616
|
+
} else if (themeName === "solar") {
|
|
617
|
+
themeCss = solarCss
|
|
618
|
+
}
|
|
619
|
+
themeStyles.href = themeCss
|
|
620
|
+
themeStyles.setAttribute('data-theme-style', 'true');
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
// Now that new CSS is loaded, remove old theme styles
|
|
624
|
+
const oldThemeStyles = document.querySelectorAll('link[data-theme-style]:not([href*="' + themeName + '"])');
|
|
625
|
+
oldThemeStyles.forEach(style => style.remove());
|
|
626
|
+
|
|
627
|
+
// Wait for CSS to load BEFORE removing old styles
|
|
628
|
+
await new Promise<void>((resolve, reject) => {
|
|
629
|
+
themeStyles.onload = () => resolve();
|
|
630
|
+
themeStyles.onerror = () => reject(new Error('Failed to load theme CSS'));
|
|
631
|
+
document.head.appendChild(themeStyles);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Remove default theme styles
|
|
635
|
+
const defaultThemeStyles = document.querySelectorAll('link[data-xyd-theme-default]');
|
|
636
|
+
defaultThemeStyles.forEach(style => style.remove());
|
|
637
|
+
|
|
638
|
+
// Small delay to ensure CSS is applied
|
|
639
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
640
|
+
|
|
641
|
+
// Update global state to trigger re-render with new theme
|
|
642
|
+
setActionData((prev: any) => ({
|
|
643
|
+
...prev,
|
|
644
|
+
settings: newSettings
|
|
645
|
+
}));
|
|
646
|
+
|
|
647
|
+
} catch (error) {
|
|
648
|
+
console.error('Error switching theme:', error);
|
|
649
|
+
} finally {
|
|
650
|
+
setIsThemeSwitching(false);
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
return <>
|
|
655
|
+
<Button
|
|
656
|
+
ref={anchorRef}
|
|
657
|
+
accessibilityControls="choose-theme"
|
|
658
|
+
accessibilityExpanded={open}
|
|
659
|
+
accessibilityHaspopup
|
|
660
|
+
iconEnd="arrow-down"
|
|
661
|
+
onClick={() => setOpen((prevVal) => !prevVal)}
|
|
662
|
+
selected={open}
|
|
663
|
+
size="sm"
|
|
664
|
+
text={selected ? selected.label : 'Pick Theme'}
|
|
665
|
+
/>
|
|
666
|
+
{
|
|
667
|
+
open && (
|
|
668
|
+
<Dropdown
|
|
669
|
+
anchor={anchorRef.current}
|
|
670
|
+
id="choose-theme"
|
|
671
|
+
onDismiss={() => setOpen(false)}
|
|
672
|
+
zIndex={new FixedZIndex(9999)}
|
|
673
|
+
>
|
|
674
|
+
{themes.map((theme) => (
|
|
675
|
+
<Dropdown.Item
|
|
676
|
+
key={theme.value}
|
|
677
|
+
onSelect={onSelect}
|
|
678
|
+
option={{ value: theme.value, label: theme.label }}
|
|
679
|
+
selected={selected}
|
|
680
|
+
/>
|
|
681
|
+
))}
|
|
682
|
+
</Dropdown>
|
|
683
|
+
)
|
|
684
|
+
}
|
|
685
|
+
</>
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function UniformURLInput() {
|
|
689
|
+
const { example, setExample } = useContext(DemoContext)
|
|
690
|
+
|
|
691
|
+
function handleChange(value: string) {
|
|
692
|
+
setExample({
|
|
693
|
+
...example,
|
|
694
|
+
url: value
|
|
695
|
+
})
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return (
|
|
699
|
+
<Flex
|
|
700
|
+
alignItems="center"
|
|
701
|
+
gap={4}
|
|
702
|
+
height="100%"
|
|
703
|
+
width="100%"
|
|
704
|
+
>
|
|
705
|
+
<Box width={400}>
|
|
706
|
+
<TextField
|
|
707
|
+
id="header-example"
|
|
708
|
+
onChange={({ value }) => {
|
|
709
|
+
handleChange(value);
|
|
710
|
+
}}
|
|
711
|
+
placeholder="URL to OpenAPI / GraphQL / React"
|
|
712
|
+
type="text"
|
|
713
|
+
size="sm"
|
|
714
|
+
value={example?.url || ""}
|
|
715
|
+
name="example"
|
|
716
|
+
/>
|
|
717
|
+
</Box>
|
|
718
|
+
</Flex>
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
|