@clidey/dory 0.22.0 → 0.23.0
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/bin/dory.ts +18 -4
- package/dist/dory.json +5 -0
- package/dist/favicon.svg +1 -1
- package/dist/index.html +6 -6
- package/dist/introduction.mdx +2 -2
- package/dist/llms.txt +2 -2
- package/docs/dory.json +5 -0
- package/docs/favicon.svg +1 -1
- package/docs/introduction.mdx +2 -2
- package/package.json +13 -10
- package/src/app.css +2 -0
- package/src/components/dropdown.tsx +30 -26
- package/src/components/loading.tsx +6 -8
- package/src/components/navigation.tsx +8 -6
- package/src/components/notification.tsx +9 -93
- package/src/components/routes.tsx +1 -1
- package/src/components/search.tsx +31 -104
- package/src/components/store.ts +4 -1
- package/src/main.tsx +9 -5
- package/src/mdx/accordion.tsx +21 -28
- package/src/mdx/api-playground.tsx +68 -83
- package/src/mdx/api.tsx +21 -21
- package/src/mdx/async-api.tsx +15 -31
- package/src/mdx/callout.tsx +27 -43
- package/src/mdx/card.tsx +20 -14
- package/src/mdx/code.tsx +26 -26
- package/src/mdx/expandable.tsx +21 -25
- package/src/mdx/fence.tsx +2 -2
- package/src/mdx/list.tsx +2 -2
- package/src/mdx/open-api.tsx +31 -45
- package/src/mdx/table.tsx +8 -3
- package/src/mdx/tag.tsx +27 -40
- package/src/mdx/text.tsx +3 -2
- package/src/mdx/websocket-playground.tsx +150 -81
- package/src/theme.scss +50 -106
- package/src/types/config.ts +47 -0
- package/src/ui/header.tsx +12 -9
- package/src/ui/layout.tsx +5 -4
- package/src/ui/mobile-navigation.tsx +36 -63
- package/src/ui/table-of-content.tsx +3 -3
- package/src/utils/colors.ts +54 -0
- package/src/utils/fonts.ts +4 -1
- package/vite.config.ts +2 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/__vite-browser-external-BIHI7g3E.js +0 -1
- package/dist/assets/_basePickBy-CsnA5_xx.js +0 -1
- package/dist/assets/_baseUniq-COwFna6s.js +0 -1
- package/dist/assets/api-playground-demo-Ct5XK2u1.js +0 -45
- package/dist/assets/api-playground-demo-vBae5R-d.js +0 -119
- package/dist/assets/arc-Dy4kITED.js +0 -1
- package/dist/assets/architectureDiagram-IEHRJDOE-BiaWQpzt.js +0 -36
- package/dist/assets/blockDiagram-JOT3LUYC-7LfUOsM7.js +0 -121
- package/dist/assets/c4Diagram-VJAJSXHY-Ca-3deDF.js +0 -10
- package/dist/assets/channel-j6jMzwSu.js +0 -1
- package/dist/assets/chunk-4BMEZGHF-Cln31Q_z.js +0 -1
- package/dist/assets/chunk-A2AXSNBT-Cj4U7K4I.js +0 -164
- package/dist/assets/chunk-AEK57VVT-1kn9120k.js +0 -220
- package/dist/assets/chunk-D6G4REZN-DMgQaOLS.js +0 -1
- package/dist/assets/chunk-RZ5BOZE2-DDd-DrSb.js +0 -1
- package/dist/assets/chunk-XZIHB7SX-sVdgMrEr.js +0 -1
- package/dist/assets/classDiagram-GIVACNV2-B9ObtIrc.js +0 -1
- package/dist/assets/classDiagram-v2-COTLJTTW-B9ObtIrc.js +0 -1
- package/dist/assets/clone-UOttdbrS.js +0 -1
- package/dist/assets/create-D-Wjvs-T.js +0 -6
- package/dist/assets/create-DpzlgYHc.js +0 -4
- package/dist/assets/cytoscape.esm-BY7v1xhs.js +0 -331
- package/dist/assets/dagre-OKDRZEBW-C4q12sCc.js +0 -4
- package/dist/assets/delete-DwAmDeAN.js +0 -4
- package/dist/assets/delete-cbziNMWs.js +0 -5
- package/dist/assets/diagram-SSKATNLV-BxNC6sGs.js +0 -43
- package/dist/assets/diagram-VNBRO52H-CukGDk3b.js +0 -24
- package/dist/assets/dory.jpeg +0 -0
- package/dist/assets/erDiagram-Q7BY3M3F-TiiVq0dv.js +0 -60
- package/dist/assets/favicon-Z67lNtJu.svg +0 -75
- package/dist/assets/features-6EEoDloV.js +0 -269
- package/dist/assets/features-EZwrzTnN.js +0 -441
- package/dist/assets/flowDiagram-4HSFHLVR-DykkGbol.js +0 -161
- package/dist/assets/ganttDiagram-APWFNJXF-Bqp95-S3.js +0 -257
- package/dist/assets/get-BeAg4Qrr.js +0 -5
- package/dist/assets/get-Dj1eDs2i.js +0 -4
- package/dist/assets/gitGraphDiagram-7IBYFJ6S-BiYVs9VI.js +0 -65
- package/dist/assets/graph-LkKKMaDZ.js +0 -1
- package/dist/assets/index-CGd7uPvh.css +0 -1
- package/dist/assets/index-vOROtq-e.js +0 -9596
- package/dist/assets/infoDiagram-PH2N3AL5-EA7h3wZa.js +0 -2
- package/dist/assets/init-Gi6I4Gst.js +0 -1
- package/dist/assets/introduction-CzXlzjWa.js +0 -38
- package/dist/assets/introduction-ylYJcPz3.js +0 -45
- package/dist/assets/journeyDiagram-U35MCT3I-BZiVuCZu.js +0 -138
- package/dist/assets/kanban-definition-NDS4AKOZ-SCdQ8OjR.js +0 -88
- package/dist/assets/layout-C9-9-M2q.js +0 -1
- package/dist/assets/linear-CTbdhYd1.js +0 -1
- package/dist/assets/mermaid.core-DUFqLK_I.js +0 -213
- package/dist/assets/mindmap-definition-ALO5MXBD-BPbKEbjk.js +0 -95
- package/dist/assets/ordinal-Cboi1Yqb.js +0 -1
- package/dist/assets/pieDiagram-IB7DONF6-DPg1sNm8.js +0 -30
- package/dist/assets/quadrantDiagram-7GDLP6J5-DmtJM50d.js +0 -7
- package/dist/assets/radar-MK3ICKWK-DnO5LA4B.js +0 -128
- package/dist/assets/receive-chat-message-B2jsY1P6.js +0 -4
- package/dist/assets/receive-chat-message-BeaC4eqS.js +0 -5
- package/dist/assets/receive-notifications-BouRLPFj.js +0 -4
- package/dist/assets/receive-notifications-DYnXRRBz.js +0 -5
- package/dist/assets/receive-presence-update-CRgeSUpk.js +0 -5
- package/dist/assets/receive-presence-update-UEm47KAG.js +0 -4
- package/dist/assets/requirementDiagram-KVF5MWMF-iNwUJLq2.js +0 -64
- package/dist/assets/sankeyDiagram-QLVOVGJD-DWLyAD59.js +0 -10
- package/dist/assets/send-chat-message-Bvf2XJiv.js +0 -5
- package/dist/assets/send-chat-message-FDmVETeD.js +0 -4
- package/dist/assets/send-presence-update-26clGQ0z.js +0 -5
- package/dist/assets/send-presence-update-DeywSKYu.js +0 -4
- package/dist/assets/sequenceDiagram-X6HHIX6F-HyRN2_g-.js +0 -122
- package/dist/assets/stateDiagram-DGXRK772-DdMr0V93.js +0 -1
- package/dist/assets/stateDiagram-v2-YXO3MK2T-D6kpXRiR.js +0 -1
- package/dist/assets/timeline-definition-BDJGKUSR-CDf8nWgC.js +0 -61
- package/dist/assets/update-BEeNDeXY.js +0 -4
- package/dist/assets/update-CykJMYvN.js +0 -5
- package/dist/assets/websocket-playground-demo-4pj7mj19.js +0 -135
- package/dist/assets/websocket-playground-demo-F71LN60e.js +0 -61
- package/dist/assets/xychartDiagram-VJFVF3MP-BY_uTq9r.js +0 -7
- package/docs/assets/dory.jpeg +0 -0
- package/src/components/dark-mode-toggle.tsx +0 -25
- package/src/index.css +0 -24
- package/src/utils/hooks.ts +0 -46
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Button, TextArea, useTheme, Input } from '@clidey/ux'
|
|
2
|
+
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
|
|
2
3
|
import { CheckIcon } from '@heroicons/react/24/outline'
|
|
3
4
|
import classNames from 'classnames'
|
|
4
|
-
import { PencilIcon, TrashIcon, XIcon } from 'lucide-react'
|
|
5
|
+
import { PencilIcon, PlusIcon, TrashIcon, XIcon } from 'lucide-react'
|
|
5
6
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
|
6
7
|
import { Highlight, themes } from 'prism-react-renderer'
|
|
7
8
|
import Dropdown from '../components/dropdown'
|
|
8
|
-
import { useDarkMode } from '../utils/hooks'
|
|
9
9
|
import { Fence } from './fence'
|
|
10
10
|
import { Tag } from './tag'
|
|
11
11
|
|
|
@@ -166,39 +166,39 @@ export const HeadersTab = ({ requestConfig, setRequestConfig }: HeadersTabProps)
|
|
|
166
166
|
value={hdr.key}
|
|
167
167
|
onChange={e => updateHeader(idx, 'key', (e.target as HTMLInputElement).value)}
|
|
168
168
|
placeholder="Header name"
|
|
169
|
-
className="w-32
|
|
169
|
+
className="w-32 text-sm"
|
|
170
170
|
/>
|
|
171
171
|
<Input
|
|
172
172
|
type="text"
|
|
173
173
|
value={hdr.value}
|
|
174
174
|
onChange={e => updateHeader(idx, 'value', (e.target as HTMLInputElement).value)}
|
|
175
175
|
placeholder="Header value"
|
|
176
|
-
className="flex-1
|
|
176
|
+
className="flex-1 text-sm"
|
|
177
177
|
/>
|
|
178
|
-
<
|
|
178
|
+
<Button onClick={() => saveHeader(idx)}>
|
|
179
179
|
<CheckIcon className="w-4 h-4" />
|
|
180
|
-
</
|
|
181
|
-
<
|
|
180
|
+
</Button>
|
|
181
|
+
<Button onClick={() => cancelEdit(idx)}>
|
|
182
182
|
<XIcon className="w-4 h-4" />
|
|
183
|
-
</
|
|
183
|
+
</Button>
|
|
184
184
|
</>
|
|
185
185
|
) : (
|
|
186
186
|
<>
|
|
187
|
-
<span className="text-sm
|
|
188
|
-
<span className="text-sm
|
|
189
|
-
<
|
|
187
|
+
<span className="text-sm w-32 truncate">{hdr.key}</span>
|
|
188
|
+
<span className="text-sm flex-1 truncate">{hdr.value}</span>
|
|
189
|
+
<Button onClick={() => editHeader(idx)}>
|
|
190
190
|
<PencilIcon className="w-4 h-4" />
|
|
191
|
-
</
|
|
192
|
-
<
|
|
191
|
+
</Button>
|
|
192
|
+
<Button onClick={() => removeHeader(idx)} className="text-red-400">
|
|
193
193
|
<TrashIcon className="w-4 h-4" />
|
|
194
|
-
</
|
|
194
|
+
</Button>
|
|
195
195
|
</>
|
|
196
196
|
)}
|
|
197
197
|
</div>
|
|
198
198
|
))}
|
|
199
|
-
<
|
|
200
|
-
|
|
201
|
-
</
|
|
199
|
+
<Button onClick={addEmptyHeaderRow} className="text-sm mt-2 w-fit">
|
|
200
|
+
<PlusIcon className="w-4 h-4" /> Add Header
|
|
201
|
+
</Button>
|
|
202
202
|
</div>
|
|
203
203
|
)
|
|
204
204
|
}
|
|
@@ -281,32 +281,32 @@ export const ParamsTab = ({ queryParams, setQueryParams }: ParamsTabProps) => {
|
|
|
281
281
|
<div key={i} className="flex items-center gap-2">
|
|
282
282
|
{p.isEditing ? (
|
|
283
283
|
<>
|
|
284
|
-
<Input value={p.name} onChange={e => updateParam(i, 'name', (e.target as HTMLInputElement).value)} placeholder="Param name" className="w-32
|
|
285
|
-
<Input value={p.value} onChange={e => updateParam(i, 'value', (e.target as HTMLInputElement).value)} placeholder="Param value" className="flex-1
|
|
286
|
-
<
|
|
284
|
+
<Input value={p.name} onChange={e => updateParam(i, 'name', (e.target as HTMLInputElement).value)} placeholder="Param name" className="w-32 text-sm" />
|
|
285
|
+
<Input value={p.value} onChange={e => updateParam(i, 'value', (e.target as HTMLInputElement).value)} placeholder="Param value" className="flex-1 text-sm" />
|
|
286
|
+
<Button onClick={() => saveParam(i)}>
|
|
287
287
|
<CheckIcon className="w-4 h-4" />
|
|
288
|
-
</
|
|
289
|
-
<
|
|
288
|
+
</Button>
|
|
289
|
+
<Button onClick={() => cancelEdit(i)}>
|
|
290
290
|
<XIcon className="w-4 h-4" />
|
|
291
|
-
</
|
|
291
|
+
</Button>
|
|
292
292
|
</>
|
|
293
293
|
) : (
|
|
294
294
|
<>
|
|
295
|
-
<span className="w-32 truncate text-sm
|
|
296
|
-
<span className="flex-1 truncate text-sm
|
|
297
|
-
<
|
|
295
|
+
<span className="w-32 truncate text-sm">{p.name}</span>
|
|
296
|
+
<span className="flex-1 truncate text-sm">{p.value}</span>
|
|
297
|
+
<Button onClick={() => editParam(i)}>
|
|
298
298
|
<PencilIcon className="w-4 h-4" />
|
|
299
|
-
</
|
|
300
|
-
<
|
|
299
|
+
</Button>
|
|
300
|
+
<Button onClick={() => removeParam(i)} className="text-red-400">
|
|
301
301
|
<TrashIcon className="w-4 h-4" />
|
|
302
|
-
</
|
|
302
|
+
</Button>
|
|
303
303
|
</>
|
|
304
304
|
)}
|
|
305
305
|
</div>
|
|
306
306
|
))}
|
|
307
|
-
<
|
|
308
|
-
|
|
309
|
-
</
|
|
307
|
+
<Button onClick={addParamRow} className="mt-2 text-sm self-end">
|
|
308
|
+
<PlusIcon className="w-4 h-4" /> Add Parameter
|
|
309
|
+
</Button>
|
|
310
310
|
</div>
|
|
311
311
|
);
|
|
312
312
|
};
|
|
@@ -327,7 +327,7 @@ export function APIPlayground({
|
|
|
327
327
|
const [isLoading, setIsLoading] = useState(false)
|
|
328
328
|
const [response, setResponse] = useState<ResponseData | null>(null)
|
|
329
329
|
const [error, setError] = useState<string | null>(null)
|
|
330
|
-
const
|
|
330
|
+
const isDark = useTheme().theme === 'dark';
|
|
331
331
|
const [selectedServer, setSelectedServer] = useState<Server | null>(servers?.[0] || null)
|
|
332
332
|
|
|
333
333
|
// Request configuration state
|
|
@@ -430,39 +430,26 @@ export function APIPlayground({
|
|
|
430
430
|
return (
|
|
431
431
|
<div className="my-6 overflow-hidden rounded-2xl">
|
|
432
432
|
{/* Header */}
|
|
433
|
-
<div className="flex min-h-[calc(--spacing(12)+1px)] flex-wrap items-start gap-x-4
|
|
433
|
+
<div className="flex min-h-[calc(--spacing(12)+1px)] flex-wrap items-start gap-x-4 px-4 pt-2 pb-4 justify-between">
|
|
434
434
|
<div className="flex items-center gap-3 pt-3">
|
|
435
435
|
<Tag variant="small">{method.toUpperCase()}</Tag>
|
|
436
|
-
<span className="font-mono text-sm
|
|
436
|
+
<span className="font-mono text-sm">{title || url}</span>
|
|
437
437
|
</div>
|
|
438
|
-
<
|
|
438
|
+
<Button
|
|
439
439
|
onClick={executeRequest}
|
|
440
|
-
disabled={isLoading}
|
|
441
|
-
className={classNames(
|
|
442
|
-
'ml-auto mt-3 flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition',
|
|
443
|
-
isLoading
|
|
444
|
-
? 'bg-zinc-600 text-zinc-400 cursor-not-allowed'
|
|
445
|
-
: 'bg-sky-600 text-white hover:bg-sky-700'
|
|
446
|
-
)}
|
|
447
|
-
>
|
|
448
|
-
{isLoading ? (
|
|
449
|
-
<LoadingSpinner className="h-4 w-4" />
|
|
450
|
-
) : (
|
|
451
|
-
<PlayButton className="h-4 w-4" />
|
|
452
|
-
)}
|
|
440
|
+
disabled={isLoading}>
|
|
453
441
|
{isLoading ? 'Sending...' : 'Send Request'}
|
|
454
|
-
|
|
442
|
+
{isLoading ? <LoadingSpinner className="h-4 w-4" /> : <PlayButton className="h-4 w-4" />}
|
|
443
|
+
</Button>
|
|
455
444
|
</div>
|
|
456
445
|
|
|
457
446
|
{description && (
|
|
458
|
-
<
|
|
459
|
-
<p className="text-sm text-zinc-300">{description}</p>
|
|
460
|
-
</div>
|
|
447
|
+
<p className="text-sm px-4 py-3">{description}</p>
|
|
461
448
|
)}
|
|
462
449
|
|
|
463
450
|
<div className="flex flex-col gap-4">
|
|
464
451
|
<div className="flex flex-col gap-2">
|
|
465
|
-
<label className="text-sm font-medium
|
|
452
|
+
<label className="text-sm font-medium">Server</label>
|
|
466
453
|
<Dropdown
|
|
467
454
|
buttonLabel={selectedServer?.description || 'Select Server'}
|
|
468
455
|
items={servers.map(server => ({ label: server.description || server.url, onClick: () => setSelectedServer(server) }))}
|
|
@@ -471,9 +458,9 @@ export function APIPlayground({
|
|
|
471
458
|
</div>
|
|
472
459
|
|
|
473
460
|
{/* Request Configuration */}
|
|
474
|
-
<div className="flex-1
|
|
461
|
+
<div className="flex-1">
|
|
475
462
|
<TabGroup selectedIndex={selectedTab} onChange={setSelectedTab}>
|
|
476
|
-
<TabList className="flex
|
|
463
|
+
<TabList className="flex">
|
|
477
464
|
{tabs.map((tab) => (
|
|
478
465
|
<Tab
|
|
479
466
|
key={tab.key}
|
|
@@ -481,7 +468,7 @@ export function APIPlayground({
|
|
|
481
468
|
classNames(
|
|
482
469
|
'px-4 py-3 text-sm font-medium transition border-b-2',
|
|
483
470
|
selected
|
|
484
|
-
? 'border-
|
|
471
|
+
? 'border-brand-foreground text-brand-foreground'
|
|
485
472
|
: 'border-transparent text-zinc-400 hover:text-zinc-300'
|
|
486
473
|
)
|
|
487
474
|
}
|
|
@@ -510,18 +497,18 @@ export function APIPlayground({
|
|
|
510
497
|
<TabPanel className="p-4">
|
|
511
498
|
{['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) ? (
|
|
512
499
|
<div className="flex flex-col space-y-2">
|
|
513
|
-
<label className="text-sm font-medium
|
|
500
|
+
<label className="text-sm font-medium">Request Body (JSON)</label>
|
|
514
501
|
<Highlight
|
|
515
502
|
code={requestConfig.body || ''}
|
|
516
503
|
language="json"
|
|
517
504
|
theme={isDark ? themes.vsDark : themes.vsLight}>
|
|
518
505
|
{({ className, style }) => (
|
|
519
|
-
<
|
|
506
|
+
<TextArea
|
|
520
507
|
value={requestConfig.body}
|
|
521
508
|
onChange={(e) => setRequestConfig(prev => ({ ...prev, body: (e.target as HTMLTextAreaElement).value }))}
|
|
522
509
|
placeholder="Enter JSON body..."
|
|
523
510
|
rows={8}
|
|
524
|
-
className={`w-full
|
|
511
|
+
className={`w-full text-sm font-mono ${className}`}
|
|
525
512
|
style={style}
|
|
526
513
|
/>
|
|
527
514
|
)}
|
|
@@ -535,7 +522,7 @@ export function APIPlayground({
|
|
|
535
522
|
{/* Auth Tab */}
|
|
536
523
|
<TabPanel className="p-4 space-y-4">
|
|
537
524
|
<div className="flex flex-col space-y-2">
|
|
538
|
-
<label className="text-sm font-medium
|
|
525
|
+
<label className="text-sm font-medium">Authentication Type</label>
|
|
539
526
|
<Dropdown
|
|
540
527
|
buttonLabel={auth.type === 'none' ? 'Select Auth Type' : auth.type.charAt(0).toUpperCase() + auth.type.slice(1)}
|
|
541
528
|
items={[
|
|
@@ -550,26 +537,26 @@ export function APIPlayground({
|
|
|
550
537
|
|
|
551
538
|
{auth.type === 'bearer' && (
|
|
552
539
|
<div className="space-y-2">
|
|
553
|
-
<label className="text-sm font-medium
|
|
554
|
-
<
|
|
540
|
+
<label className="text-sm font-medium">Bearer Token</label>
|
|
541
|
+
<Input
|
|
555
542
|
type="password"
|
|
556
543
|
value={auth.token}
|
|
557
544
|
onChange={(e) => setAuth(prev => ({ ...prev, token: (e.target as HTMLInputElement).value }))}
|
|
558
545
|
placeholder="Enter bearer token..."
|
|
559
|
-
className="w-full
|
|
546
|
+
className="w-full text-sm"
|
|
560
547
|
/>
|
|
561
548
|
</div>
|
|
562
549
|
)}
|
|
563
550
|
|
|
564
551
|
{auth.type === 'apiKey' && (
|
|
565
552
|
<div className="space-y-2">
|
|
566
|
-
<label className="text-sm font-medium
|
|
567
|
-
<
|
|
553
|
+
<label className="text-sm font-medium">API Key</label>
|
|
554
|
+
<Input
|
|
568
555
|
type="password"
|
|
569
556
|
value={auth.apiKey}
|
|
570
557
|
onChange={(e) => setAuth(prev => ({ ...prev, apiKey: (e.target as HTMLInputElement).value }))}
|
|
571
558
|
placeholder="Enter API key..."
|
|
572
|
-
className="w-full
|
|
559
|
+
className="w-full text-sm"
|
|
573
560
|
/>
|
|
574
561
|
</div>
|
|
575
562
|
)}
|
|
@@ -577,23 +564,23 @@ export function APIPlayground({
|
|
|
577
564
|
{auth.type === 'basic' && (
|
|
578
565
|
<div className="space-y-4">
|
|
579
566
|
<div className="space-y-2">
|
|
580
|
-
<label className="text-sm font-medium
|
|
581
|
-
<
|
|
567
|
+
<label className="text-sm font-medium">Username</label>
|
|
568
|
+
<Input
|
|
582
569
|
type="text"
|
|
583
570
|
value={auth.username}
|
|
584
571
|
onChange={(e) => setAuth(prev => ({ ...prev, username: (e.target as HTMLInputElement).value }))}
|
|
585
572
|
placeholder="Enter username..."
|
|
586
|
-
className="w-full
|
|
573
|
+
className="w-full text-sm"
|
|
587
574
|
/>
|
|
588
575
|
</div>
|
|
589
576
|
<div className="space-y-2">
|
|
590
|
-
<label className="text-sm font-medium
|
|
591
|
-
<
|
|
577
|
+
<label className="text-sm font-medium">Password</label>
|
|
578
|
+
<Input
|
|
592
579
|
type="password"
|
|
593
580
|
value={auth.password}
|
|
594
581
|
onChange={(e) => setAuth(prev => ({ ...prev, password: (e.target as HTMLInputElement).value }))}
|
|
595
582
|
placeholder="Enter password..."
|
|
596
|
-
className="w-full
|
|
583
|
+
className="w-full text-sm"
|
|
597
584
|
/>
|
|
598
585
|
</div>
|
|
599
586
|
</div>
|
|
@@ -605,8 +592,8 @@ export function APIPlayground({
|
|
|
605
592
|
|
|
606
593
|
{/* Response */}
|
|
607
594
|
<div className="flex-1">
|
|
608
|
-
<div className="
|
|
609
|
-
<h3 className="text-sm font-semibold
|
|
595
|
+
<div className="px-4 py-3">
|
|
596
|
+
<h3 className="text-sm font-semibold">Response</h3>
|
|
610
597
|
</div>
|
|
611
598
|
|
|
612
599
|
<div className="p-4">
|
|
@@ -638,7 +625,7 @@ export function APIPlayground({
|
|
|
638
625
|
</div>
|
|
639
626
|
|
|
640
627
|
<div className="space-y-2">
|
|
641
|
-
<h4 className="text-sm font-medium
|
|
628
|
+
<h4 className="text-sm font-medium">Response Body</h4>
|
|
642
629
|
<Fence language="json">
|
|
643
630
|
{typeof response.data === 'string'
|
|
644
631
|
? response.data
|
|
@@ -648,7 +635,7 @@ export function APIPlayground({
|
|
|
648
635
|
</div>
|
|
649
636
|
|
|
650
637
|
<div className="space-y-2">
|
|
651
|
-
<h4 className="text-sm font-medium
|
|
638
|
+
<h4 className="text-sm font-medium">Response Headers</h4>
|
|
652
639
|
<div className="space-y-1">
|
|
653
640
|
{Object.entries(response.headers).map(([key, value]) => (
|
|
654
641
|
<div key={key} className="flex text-xs">
|
|
@@ -660,11 +647,9 @@ export function APIPlayground({
|
|
|
660
647
|
</div>
|
|
661
648
|
</div>
|
|
662
649
|
) : (
|
|
663
|
-
<
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
</div>
|
|
667
|
-
</div>
|
|
650
|
+
<p className="text-sm text-center py-8 text-muted-foreground">
|
|
651
|
+
Click"Send Request" to see the response
|
|
652
|
+
</p>
|
|
668
653
|
)}
|
|
669
654
|
</div>
|
|
670
655
|
</div>
|
package/src/mdx/api.tsx
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
useState,
|
|
11
11
|
} from 'react'
|
|
12
12
|
import { Tag } from './tag'
|
|
13
|
+
import { CheckIcon } from 'lucide-react'
|
|
14
|
+
import { Button, Card } from '@clidey/ux'
|
|
13
15
|
|
|
14
16
|
const languageNames: Record<string, string> = {
|
|
15
17
|
js: 'JavaScript',
|
|
@@ -68,8 +70,7 @@ function CopyButton({ code }: { code: string }) {
|
|
|
68
70
|
}, [copyCount])
|
|
69
71
|
|
|
70
72
|
return (
|
|
71
|
-
<
|
|
72
|
-
type="button"
|
|
73
|
+
<Button
|
|
73
74
|
className={classNames(
|
|
74
75
|
'group/button absolute top-3.5 right-4 overflow-hidden rounded-full py-1 pr-3 pl-2 text-2xs font-medium opacity-0 backdrop-blur-sm transition group-hover:opacity-100 focus:opacity-100',
|
|
75
76
|
copied
|
|
@@ -89,8 +90,7 @@ function CopyButton({ code }: { code: string }) {
|
|
|
89
90
|
copied && '-translate-y-1.5 opacity-0',
|
|
90
91
|
)}
|
|
91
92
|
>
|
|
92
|
-
<ClipboardIcon className="h-
|
|
93
|
-
Copy
|
|
93
|
+
<ClipboardIcon className="h-4 w-4 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400" />
|
|
94
94
|
</span>
|
|
95
95
|
<span
|
|
96
96
|
aria-hidden={!copied}
|
|
@@ -99,9 +99,9 @@ function CopyButton({ code }: { code: string }) {
|
|
|
99
99
|
!copied && 'translate-y-1.5 opacity-0',
|
|
100
100
|
)}
|
|
101
101
|
>
|
|
102
|
-
|
|
102
|
+
<CheckIcon className="h-4 w-4" />
|
|
103
103
|
</span>
|
|
104
|
-
</
|
|
104
|
+
</Button>
|
|
105
105
|
)
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -111,7 +111,7 @@ function APIPanelHeader({ tag, label }: { tag?: string; label?: string }) {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
return (
|
|
114
|
-
<div className="flex h-9 items-center gap-2
|
|
114
|
+
<div className="flex h-9 items-center gap-2 px-4">
|
|
115
115
|
{tag && (
|
|
116
116
|
<div className="dark flex">
|
|
117
117
|
<Tag variant="small">{tag}</Tag>
|
|
@@ -153,7 +153,7 @@ function APIPanel({
|
|
|
153
153
|
<div className="group dark:bg-white/2.5">
|
|
154
154
|
<APIPanelHeader tag={tag} label={label} />
|
|
155
155
|
<div className="relative">
|
|
156
|
-
<pre className="overflow-x-auto p-4 text-xs
|
|
156
|
+
<pre className="overflow-x-auto p-4 text-xs">{children}</pre>
|
|
157
157
|
{/* @ts-expect-error code is not typed */}
|
|
158
158
|
<CopyButton code={code} />
|
|
159
159
|
</div>
|
|
@@ -177,9 +177,9 @@ function APIGroupHeader({
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
return (
|
|
180
|
-
<div className="flex min-h-[calc(--spacing(12)+1px)] flex-wrap items-start gap-x-4
|
|
180
|
+
<div className="flex min-h-[calc(--spacing(12)+1px)] flex-wrap items-start gap-x-4 px-4 pt-2">
|
|
181
181
|
{title && (
|
|
182
|
-
<h3 className="mr-auto pt-3 text-xs font-semibold
|
|
182
|
+
<h3 className="mr-auto pt-3 text-xs font-semibold">
|
|
183
183
|
{title}
|
|
184
184
|
</h3>
|
|
185
185
|
)}
|
|
@@ -188,7 +188,7 @@ function APIGroupHeader({
|
|
|
188
188
|
{Children.map(children, (child, childIndex) => (
|
|
189
189
|
<Tab
|
|
190
190
|
className={classNames(
|
|
191
|
-
'
|
|
191
|
+
'pt-2 transition data-selected:not-data-focus:outline-hidden',
|
|
192
192
|
childIndex === selectedIndex
|
|
193
193
|
? 'border-sky-500 text-sky-400'
|
|
194
194
|
: 'border-transparent text-zinc-400 hover:text-zinc-300',
|
|
@@ -303,8 +303,6 @@ export function APIGroup({
|
|
|
303
303
|
const tabGroupProps = useTabGroupProps(languages)
|
|
304
304
|
const hasTabs = Children.count(children) > 1
|
|
305
305
|
|
|
306
|
-
const containerClassName =
|
|
307
|
-
'my-6 overflow-hidden rounded-2xl bg-zinc-900 shadow-md dark:ring-1 dark:ring-white/10 w-full'
|
|
308
306
|
const header = (
|
|
309
307
|
<APIGroupHeader title={title} selectedIndex={tabGroupProps.selectedIndex}>
|
|
310
308
|
{children}
|
|
@@ -315,19 +313,21 @@ export function APIGroup({
|
|
|
315
313
|
return (
|
|
316
314
|
<APIGroupContext.Provider value={true}>
|
|
317
315
|
{hasTabs ? (
|
|
318
|
-
<
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
316
|
+
<Card className="py-0 border-black/10 bg-none overflow-hidden mb-4">
|
|
317
|
+
<TabGroup {...tabGroupProps}>
|
|
318
|
+
<div className="not-prose">
|
|
319
|
+
{header}
|
|
320
|
+
{panels}
|
|
321
|
+
</div>
|
|
322
|
+
</TabGroup>
|
|
323
|
+
</Card>
|
|
324
324
|
) : (
|
|
325
|
-
<
|
|
325
|
+
<Card className="py-0 border-black/10 bg-none overflow-hidden mb-4">
|
|
326
326
|
<div className="not-prose">
|
|
327
327
|
{header}
|
|
328
328
|
{panels}
|
|
329
329
|
</div>
|
|
330
|
-
</
|
|
330
|
+
</Card>
|
|
331
331
|
)}
|
|
332
332
|
</APIGroupContext.Provider>
|
|
333
333
|
)
|
package/src/mdx/async-api.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { motion } from "motion/react"
|
|
2
1
|
import { useState } from "preact/hooks"
|
|
3
2
|
import { APIGroup } from "./api"
|
|
4
3
|
import { PlayButton } from "./api-playground"
|
|
@@ -7,6 +6,7 @@ import { Properties, Property } from "./open-api"
|
|
|
7
6
|
import { Tag } from "./tag"
|
|
8
7
|
import { Col, Row } from "./text"
|
|
9
8
|
import { WebSocketPlayground, type WebSocketServer } from "./websocket-playground"
|
|
9
|
+
import { Button, Sheet, SheetContent, SheetHeader, SheetTitle } from "@clidey/ux"
|
|
10
10
|
|
|
11
11
|
interface AsyncAPIMessage {
|
|
12
12
|
name?: string
|
|
@@ -191,33 +191,17 @@ export function AsyncAPI({
|
|
|
191
191
|
|
|
192
192
|
return (
|
|
193
193
|
<div className="max-w-none w-full">
|
|
194
|
-
{showPlayground
|
|
195
|
-
<
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
<button
|
|
206
|
-
onClick={() => setShowPlayground(false)}
|
|
207
|
-
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
|
208
|
-
>
|
|
209
|
-
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
210
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
211
|
-
</svg>
|
|
212
|
-
</button>
|
|
213
|
-
</div>
|
|
214
|
-
<WebSocketPlayground
|
|
215
|
-
url={selectedChannel}
|
|
216
|
-
servers={wsServers}
|
|
217
|
-
/>
|
|
218
|
-
</div>
|
|
219
|
-
</motion.div>
|
|
220
|
-
)}
|
|
194
|
+
<Sheet open={showPlayground} onOpenChange={setShowPlayground} modal={true}>
|
|
195
|
+
<SheetContent side="bottom" className="h-[90vh] w-full overflow-y-auto p-8">
|
|
196
|
+
<SheetHeader className="p-0">
|
|
197
|
+
<SheetTitle>WebSocket Playground</SheetTitle>
|
|
198
|
+
</SheetHeader>
|
|
199
|
+
<WebSocketPlayground
|
|
200
|
+
url={selectedChannel}
|
|
201
|
+
servers={wsServers}
|
|
202
|
+
/>
|
|
203
|
+
</SheetContent>
|
|
204
|
+
</Sheet>
|
|
221
205
|
|
|
222
206
|
{Object.entries(channels).map(([channelPath, channelDetails]) => {
|
|
223
207
|
if (channelPath !== selectedChannel) {
|
|
@@ -340,13 +324,13 @@ export function AsyncAPI({
|
|
|
340
324
|
)}
|
|
341
325
|
</Col>
|
|
342
326
|
<Col sticky>
|
|
343
|
-
<
|
|
327
|
+
<Button
|
|
344
328
|
onClick={handleShowPlayground}
|
|
345
|
-
className='ml-auto mt-3 flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium
|
|
329
|
+
className='ml-auto mt-3 flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium'
|
|
346
330
|
>
|
|
347
331
|
Try
|
|
348
332
|
<PlayButton className="h-4 w-4" />
|
|
349
|
-
</
|
|
333
|
+
</Button>
|
|
350
334
|
<APIGroup title="Message" tag={selectedOperation.toUpperCase()} label={channelPath}>
|
|
351
335
|
<>
|
|
352
336
|
{resolvedMessage?.payload && (() => {
|
package/src/mdx/callout.tsx
CHANGED
|
@@ -1,40 +1,28 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Alert, AlertTitle, AlertDescription } from '@clidey/ux'
|
|
2
2
|
import { Icon } from "./icon"
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
note:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
container: 'my-6 flex gap-2.5 rounded-2xl border border-amber-500/20 bg-amber-50/50 p-4 text-sm/6 text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/5 dark:text-amber-200 dark:[--tw-prose-links-hover:var(--color-amber-300)] dark:[--tw-prose-links:var(--color-white)]',
|
|
12
|
-
title: 'text-amber-900 dark:text-amber-200',
|
|
13
|
-
body: 'text-amber-900 dark:text-amber-200',
|
|
14
|
-
},
|
|
15
|
-
info: {
|
|
16
|
-
container: 'my-6 flex gap-2.5 rounded-2xl border border-blue-500/20 bg-blue-50/50 p-4 text-sm/6 text-blue-900 dark:border-blue-500/30 dark:bg-blue-500/5 dark:text-blue-200 dark:[--tw-prose-links-hover:var(--color-blue-300)] dark:[--tw-prose-links:var(--color-white)]',
|
|
17
|
-
title: 'text-blue-900 dark:text-blue-200',
|
|
18
|
-
body: 'text-blue-900 dark:text-blue-200',
|
|
19
|
-
},
|
|
20
|
-
tip: {
|
|
21
|
-
container: 'my-6 flex gap-2.5 rounded-2xl border border-green-500/20 bg-green-50/50 p-4 text-sm/6 text-green-900 dark:border-green-500/30 dark:bg-green-500/5 dark:text-green-200 dark:[--tw-prose-links-hover:var(--color-green-300)] dark:[--tw-prose-links:var(--color-white)]',
|
|
22
|
-
title: 'text-green-900 dark:text-green-200',
|
|
23
|
-
body: 'text-green-900 dark:text-green-200',
|
|
24
|
-
},
|
|
25
|
-
check: {
|
|
26
|
-
container: 'my-6 flex gap-2.5 rounded-2xl border border-purple-500/20 bg-purple-50/50 p-4 text-sm/6 text-purple-900 dark:border-purple-500/30 dark:bg-purple-500/5 dark:text-purple-200 dark:[--tw-prose-links-hover:var(--color-purple-300)] dark:[--tw-prose-links:var(--color-white)]',
|
|
27
|
-
title: 'text-purple-900 dark:text-purple-200',
|
|
28
|
-
body: 'text-purple-900 dark:text-purple-200',
|
|
29
|
-
},
|
|
30
|
-
}
|
|
4
|
+
const typeToVariant = {
|
|
5
|
+
note: 'default',
|
|
6
|
+
warning: 'destructive',
|
|
7
|
+
info: 'default',
|
|
8
|
+
tip: 'default',
|
|
9
|
+
check: 'default',
|
|
10
|
+
} as const
|
|
31
11
|
|
|
32
12
|
const icons = {
|
|
33
|
-
note:
|
|
34
|
-
warning:
|
|
35
|
-
info:
|
|
36
|
-
tip:
|
|
37
|
-
check:
|
|
13
|
+
note: 'InfoCircle',
|
|
14
|
+
warning: 'AlertTriangle',
|
|
15
|
+
info: 'InfoCircle',
|
|
16
|
+
tip: 'Lightbulb',
|
|
17
|
+
check: 'CheckCircle',
|
|
18
|
+
} as const
|
|
19
|
+
|
|
20
|
+
const typeColors = {
|
|
21
|
+
note: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-900 dark:text-emerald-200',
|
|
22
|
+
warning: 'border-amber-500/30 bg-amber-500/10 text-amber-900 dark:text-amber-200 *:data-[slot=alert-description]:text-inherit dark:data-[slot=alert-description]:text-inherit',
|
|
23
|
+
info: 'border-blue-500/30 bg-blue-500/10 text-blue-900 dark:text-blue-200',
|
|
24
|
+
tip: 'border-green-500/30 bg-green-500/10 text-green-900 dark:text-green-200',
|
|
25
|
+
check: 'border-purple-500/30 bg-purple-500/10 text-purple-900 dark:text-purple-200',
|
|
38
26
|
}
|
|
39
27
|
|
|
40
28
|
export function Callout({
|
|
@@ -44,18 +32,14 @@ export function Callout({
|
|
|
44
32
|
}: {
|
|
45
33
|
title: string
|
|
46
34
|
children: React.ReactNode
|
|
47
|
-
type?: keyof typeof
|
|
35
|
+
type?: keyof typeof icons
|
|
48
36
|
}) {
|
|
49
|
-
const IconComponent = icons[type]
|
|
50
|
-
|
|
51
37
|
return (
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
38
|
+
<Alert variant={typeToVariant[type]} className={`my-6 ${typeColors[type]}`}>
|
|
39
|
+
<Icon icon={icons[type]} className="h-4 w-4" />
|
|
40
|
+
<AlertTitle>{title}</AlertTitle>
|
|
41
|
+
<AlertDescription>{children}</AlertDescription>
|
|
42
|
+
</Alert>
|
|
59
43
|
)
|
|
60
44
|
}
|
|
61
45
|
|