@tscircuit/fake-snippets 0.0.43 → 0.0.44

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.
@@ -0,0 +1,490 @@
1
+ import React from "react"
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
3
+ import { ChevronRight } from "lucide-react"
4
+ import { cva } from "class-variance-authority"
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const treeVariants = cva(
8
+ "group hover:before:opacity-100 before:absolute before:rounded-lg before:left-0 before:w-full before:opacity-0 before:bg-slate-100/70 before:h-[2rem] before:-z-10' dark:before:bg-slate-800/70",
9
+ )
10
+
11
+ const selectedTreeVariants = cva(
12
+ "before:opacity-100 before:bg-slate-100/70 text-accent-foreground' dark:before:bg-slate-800/70",
13
+ )
14
+
15
+ const dragOverVariants = cva(
16
+ "before:opacity-100 before:bg-slate-900/20 text-primary-foreground' dark:before:bg-slate-50/20",
17
+ )
18
+
19
+ interface TreeDataItem {
20
+ id: string
21
+ name: string
22
+ icon?: any
23
+ selectedIcon?: any
24
+ openIcon?: any
25
+ children?: TreeDataItem[]
26
+ actions?: React.ReactNode
27
+ onClick?: () => void
28
+ draggable?: boolean
29
+ droppable?: boolean
30
+ }
31
+
32
+ type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
33
+ data: TreeDataItem[] | TreeDataItem
34
+ initialSelectedItemId?: string
35
+ onSelectChange?: (item: TreeDataItem | undefined) => void
36
+ expandAll?: boolean
37
+ defaultNodeIcon?: any
38
+ defaultLeafIcon?: any
39
+ onDocumentDrag?: (sourceItem: TreeDataItem, targetItem: TreeDataItem) => void
40
+ }
41
+
42
+ const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
43
+ (
44
+ {
45
+ data,
46
+ initialSelectedItemId,
47
+ onSelectChange,
48
+ expandAll,
49
+ defaultLeafIcon,
50
+ defaultNodeIcon,
51
+ className,
52
+ onDocumentDrag,
53
+ ...props
54
+ },
55
+ ref,
56
+ ) => {
57
+ const [selectedItemId, setSelectedItemId] = React.useState<
58
+ string | undefined
59
+ >(initialSelectedItemId)
60
+
61
+ const [draggedItem, setDraggedItem] = React.useState<TreeDataItem | null>(
62
+ null,
63
+ )
64
+
65
+ const handleSelectChange = React.useCallback(
66
+ (item: TreeDataItem | undefined) => {
67
+ setSelectedItemId(item?.id)
68
+ if (onSelectChange) {
69
+ onSelectChange(item)
70
+ }
71
+ },
72
+ [onSelectChange],
73
+ )
74
+
75
+ const handleDragStart = React.useCallback((item: TreeDataItem) => {
76
+ setDraggedItem(item)
77
+ }, [])
78
+
79
+ const handleDrop = React.useCallback(
80
+ (targetItem: TreeDataItem) => {
81
+ if (draggedItem && onDocumentDrag && draggedItem.id !== targetItem.id) {
82
+ onDocumentDrag(draggedItem, targetItem)
83
+ }
84
+ setDraggedItem(null)
85
+ },
86
+ [draggedItem, onDocumentDrag],
87
+ )
88
+
89
+ const expandedItemIds = React.useMemo(() => {
90
+ if (!initialSelectedItemId) {
91
+ return [] as string[]
92
+ }
93
+
94
+ const ids: string[] = []
95
+
96
+ function walkTreeItems(
97
+ items: TreeDataItem[] | TreeDataItem,
98
+ targetId: string,
99
+ ) {
100
+ if (items instanceof Array) {
101
+ for (let i = 0; i < items.length; i++) {
102
+ ids.push(items[i]!.id)
103
+ if (walkTreeItems(items[i]!, targetId) && !expandAll) {
104
+ return true
105
+ }
106
+ if (!expandAll) ids.pop()
107
+ }
108
+ } else if (!expandAll && items.id === targetId) {
109
+ return true
110
+ } else if (items.children) {
111
+ return walkTreeItems(items.children, targetId)
112
+ }
113
+ }
114
+
115
+ walkTreeItems(data, initialSelectedItemId)
116
+ return ids
117
+ }, [data, expandAll, initialSelectedItemId])
118
+
119
+ return (
120
+ <div className={cn("overflow-hidden relative p-2", className)}>
121
+ <TreeItem
122
+ data={data}
123
+ ref={ref}
124
+ selectedItemId={selectedItemId}
125
+ handleSelectChange={handleSelectChange}
126
+ expandedItemIds={expandedItemIds}
127
+ defaultLeafIcon={defaultLeafIcon}
128
+ defaultNodeIcon={defaultNodeIcon}
129
+ handleDragStart={handleDragStart}
130
+ handleDrop={handleDrop}
131
+ draggedItem={draggedItem}
132
+ {...props}
133
+ />
134
+ <div
135
+ className="w-full h-[48px]"
136
+ onDrop={(e) => {
137
+ handleDrop({ id: "", name: "parent_div" })
138
+ }}
139
+ ></div>
140
+ </div>
141
+ )
142
+ },
143
+ )
144
+ TreeView.displayName = "TreeView"
145
+
146
+ type TreeItemProps = TreeProps & {
147
+ selectedItemId?: string
148
+ handleSelectChange: (item: TreeDataItem | undefined) => void
149
+ expandedItemIds: string[]
150
+ defaultNodeIcon?: any
151
+ defaultLeafIcon?: any
152
+ handleDragStart?: (item: TreeDataItem) => void
153
+ handleDrop?: (item: TreeDataItem) => void
154
+ draggedItem: TreeDataItem | null
155
+ }
156
+
157
+ const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
158
+ (
159
+ {
160
+ className,
161
+ data,
162
+ selectedItemId,
163
+ handleSelectChange,
164
+ expandedItemIds,
165
+ defaultNodeIcon,
166
+ defaultLeafIcon,
167
+ handleDragStart,
168
+ handleDrop,
169
+ draggedItem,
170
+ ...props
171
+ },
172
+ ref,
173
+ ) => {
174
+ if (!(data instanceof Array)) {
175
+ data = [data]
176
+ }
177
+ return (
178
+ <div ref={ref} role="tree" className={className} {...props}>
179
+ <ul>
180
+ {data.map((item) => (
181
+ <li key={item.id}>
182
+ {item.children ? (
183
+ <TreeNode
184
+ item={item}
185
+ selectedItemId={selectedItemId}
186
+ expandedItemIds={expandedItemIds}
187
+ handleSelectChange={handleSelectChange}
188
+ defaultNodeIcon={defaultNodeIcon}
189
+ defaultLeafIcon={defaultLeafIcon}
190
+ handleDragStart={handleDragStart}
191
+ handleDrop={handleDrop}
192
+ draggedItem={draggedItem}
193
+ />
194
+ ) : (
195
+ <TreeLeaf
196
+ item={item}
197
+ selectedItemId={selectedItemId}
198
+ handleSelectChange={handleSelectChange}
199
+ defaultLeafIcon={defaultLeafIcon}
200
+ handleDragStart={handleDragStart}
201
+ handleDrop={handleDrop}
202
+ draggedItem={draggedItem}
203
+ />
204
+ )}
205
+ </li>
206
+ ))}
207
+ </ul>
208
+ </div>
209
+ )
210
+ },
211
+ )
212
+ TreeItem.displayName = "TreeItem"
213
+
214
+ const TreeNode = ({
215
+ item,
216
+ handleSelectChange,
217
+ expandedItemIds,
218
+ selectedItemId,
219
+ defaultNodeIcon,
220
+ defaultLeafIcon,
221
+ handleDragStart,
222
+ handleDrop,
223
+ draggedItem,
224
+ }: {
225
+ item: TreeDataItem
226
+ handleSelectChange: (item: TreeDataItem | undefined) => void
227
+ expandedItemIds: string[]
228
+ selectedItemId?: string
229
+ defaultNodeIcon?: any
230
+ defaultLeafIcon?: any
231
+ handleDragStart?: (item: TreeDataItem) => void
232
+ handleDrop?: (item: TreeDataItem) => void
233
+ draggedItem: TreeDataItem | null
234
+ }) => {
235
+ const [value, setValue] = React.useState(
236
+ expandedItemIds.includes(item.id) ? [item.id] : [],
237
+ )
238
+ const [isDragOver, setIsDragOver] = React.useState(false)
239
+
240
+ const onDragStart = (e: React.DragEvent) => {
241
+ if (!item.draggable) {
242
+ e.preventDefault()
243
+ return
244
+ }
245
+ e.dataTransfer.setData("text/plain", item.id)
246
+ handleDragStart?.(item)
247
+ }
248
+
249
+ const onDragOver = (e: React.DragEvent) => {
250
+ if (item.droppable !== false && draggedItem && draggedItem.id !== item.id) {
251
+ e.preventDefault()
252
+ setIsDragOver(true)
253
+ }
254
+ }
255
+
256
+ const onDragLeave = () => {
257
+ setIsDragOver(false)
258
+ }
259
+
260
+ const onDrop = (e: React.DragEvent) => {
261
+ e.preventDefault()
262
+ setIsDragOver(false)
263
+ handleDrop?.(item)
264
+ }
265
+
266
+ return (
267
+ <AccordionPrimitive.Root
268
+ type="multiple"
269
+ value={value}
270
+ onValueChange={(s) => setValue(s)}
271
+ >
272
+ <AccordionPrimitive.Item value={item.id}>
273
+ <AccordionTrigger
274
+ className={cn(
275
+ treeVariants(),
276
+ selectedItemId === item.id && selectedTreeVariants(),
277
+ isDragOver && dragOverVariants(),
278
+ )}
279
+ onClick={() => {
280
+ handleSelectChange(item)
281
+ item.onClick?.()
282
+ }}
283
+ draggable={!!item.draggable}
284
+ onDragStart={onDragStart}
285
+ onDragOver={onDragOver}
286
+ onDragLeave={onDragLeave}
287
+ onDrop={onDrop}
288
+ >
289
+ <TreeIcon
290
+ item={item}
291
+ isSelected={selectedItemId === item.id}
292
+ isOpen={value.includes(item.id)}
293
+ default={defaultNodeIcon}
294
+ />
295
+ <span className="text-sm truncate">{item.name}</span>
296
+ <TreeActions isSelected={selectedItemId === item.id}>
297
+ {item.actions}
298
+ </TreeActions>
299
+ </AccordionTrigger>
300
+ <AccordionContent className="ml-4 pl-1 border-l">
301
+ <TreeItem
302
+ data={item.children ? item.children : item}
303
+ selectedItemId={selectedItemId}
304
+ handleSelectChange={handleSelectChange}
305
+ expandedItemIds={expandedItemIds}
306
+ defaultLeafIcon={defaultLeafIcon}
307
+ defaultNodeIcon={defaultNodeIcon}
308
+ handleDragStart={handleDragStart}
309
+ handleDrop={handleDrop}
310
+ draggedItem={draggedItem}
311
+ />
312
+ </AccordionContent>
313
+ </AccordionPrimitive.Item>
314
+ </AccordionPrimitive.Root>
315
+ )
316
+ }
317
+
318
+ const TreeLeaf = React.forwardRef<
319
+ HTMLDivElement,
320
+ React.HTMLAttributes<HTMLDivElement> & {
321
+ item: TreeDataItem
322
+ selectedItemId?: string
323
+ handleSelectChange: (item: TreeDataItem | undefined) => void
324
+ defaultLeafIcon?: any
325
+ handleDragStart?: (item: TreeDataItem) => void
326
+ handleDrop?: (item: TreeDataItem) => void
327
+ draggedItem: TreeDataItem | null
328
+ }
329
+ >(
330
+ (
331
+ {
332
+ className,
333
+ item,
334
+ selectedItemId,
335
+ handleSelectChange,
336
+ defaultLeafIcon,
337
+ handleDragStart,
338
+ handleDrop,
339
+ draggedItem,
340
+ ...props
341
+ },
342
+ ref,
343
+ ) => {
344
+ const [isDragOver, setIsDragOver] = React.useState(false)
345
+
346
+ const onDragStart = (e: React.DragEvent) => {
347
+ if (!item.draggable) {
348
+ e.preventDefault()
349
+ return
350
+ }
351
+ e.dataTransfer.setData("text/plain", item.id)
352
+ handleDragStart?.(item)
353
+ }
354
+
355
+ const onDragOver = (e: React.DragEvent) => {
356
+ if (
357
+ item.droppable !== false &&
358
+ draggedItem &&
359
+ draggedItem.id !== item.id
360
+ ) {
361
+ e.preventDefault()
362
+ setIsDragOver(true)
363
+ }
364
+ }
365
+
366
+ const onDragLeave = () => {
367
+ setIsDragOver(false)
368
+ }
369
+
370
+ const onDrop = (e: React.DragEvent) => {
371
+ e.preventDefault()
372
+ setIsDragOver(false)
373
+ handleDrop?.(item)
374
+ }
375
+
376
+ return (
377
+ <div
378
+ ref={ref}
379
+ className={cn(
380
+ "ml-[2px] flex text-left items-center py-2 cursor-pointer before:right-1",
381
+ treeVariants(),
382
+ className,
383
+ selectedItemId === item.id && selectedTreeVariants(),
384
+ isDragOver && dragOverVariants(),
385
+ )}
386
+ onClick={() => {
387
+ handleSelectChange(item)
388
+ item.onClick?.()
389
+ }}
390
+ draggable={!!item.draggable}
391
+ onDragStart={onDragStart}
392
+ onDragOver={onDragOver}
393
+ onDragLeave={onDragLeave}
394
+ onDrop={onDrop}
395
+ {...props}
396
+ >
397
+ <TreeIcon
398
+ item={item}
399
+ isSelected={selectedItemId === item.id}
400
+ default={defaultLeafIcon}
401
+ />
402
+ <span className="flex-grow text-sm truncate">{item.name}</span>
403
+ <TreeActions isSelected={selectedItemId === item.id}>
404
+ {item.actions}
405
+ </TreeActions>
406
+ </div>
407
+ )
408
+ },
409
+ )
410
+ TreeLeaf.displayName = "TreeLeaf"
411
+
412
+ const AccordionTrigger = React.forwardRef<
413
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
414
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
415
+ >(({ className, children, ...props }, ref) => (
416
+ <AccordionPrimitive.Header>
417
+ <AccordionPrimitive.Trigger
418
+ ref={ref}
419
+ className={cn(
420
+ "flex flex-1 w-full items-center py-2 transition-all first:[&[data-state=open]>svg]:rotate-90",
421
+ className,
422
+ )}
423
+ {...props}
424
+ >
425
+ <ChevronRight className="h-4 w-4 shrink-0 transition-transform duration-200 text-slate-900/50 mr-1 dark:text-slate-50/50" />
426
+ {children}
427
+ </AccordionPrimitive.Trigger>
428
+ </AccordionPrimitive.Header>
429
+ ))
430
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
431
+
432
+ const AccordionContent = React.forwardRef<
433
+ React.ElementRef<typeof AccordionPrimitive.Content>,
434
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
435
+ >(({ className, children, ...props }, ref) => (
436
+ <AccordionPrimitive.Content
437
+ ref={ref}
438
+ className={cn(
439
+ "overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
440
+ className,
441
+ )}
442
+ {...props}
443
+ >
444
+ <div className="pb-1 pt-0">{children}</div>
445
+ </AccordionPrimitive.Content>
446
+ ))
447
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
448
+
449
+ const TreeIcon = ({
450
+ item,
451
+ isOpen,
452
+ isSelected,
453
+ default: defaultIcon,
454
+ }: {
455
+ item: TreeDataItem
456
+ isOpen?: boolean
457
+ isSelected?: boolean
458
+ default?: any
459
+ }) => {
460
+ let Icon = defaultIcon
461
+ if (isSelected && item.selectedIcon) {
462
+ Icon = item.selectedIcon
463
+ } else if (isOpen && item.openIcon) {
464
+ Icon = item.openIcon
465
+ } else if (item.icon) {
466
+ Icon = item.icon
467
+ }
468
+ return Icon ? <Icon className="h-4 w-4 shrink-0 mr-2" /> : <></>
469
+ }
470
+
471
+ const TreeActions = ({
472
+ children,
473
+ isSelected,
474
+ }: {
475
+ children: React.ReactNode
476
+ isSelected: boolean
477
+ }) => {
478
+ return (
479
+ <div
480
+ className={cn(
481
+ isSelected ? "block" : "hidden",
482
+ "absolute right-3 group-hover:block",
483
+ )}
484
+ >
485
+ {children}
486
+ </div>
487
+ )
488
+ }
489
+
490
+ export { TreeView, type TreeDataItem }
@@ -0,0 +1,23 @@
1
+ import { useQuery } from "react-query"
2
+ import { useAxios } from "@/hooks/use-axios"
3
+ import type { Package, Snippet } from "fake-snippets-api/lib/db/schema"
4
+
5
+ export const usePackage = (packageId: string | null) => {
6
+ const axios = useAxios()
7
+ return useQuery<Package, Error & { status: number }>(
8
+ ["packages", packageId],
9
+ async () => {
10
+ if (!packageId) {
11
+ throw new Error("Package ID is required")
12
+ }
13
+ const { data } = await axios.get("/packages/get", {
14
+ params: { package_id: packageId },
15
+ })
16
+ return data.package
17
+ },
18
+ {
19
+ enabled: Boolean(packageId),
20
+ retry: false,
21
+ },
22
+ )
23
+ }
@@ -0,0 +1,49 @@
1
+ import { useMutation } from "react-query"
2
+ import { useGlobalStore } from "@/hooks/use-global-store"
3
+ import { useAxios } from "@/hooks/use-axios"
4
+ import { useToast } from "@/hooks/use-toast"
5
+ import { Package } from "fake-snippets-api/lib/db/schema"
6
+
7
+ export const useForkPackageMutation = ({
8
+ pkg,
9
+ currentCode,
10
+ onSuccess,
11
+ }: {
12
+ pkg: Package
13
+ currentCode?: string
14
+ onSuccess?: (forkedSnippet: Package) => void
15
+ }) => {
16
+ const axios = useAxios()
17
+ const session = useGlobalStore((s) => s.session)
18
+ const { toast } = useToast()
19
+
20
+ return useMutation(
21
+ ["createForkSnippet"],
22
+ async () => {
23
+ if (!session) throw new Error("No session")
24
+ if (!pkg) throw new Error("No package to fork")
25
+
26
+ const { data } = await axios.post("/packages/fork", {
27
+ package_id: pkg?.package_id,
28
+ })
29
+ return data.package
30
+ },
31
+ {
32
+ onSuccess: (forkedPkg: Package) => {
33
+ toast({
34
+ title: `Forked Package`,
35
+ description: `You have successfully forked the package. Redirecting...`,
36
+ })
37
+ onSuccess?.(forkedPkg)
38
+ },
39
+ onError: (error: any) => {
40
+ console.error("Error forking package:", error)
41
+ toast({
42
+ title: "Error",
43
+ description: "Failed to fork package. Please try again.",
44
+ variant: "destructive",
45
+ })
46
+ },
47
+ },
48
+ )
49
+ }
@@ -0,0 +1,56 @@
1
+ import { useQueries } from "react-query"
2
+ import { useAxios } from "./use-axios"
3
+ import type { Package } from "fake-snippets-api/lib/db/schema"
4
+ import { usePackageFileById, usePackageFiles } from "./use-package-files"
5
+
6
+ export interface PackageFile {
7
+ path: string
8
+ content: string
9
+ }
10
+
11
+ export function usePackageFilesLoader(pkg?: Package) {
12
+ const axios = useAxios()
13
+ const pkgFiles = usePackageFiles(pkg?.latest_package_release_id)
14
+ const indexFileFromHook = usePackageFileById(
15
+ pkgFiles.data?.find((x) => x.file_path === "index.tsx")?.package_file_id ??
16
+ null,
17
+ )
18
+
19
+ const queries = useQueries(
20
+ pkgFiles.data?.map((file) => ({
21
+ queryKey: ["packageFile", file.package_file_id],
22
+ queryFn: async () => {
23
+ if (
24
+ file.file_path === "index.tsx" &&
25
+ indexFileFromHook.data?.content_text
26
+ ) {
27
+ return {
28
+ path: file.file_path,
29
+ content: indexFileFromHook.data.content_text,
30
+ }
31
+ }
32
+
33
+ const response = await axios.post(`/package_files/get`, {
34
+ package_file_id: file.package_file_id,
35
+ })
36
+ const content = response.data.package_file?.content_text ?? ""
37
+ return content ? { path: file.file_path, content } : null
38
+ },
39
+ staleTime: 2,
40
+ })) ?? [],
41
+ )
42
+
43
+ const isLoading = queries.some((query) => query.isLoading)
44
+ const error = queries.find((query) => query.error)?.error
45
+
46
+ const processedResults = queries
47
+ .map((query) => query.data)
48
+ .filter((x): x is PackageFile => x !== null)
49
+ .filter(Boolean)
50
+
51
+ return {
52
+ isLoading,
53
+ error,
54
+ data: processedResults,
55
+ }
56
+ }
@@ -0,0 +1,86 @@
1
+ import { useMutation } from "react-query"
2
+ import type { Package } from "fake-snippets-api/lib/db/schema"
3
+
4
+ interface PackageFile {
5
+ path: string
6
+ content: string
7
+ }
8
+
9
+ interface UseUpdatePackageFilesMutationProps {
10
+ pkg: Package | undefined
11
+ pkgFilesWithContent: PackageFile[]
12
+ initialFilesLoad: PackageFile[]
13
+ pkgFiles: any
14
+ axios: any
15
+ toast: any
16
+ loadPkgFiles: () => void
17
+ }
18
+
19
+ export function useUpdatePackageFilesMutation({
20
+ pkg,
21
+ pkgFilesWithContent,
22
+ initialFilesLoad,
23
+ pkgFiles,
24
+ axios,
25
+ toast,
26
+ loadPkgFiles,
27
+ }: UseUpdatePackageFilesMutationProps) {
28
+ return useMutation({
29
+ mutationFn: async (
30
+ newpackage: Pick<Package, "package_id" | "name"> & {
31
+ package_name_with_version: string
32
+ },
33
+ ) => {
34
+ if (pkg) {
35
+ newpackage = { ...pkg, ...newpackage }
36
+ }
37
+ if (!newpackage) throw new Error("No package to update")
38
+
39
+ let updatedFilesCount = 0
40
+
41
+ for (const file of pkgFilesWithContent) {
42
+ const initialFile = initialFilesLoad.find((x) => x.path === file.path)
43
+ if (file.content && file.content !== initialFile?.content) {
44
+ const updatePkgFilePayload = {
45
+ package_file_id:
46
+ pkgFiles.data?.find((x: any) => x.file_path === file.path)
47
+ ?.package_file_id ?? null,
48
+ content_text: file.content,
49
+ file_path: file.path,
50
+ package_name_with_version: `${newpackage.name}`,
51
+ }
52
+
53
+ const response = await axios.post(
54
+ "/package_files/create_or_update",
55
+ updatePkgFilePayload,
56
+ )
57
+
58
+ if (response.status === 200) {
59
+ updatedFilesCount++
60
+ }
61
+ }
62
+ }
63
+ return updatedFilesCount
64
+ },
65
+ onSuccess: (updatedFilesCount) => {
66
+ if (updatedFilesCount) {
67
+ toast({
68
+ title: `Package's ${updatedFilesCount} files saved`,
69
+ description: "Your changes have been saved successfully.",
70
+ })
71
+ loadPkgFiles()
72
+ }
73
+ },
74
+ onError: (error: any) => {
75
+ console.error("Error updating pkg files:", error)
76
+ toast({
77
+ title: "Error",
78
+ description:
79
+ error instanceof Error
80
+ ? error.message
81
+ : "Failed to update package files. Please try again.",
82
+ variant: "destructive",
83
+ })
84
+ },
85
+ })
86
+ }