@epic-web/workshop-app 6.77.0 → 6.77.2
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/build/client/assets/{_exerciseNumber-C1aZpAWV.js → _exerciseNumber-JuXb6JTN.js} +2 -2
- package/build/client/assets/{_exerciseNumber-C1aZpAWV.js.map → _exerciseNumber-JuXb6JTN.js.map} +1 -1
- package/build/client/assets/{_exerciseNumber_.finished-BICggZV1.js → _exerciseNumber_.finished-DD-1txyR.js} +2 -2
- package/build/client/assets/{_exerciseNumber_.finished-BICggZV1.js.map → _exerciseNumber_.finished-DD-1txyR.js.map} +1 -1
- package/build/client/assets/_extra-BfK-kKBL.js +2 -0
- package/build/client/assets/_extra-BfK-kKBL.js.map +1 -0
- package/build/client/assets/{_layout-BZI3FoQD.js → _layout-BsHVX3B_.js} +2 -2
- package/build/client/assets/{_layout-BZI3FoQD.js.map → _layout-BsHVX3B_.js.map} +1 -1
- package/build/client/assets/{_layout-CunOyAg4.js → _layout-CDyeQVKG.js} +2 -2
- package/build/client/assets/{_layout-CunOyAg4.js.map → _layout-CDyeQVKG.js.map} +1 -1
- package/build/client/assets/_layout-mVyOtxmC.js +2 -0
- package/build/client/assets/_layout-mVyOtxmC.js.map +1 -0
- package/build/client/assets/{account-C96cpeZR.js → account-BKXMYhfO.js} +2 -2
- package/build/client/assets/{account-C96cpeZR.js.map → account-BKXMYhfO.js.map} +1 -1
- package/build/client/assets/{app-CJniokic.js → app-AWsnszeo.js} +2 -2
- package/build/client/assets/{app-CJniokic.js.map → app-AWsnszeo.js.map} +1 -1
- package/build/client/assets/{cache-CAr50MIB.js → cache-CxMFS2A_.js} +2 -2
- package/build/client/assets/{cache-CAr50MIB.js.map → cache-CxMFS2A_.js.map} +1 -1
- package/build/client/assets/{db-DAnX-T3_.js → db-BZ-tFzMp.js} +2 -2
- package/build/client/assets/{db-DAnX-T3_.js.map → db-BZ-tFzMp.js.map} +1 -1
- package/build/client/assets/{diff-D8TUonC2.js → diff-0VO9C_Sf.js} +2 -2
- package/build/client/assets/{diff-D8TUonC2.js.map → diff-0VO9C_Sf.js.map} +1 -1
- package/build/client/assets/{diff-Col_iM3X.js → diff-CC_jaH50.js} +2 -2
- package/build/client/assets/{diff-Col_iM3X.js.map → diff-CC_jaH50.js.map} +1 -1
- package/build/client/assets/discord-BVfxP4Rd.js +2 -0
- package/build/client/assets/discord-BVfxP4Rd.js.map +1 -0
- package/build/client/assets/{discord-BJkw0IrB.js → discord-XhHqPI49.js} +2 -2
- package/build/client/assets/{discord-BJkw0IrB.js.map → discord-XhHqPI49.js.map} +1 -1
- package/build/client/assets/{epic-video-BJW6MU1i.js → epic-video-D4jBdSKs.js} +2 -2
- package/build/client/assets/{epic-video-BJW6MU1i.js.map → epic-video-D4jBdSKs.js.map} +1 -1
- package/build/client/assets/{epic-video-jtxu_0bl.js → epic-video-Ddc9rYLT.js} +2 -2
- package/build/client/assets/{epic-video-jtxu_0bl.js.map → epic-video-Ddc9rYLT.js.map} +1 -1
- package/build/client/assets/{finished-C2jP5uE7.js → finished-BSfM_f4M.js} +2 -2
- package/build/client/assets/{finished-C2jP5uE7.js.map → finished-BSfM_f4M.js.map} +1 -1
- package/build/client/assets/{index-CdzVFL-Z.js → index-BrVvTrwg.js} +2 -2
- package/build/client/assets/{index-CdzVFL-Z.js.map → index-BrVvTrwg.js.map} +1 -1
- package/build/client/assets/{index-Dpyv8N6m.js → index-CfPaTqRT.js} +2 -2
- package/build/client/assets/{index-Dpyv8N6m.js.map → index-CfPaTqRT.js.map} +1 -1
- package/build/client/assets/{index-X8or9u7t.js → index-CuCZuiBp.js} +2 -2
- package/build/client/assets/{index-X8or9u7t.js.map → index-CuCZuiBp.js.map} +1 -1
- package/build/client/assets/{index-KRgoKRWG.js → index-D0vH1MiQ.js} +2 -2
- package/build/client/assets/{index-KRgoKRWG.js.map → index-D0vH1MiQ.js.map} +1 -1
- package/build/client/assets/{index-BCQgVrao.js → index-DTC_5pri.js} +2 -2
- package/build/client/assets/{index-BCQgVrao.js.map → index-DTC_5pri.js.map} +1 -1
- package/build/client/assets/{launch-editor-D2exGfVu.js → launch-editor-0oPpbFQe.js} +2 -2
- package/build/client/assets/{launch-editor-D2exGfVu.js.map → launch-editor-0oPpbFQe.js.map} +1 -1
- package/build/client/assets/{loading-CDNzW5oO.js → loading-CaCCsk9k.js} +2 -2
- package/build/client/assets/{loading-CDNzW5oO.js.map → loading-CaCCsk9k.js.map} +1 -1
- package/build/client/assets/{login-mWjVXGbJ.js → login-w4y7RVYa.js} +2 -2
- package/build/client/assets/{login-mWjVXGbJ.js.map → login-w4y7RVYa.js.map} +1 -1
- package/build/client/assets/{manifest-dd28a0e5.js → manifest-a63db2d2.js} +1 -1
- package/build/client/assets/{mdx-DdpoOTHm.js → mdx-DwvWT4Ou.js} +2 -2
- package/build/client/assets/{mdx-DdpoOTHm.js.map → mdx-DwvWT4Ou.js.map} +1 -1
- package/build/client/assets/{onboarding-indicator-B-XR90_G.js → onboarding-indicator-BkeHYs2C.js} +2 -2
- package/build/client/assets/{onboarding-indicator-B-XR90_G.js.map → onboarding-indicator-BkeHYs2C.js.map} +1 -1
- package/build/client/assets/{online-DiNLkgTC.js → online-DVk-W8Cr.js} +2 -2
- package/build/client/assets/{online-DiNLkgTC.js.map → online-DVk-W8Cr.js.map} +1 -1
- package/build/client/assets/{playground-DmEAkxG1.js → playground-DG4B62WX.js} +2 -2
- package/build/client/assets/{playground-DmEAkxG1.js.map → playground-DG4B62WX.js.map} +1 -1
- package/build/client/assets/{playground-window-x2mQ5o1O.js → playground-window-CF8lTXXI.js} +2 -2
- package/build/client/assets/{playground-window-x2mQ5o1O.js.map → playground-window-CF8lTXXI.js.map} +1 -1
- package/build/client/assets/{preferences-B7ND1VS9.js → preferences-Czy5oqWs.js} +2 -2
- package/build/client/assets/{preferences-B7ND1VS9.js.map → preferences-Czy5oqWs.js.map} +1 -1
- package/build/client/assets/{presence-VCvV2mg7.js → presence-Brtc_BN7.js} +2 -2
- package/build/client/assets/{presence-VCvV2mg7.js.map → presence-Brtc_BN7.js.map} +1 -1
- package/build/client/assets/{preview-fhmjENlm.js → preview-Be-plbPz.js} +2 -2
- package/build/client/assets/{preview-fhmjENlm.js.map → preview-Be-plbPz.js.map} +1 -1
- package/build/client/assets/{product-CvyMpYD_.js → product-D4IgPJN5.js} +2 -2
- package/build/client/assets/{product-CvyMpYD_.js.map → product-D4IgPJN5.js.map} +1 -1
- package/build/client/assets/{progress-Bby-ybMA.js → progress-JDMkfyr3.js} +2 -2
- package/build/client/assets/{progress-Bby-ybMA.js.map → progress-JDMkfyr3.js.map} +1 -1
- package/build/client/assets/{revalidation-ws-BJWJviUX.js → revalidation-ws-iocj9Mrl.js} +2 -2
- package/build/client/assets/{revalidation-ws-BJWJviUX.js.map → revalidation-ws-iocj9Mrl.js.map} +1 -1
- package/build/client/assets/{root-B-MkiU1r.js → root-IJJBVEoj.js} +2 -2
- package/build/client/assets/{root-B-MkiU1r.js.map → root-IJJBVEoj.js.map} +1 -1
- package/build/client/assets/{root-loader-BOzEMapJ.js → root-loader-BmUqzUDN.js} +2 -2
- package/build/client/assets/{root-loader-BOzEMapJ.js.map → root-loader-BmUqzUDN.js.map} +1 -1
- package/build/client/assets/{set-playground-BSGwH9dH.js → set-playground-DO5tJu4h.js} +2 -2
- package/build/client/assets/{set-playground-BSGwH9dH.js.map → set-playground-DO5tJu4h.js.map} +1 -1
- package/build/client/assets/{test-BeEphi7e.js → test-C8NrA0Ls.js} +2 -2
- package/build/client/assets/{test-BeEphi7e.js.map → test-C8NrA0Ls.js.map} +1 -1
- package/build/client/assets/{tests-RWSslYc0.js → tests-Bko6qhsh.js} +2 -2
- package/build/client/assets/{tests-RWSslYc0.js.map → tests-Bko6qhsh.js.map} +1 -1
- package/build/client/assets/{user-BsPobzjB.js → user-CYXKquT7.js} +2 -2
- package/build/client/assets/{user-BsPobzjB.js.map → user-CYXKquT7.js.map} +1 -1
- package/build/client/assets/{version-f8qqYbyU.js → version-CwWSRwe5.js} +2 -2
- package/build/client/assets/{version-f8qqYbyU.js.map → version-CwWSRwe5.js.map} +1 -1
- package/build/client/assets/{workshop-config-Zfc8zU0x.js → workshop-config-BMWaKPZT.js} +2 -2
- package/build/client/assets/{workshop-config-Zfc8zU0x.js.map → workshop-config-BMWaKPZT.js.map} +1 -1
- package/build/server/index.js +7 -3
- package/build/server/index.js.map +1 -1
- package/package.json +3 -3
- package/build/client/assets/_extra-Dki7HdFp.js +0 -2
- package/build/client/assets/_extra-Dki7HdFp.js.map +0 -1
- package/build/client/assets/_layout-DTrIGRDO.js +0 -2
- package/build/client/assets/_layout-DTrIGRDO.js.map +0 -1
- package/build/client/assets/discord-r3m19sUM.js +0 -2
- package/build/client/assets/discord-r3m19sUM.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-CAr50MIB.js","sources":["../../../app/routes/admin+/cache.tsx"],"sourcesContent":["import {\n\tdeleteCacheEntry,\n\tdeleteWorkshopCache,\n\tgetAllWorkshopCaches,\n\tgetGlobalCaches,\n\tglobalCacheDirectoryExists,\n\tupdateCacheEntry,\n} from '@epic-web/workshop-utils/cache.server'\nimport { getEnv } from '@epic-web/workshop-utils/env.server'\nimport { getErrorMessage } from '@epic-web/workshop-utils/utils'\nimport { useEffect, useRef, useState } from 'react'\nimport { href, useFetcher, useSearchParams } from 'react-router'\nimport { ClientOnly } from 'remix-utils/client-only'\nimport { z } from 'zod'\nimport {\n\tButton,\n\tIconButton,\n\ticonButtonClassName,\n} from '#app/components/button.tsx'\nimport { Icon } from '#app/components/icons.tsx'\nimport { LaunchEditor } from '#app/routes/launch-editor.tsx'\nimport {\n\tcalculateExpirationTime,\n\tcn,\n\tensureUndeployed,\n\tformatDuration,\n\tformatFileSize,\n\tformatTimeRemaining,\n\tuseDayjs,\n\tuseDoubleCheck,\n\tuseInterval,\n} from '#app/utils/misc.tsx'\nimport { type Route } from './+types/cache.ts'\n\nexport async function loader({ request }: Route.LoaderArgs) {\n\tensureUndeployed()\n\tconst currentWorkshopId = getEnv().EPICSHOP_WORKSHOP_INSTANCE_ID\n\tconst allWorkshopCaches = await getAllWorkshopCaches()\n\tconst globalCaches = await getGlobalCaches()\n\tconst allCaches = [...allWorkshopCaches, ...globalCaches]\n\n\tconst url = new URL(request.url)\n\tconst filterQuery = url.searchParams.get('q') || ''\n\n\t// Ensure 'global' is always in available workshops if global cache directory exists\n\tconst availableWorkshopIds = new Set(allCaches.map((w) => w.workshopId))\n\tconst globalDirExists = await globalCacheDirectoryExists()\n\tif (globalDirExists) {\n\t\tavailableWorkshopIds.add('global')\n\t}\n\n\tconst selectedWorkshops = url.searchParams\n\t\t.get('workshops')\n\t\t?.split(',')\n\t\t.filter(Boolean) || [\n\t\tcurrentWorkshopId,\n\t\t...(globalDirExists ? ['global'] : []),\n\t]\n\n\t// Filter caches based on search query and selected workshops\n\tconst filteredCaches = allCaches\n\t\t.filter(\n\t\t\t(workshopCache) =>\n\t\t\t\tselectedWorkshops.includes(workshopCache.workshopId) ||\n\t\t\t\tselectedWorkshops.length === 0,\n\t\t)\n\t\t.map((workshopCache) => ({\n\t\t\t...workshopCache,\n\t\t\tcaches: workshopCache.caches\n\t\t\t\t.map((cache) => ({\n\t\t\t\t\t...cache,\n\t\t\t\t\tentries: cache.entries.filter(\n\t\t\t\t\t\t(entry) =>\n\t\t\t\t\t\t\tfilterQuery === '' ||\n\t\t\t\t\t\t\tentry.key.toLowerCase().includes(filterQuery.toLowerCase()) ||\n\t\t\t\t\t\t\tcache.name.toLowerCase().includes(filterQuery.toLowerCase()),\n\t\t\t\t\t),\n\t\t\t\t}))\n\t\t\t\t.filter((cache) => cache.entries.length > 0 || filterQuery === ''),\n\t\t}))\n\t\t.filter(\n\t\t\t(workshopCache) => workshopCache.caches.length > 0 || filterQuery === '',\n\t\t)\n\n\treturn {\n\t\tcurrentWorkshopId,\n\t\tallWorkshopCaches: allCaches,\n\t\tfilteredCaches,\n\t\tfilterQuery,\n\t\tselectedWorkshops,\n\t\tavailableWorkshops: Array.from(availableWorkshopIds),\n\t}\n}\n\nconst ActionSchema = z.discriminatedUnion('intent', [\n\tz.object({\n\t\tintent: z.literal('delete-entry'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t\tfilename: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('delete-cache'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('delete-workshop-cache'),\n\t\tworkshopId: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('update-entry'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t\tfilename: z.string(),\n\t\tnewValue: z.string(),\n\t}),\n])\n\nexport async function action({ request }: Route.ActionArgs) {\n\tensureUndeployed()\n\n\tconst formData = await request.formData()\n\tconst rawData = Object.fromEntries(formData.entries())\n\tconst result = ActionSchema.safeParse(rawData)\n\n\tif (!result.success) {\n\t\treturn { status: 'error', error: 'Invalid request' } as const\n\t}\n\n\tconst data = result.data\n\n\ttry {\n\t\tswitch (data.intent) {\n\t\t\tcase 'delete-entry': {\n\t\t\t\tconst path = `${data.workshopId}/${data.cacheName}/${data.filename}`\n\t\t\t\tawait deleteCacheEntry(path)\n\t\t\t\treturn { status: 'success', message: 'Cache entry deleted' } as const\n\t\t\t}\n\t\t\tcase 'delete-cache': {\n\t\t\t\tawait deleteWorkshopCache(data.workshopId, data.cacheName)\n\t\t\t\treturn { status: 'success', message: 'Cache deleted' } as const\n\t\t\t}\n\t\t\tcase 'delete-workshop-cache': {\n\t\t\t\tawait deleteWorkshopCache(data.workshopId)\n\t\t\t\treturn { status: 'success', message: 'Workshop cache deleted' } as const\n\t\t\t}\n\t\t\tcase 'update-entry': {\n\t\t\t\tconst path = `${data.workshopId}/${data.cacheName}/${data.filename}`\n\t\t\t\ttry {\n\t\t\t\t\tconst parsedValue = JSON.parse(data.newValue)\n\t\t\t\t\tawait updateCacheEntry(path, parsedValue)\n\t\t\t\t\treturn { status: 'success', message: 'Cache entry updated' } as const\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\terror: getErrorMessage(error, 'Invalid JSON value'),\n\t\t\t\t\t} as const\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconsole.error('Cache action error:', error)\n\t\treturn { status: 'error', error: 'Operation failed' } as const\n\t}\n}\n\nfunction WorkshopChooser({\n\tselectedWorkshops,\n\tavailableWorkshops,\n\tcurrentWorkshopId,\n}: {\n\tselectedWorkshops: string[]\n\tavailableWorkshops: string[]\n\tcurrentWorkshopId: string\n}) {\n\tconst [searchParams, setSearchParams] = useSearchParams()\n\n\tconst handleWorkshopChange = (workshop: string, checked: boolean) => {\n\t\tconst newSelected = checked\n\t\t\t? [...selectedWorkshops, workshop]\n\t\t\t: selectedWorkshops.filter((w) => w !== workshop)\n\n\t\tconst params = new URLSearchParams(searchParams)\n\t\tif (newSelected.length > 0) {\n\t\t\tparams.set('workshops', newSelected.join(','))\n\t\t} else {\n\t\t\tparams.delete('workshops')\n\t\t}\n\t\tsetSearchParams(params)\n\t}\n\n\treturn (\n\t\t<div className=\"mb-6\">\n\t\t\t<h3 className=\"mb-3 text-lg font-semibold\">Workshop Filter</h3>\n\t\t\t<div className=\"flex flex-wrap gap-3\">\n\t\t\t\t{availableWorkshops.map((workshop) => (\n\t\t\t\t\t<label key={workshop} className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={selectedWorkshops.includes(workshop)}\n\t\t\t\t\t\t\tonChange={(e) => handleWorkshopChange(workshop, e.target.checked)}\n\t\t\t\t\t\t\tclassName=\"rounded\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName={`text-sm ${workshop === currentWorkshopId ? 'text-primary font-bold' : ''}`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{workshop} {workshop === currentWorkshopId ? '(current)' : null}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nfunction SearchFilter({ filterQuery }: { filterQuery: string }) {\n\tconst [searchParams, setSearchParams] = useSearchParams()\n\tconst [inputValue, setInputValue] = useState(filterQuery)\n\tconst inputRef = useRef<HTMLInputElement>(null)\n\n\t// Update input value when filterQuery changes (e.g., from URL)\n\tuseEffect(() => {\n\t\tsetInputValue(filterQuery)\n\t}, [filterQuery])\n\n\tconst handleSearch = (query: string) => {\n\t\tconst params = new URLSearchParams(searchParams)\n\t\tif (query) {\n\t\t\tparams.set('q', query)\n\t\t} else {\n\t\t\tparams.delete('q')\n\t\t}\n\t\tsetSearchParams(params)\n\t}\n\n\tconst handleClear = () => {\n\t\tsetInputValue('')\n\t\thandleSearch('')\n\t\tinputRef.current?.focus()\n\t}\n\n\treturn (\n\t\t<div className=\"mb-6\">\n\t\t\t<h3 className=\"mb-3 text-lg font-semibold\">Search Cache Entries</h3>\n\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t<input\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tplaceholder=\"Search by key or cache name...\"\n\t\t\t\t\tvalue={inputValue}\n\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\tsetInputValue(e.target.value)\n\t\t\t\t\t\thandleSearch(e.target.value)\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"border-border bg-background text-foreground focus:ring-ring flex-1 rounded-md border px-3 py-2 focus:ring-2 focus:outline-none\"\n\t\t\t\t/>\n\t\t\t\t{inputValue ? (\n\t\t\t\t\t<IconButton onClick={handleClear} title=\"Clear search\">\n\t\t\t\t\t\t<Icon name=\"Close\" className=\"h-4 w-4\" />\n\t\t\t\t\t</IconButton>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Inline entry editor component\nfunction InlineEntryEditor({\n\tworkshopId,\n\tcacheName,\n\tfilename,\n\tcurrentValue,\n\tentryKey,\n}: {\n\tworkshopId: string\n\tcacheName: string\n\tfilename: string\n\tcurrentValue: any\n\tentryKey: string\n}) {\n\tconst fetcher = useFetcher<typeof action>()\n\tconst [editValue, setEditValue] = useState(\n\t\tJSON.stringify(currentValue, null, 2),\n\t)\n\tconst [hasChanges, setHasChanges] = useState(false)\n\n\tconst handleSave = () => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'update-entry',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t\tfilename,\n\t\t\t\tnewValue: editValue,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t\tsetHasChanges(false)\n\t}\n\n\tconst handleChange = (value: string) => {\n\t\tsetEditValue(value)\n\t\tsetHasChanges(value !== JSON.stringify(currentValue, null, 2))\n\t}\n\n\tconst handleReset = () => {\n\t\tsetEditValue(JSON.stringify(currentValue, null, 2))\n\t\tsetHasChanges(false)\n\t}\n\n\treturn (\n\t\t<details className=\"mt-2\">\n\t\t\t<summary className=\"text-muted-foreground hover:text-foreground cursor-pointer text-sm\">\n\t\t\t\tEdit entry details\n\t\t\t</summary>\n\t\t\t<div className=\"border-border bg-muted mt-2 space-y-3 rounded border p-3\">\n\t\t\t\t<div>\n\t\t\t\t\t<label className=\"mb-1 block text-sm font-medium\">Key:</label>\n\t\t\t\t\t<code className=\"bg-background rounded border px-2 py-1 text-sm\">\n\t\t\t\t\t\t{entryKey}\n\t\t\t\t\t</code>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<label className=\"mb-1 block text-sm font-medium\">Value:</label>\n\t\t\t\t\t<textarea\n\t\t\t\t\t\tvalue={editValue}\n\t\t\t\t\t\tonChange={(e) => handleChange(e.target.value)}\n\t\t\t\t\t\tclassName=\"resize-vertical border-border bg-background text-foreground focus:ring-ring h-32 w-full rounded border p-2 font-mono text-sm focus:ring-2 focus:outline-none\"\n\t\t\t\t\t\tplaceholder=\"Enter JSON value...\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvarient=\"primary\"\n\t\t\t\t\t\tonClick={handleSave}\n\t\t\t\t\t\tdisabled={!hasChanges || fetcher.state !== 'idle'}\n\t\t\t\t\t>\n\t\t\t\t\t\t{fetcher.state !== 'idle' ? 'Saving...' : 'Save'}\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button varient=\"mono\" onClick={handleReset} disabled={!hasChanges}>\n\t\t\t\t\t\tReset\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</details>\n\t)\n}\n\nfunction SkippedFilesSection({\n\tskippedFiles,\n\tworkshopId,\n\tcacheName,\n}: {\n\tskippedFiles: Array<{\n\t\tfilename: string\n\t\terror: string\n\t\tsize: number\n\t\tskipped: true\n\t}>\n\tworkshopId: string\n\tcacheName: string\n}) {\n\tconst fetcher = useFetcher<typeof action>()\n\n\tif (skippedFiles.length === 0) return null\n\n\treturn (\n\t\t<div className=\"border-warning bg-warning mt-4 rounded border p-3\">\n\t\t\t<div className=\"mb-2 flex items-center gap-2\">\n\t\t\t\t<Icon\n\t\t\t\t\tname=\"TriangleAlert\"\n\t\t\t\t\tclassName=\"text-warning-foreground h-4 w-4\"\n\t\t\t\t/>\n\t\t\t\t<h5 className=\"text-warning-foreground font-medium\">\n\t\t\t\t\tSkipped Files ({skippedFiles.length})\n\t\t\t\t</h5>\n\t\t\t</div>\n\t\t\t<p className=\"text-warning-foreground/80 mb-3 text-sm\">\n\t\t\t\tThese cache files were skipped because they exceed the 3MB size limit:\n\t\t\t</p>\n\t\t\t<div className=\"space-y-2\">\n\t\t\t\t{skippedFiles.map((skippedFile) => (\n\t\t\t\t\t<div\n\t\t\t\t\t\tkey={skippedFile.filename}\n\t\t\t\t\t\tclassName=\"border-warning/20 bg-warning/5 flex items-center justify-between rounded border p-2\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t<div className=\"text-warning-foreground truncate font-mono text-sm font-medium\">\n\t\t\t\t\t\t\t\t{skippedFile.filename}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"text-warning-foreground/70 text-xs\">\n\t\t\t\t\t\t\t\t{skippedFile.error} • Size:{' '}\n\t\t\t\t\t\t\t\t<span title={`${skippedFile.size} bytes`}>\n\t\t\t\t\t\t\t\t\t{formatFileSize(skippedFile.size)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"ml-2 flex shrink-0\">\n\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\tonConfirm={() => {\n\t\t\t\t\t\t\t\t\tvoid fetcher.submit(\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tintent: 'delete-entry',\n\t\t\t\t\t\t\t\t\t\t\tworkshopId,\n\t\t\t\t\t\t\t\t\t\t\tcacheName,\n\t\t\t\t\t\t\t\t\t\t\tfilename: skippedFile.filename,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{ method: 'POST' },\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\ttitle=\"Delete large cache file\"\n\t\t\t\t\t\t\t\tclassName=\"text-destructive-foreground hover:bg-destructive/20 hover:text-destructive-foreground\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nexport default function CacheManagement({ loaderData }: Route.ComponentProps) {\n\tconst fetcher = useFetcher<typeof action>()\n\n\tconst deleteEntry = (\n\t\tworkshopId: string,\n\t\tcacheName: string,\n\t\tfilename: string,\n\t) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-entry',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t\tfilename,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst deleteCache = (workshopId: string, cacheName: string) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-cache',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst deleteWorkshopCache = (workshopId: string) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-workshop-cache',\n\t\t\t\tworkshopId,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst {\n\t\tcurrentWorkshopId,\n\t\tfilteredCaches,\n\t\tfilterQuery,\n\t\tselectedWorkshops,\n\t\tavailableWorkshops,\n\t} = loaderData\n\n\treturn (\n\t\t<div className=\"space-y-6\">\n\t\t\t<div>\n\t\t\t\t<h2 className=\"mb-2 text-2xl font-bold\">Cache Management</h2>\n\t\t\t\t<p className=\"text-muted-foreground\">\n\t\t\t\t\tCurrent Workshop:{' '}\n\t\t\t\t\t<span className=\"text-foreground font-semibold\">\n\t\t\t\t\t\t{currentWorkshopId}\n\t\t\t\t\t</span>\n\t\t\t\t</p>\n\t\t\t</div>\n\n\t\t\t<WorkshopChooser\n\t\t\t\tselectedWorkshops={selectedWorkshops}\n\t\t\t\tavailableWorkshops={availableWorkshops}\n\t\t\t\tcurrentWorkshopId={currentWorkshopId}\n\t\t\t/>\n\n\t\t\t<SearchFilter filterQuery={filterQuery} />\n\n\t\t\t{fetcher.data?.status === 'success' ? (\n\t\t\t\t<div className=\"border-border bg-success text-success-foreground rounded border p-4\">\n\t\t\t\t\t{fetcher.data.message}\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{fetcher.data?.status === 'error' ? (\n\t\t\t\t<div className=\"border-border bg-destructive text-destructive-foreground rounded border p-4\">\n\t\t\t\t\t{fetcher.data.error}\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{filteredCaches.length === 0 ? (\n\t\t\t\t<div className=\"text-muted-foreground py-8 text-center\">\n\t\t\t\t\tNo caches found matching your criteria.\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t<div className=\"space-y-6\">\n\t\t\t\t{filteredCaches.map((workshopCache) => (\n\t\t\t\t\t<details\n\t\t\t\t\t\tkey={workshopCache.workshopId}\n\t\t\t\t\t\topen={workshopCache.workshopId === currentWorkshopId}\n\t\t\t\t\t>\n\t\t\t\t\t\t<summary className=\"border-border bg-card hover:bg-accent cursor-pointer rounded-lg border p-4\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t<h3 className=\"text-card-foreground flex items-center gap-2 text-lg font-semibold\">\n\t\t\t\t\t\t\t\t\t<Icon name=\"Files\" className=\"h-5 w-5\" />\n\t\t\t\t\t\t\t\t\t{workshopCache.workshopId === 'global'\n\t\t\t\t\t\t\t\t\t\t? 'Global Caches'\n\t\t\t\t\t\t\t\t\t\t: workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t{workshopCache.workshopId === currentWorkshopId ? (\n\t\t\t\t\t\t\t\t\t\t<span className=\"bg-primary text-primary-foreground rounded px-2 py-1 text-xs\">\n\t\t\t\t\t\t\t\t\t\t\tCurrent\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\tdeleteWorkshopCache(workshopCache.workshopId)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttitle=\"Delete all workshop caches\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</summary>\n\n\t\t\t\t\t\t<div className=\"mt-4 space-y-4 pl-4\">\n\t\t\t\t\t\t\t{workshopCache.caches.map((cache) => {\n\t\t\t\t\t\t\t\tconst totalSize = cache.entries.reduce(\n\t\t\t\t\t\t\t\t\t(sum, entry) => sum + (entry.size || 0),\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst skippedSize = (cache.skippedFiles || []).reduce(\n\t\t\t\t\t\t\t\t\t(sum, file) => sum + file.size,\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst grandTotal = totalSize + skippedSize\n\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<details key={cache.name} className=\"bg-muted rounded-md\">\n\t\t\t\t\t\t\t\t\t\t<summary className=\"hover:bg-accent cursor-pointer p-3\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t<h4 className=\"text-muted-foreground flex items-center gap-2 font-medium\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Files\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t{cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t({cache.entries.length} entr\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{cache.entries.length === 1 ? 'y' : 'ies'})\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{grandTotal > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t•{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span title={`${grandTotal} bytes`}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(grandTotal)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttotal\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{skippedSize > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-warning\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span title={`${skippedSize} bytes`}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(skippedSize)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tskipped)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeleteCache(workshopCache.workshopId, cache.name)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Delete cache\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</summary>\n\n\t\t\t\t\t\t\t\t\t\t<div className=\"p-3 pt-0\">\n\t\t\t\t\t\t\t\t\t\t\t{cache.entries.length === 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tNo entries match your search.\n\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\n\t\t\t\t\t\t\t\t\t\t\t{cache.skippedFiles && cache.skippedFiles.length > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<SkippedFilesSection\n\t\t\t\t\t\t\t\t\t\t\t\t\tskippedFiles={cache.skippedFiles}\n\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopId={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t\t\tcacheName={cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t{cache.entries.map(\n\t\t\t\t\t\t\t\t\t\t\t\t\t({ key, entry, filename, size, filepath }) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tkey={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"border-border bg-background rounded border p-3\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-start justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"mb-1 flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"truncate font-mono text-sm font-medium\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{size ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"bg-muted text-muted-foreground inline-flex items-center rounded px-1.5 py-0.5 text-xs whitespace-nowrap\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={`${size} bytes`}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(size)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CacheMetadata metadata={entry.metadata} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"ml-4 flex shrink-0 gap-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thref={href('/admin/cache/*', {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'*': `${workshopCache.workshopId}/${cache.name}/${filename}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"border-border bg-background text-foreground hover:bg-muted focus:ring-ring inline-flex h-8 w-8 items-center justify-center rounded border focus:ring-2 focus:outline-none\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"View JSON\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tname=\"ExternalLink\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"h-4 w-4\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{filepath ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<LaunchEditor\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfile={filepath}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={iconButtonClassName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tname=\"Files\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"h-4 w-4\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Open in editor\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</LaunchEditor>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeleteEntry(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopCache.workshopId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcache.name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfilename,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Delete entry\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<InlineEntryEditor\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopId={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcacheName={cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfilename={filename}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcurrentValue={entry.value}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tentryKey={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</details>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</details>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Component for displaying cache metadata with live countdown\nfunction CacheMetadata({\n\tmetadata,\n}: {\n\tmetadata: {\n\t\tcreatedTime: number\n\t\tttl?: number | null\n\t\tswr?: number\n\t}\n}) {\n\tconst dayjs = useDayjs()\n\tconst [, setCurrentTime] = useState(Date.now())\n\tconst expirationTime = calculateExpirationTime(metadata)\n\n\t// Update time every second for live countdown\n\tuseInterval(() => {\n\t\tsetCurrentTime(Date.now())\n\t}, 1000)\n\n\tconst createdDate = dayjs(metadata.createdTime)\n\tconst timeRemaining = expirationTime\n\t\t? formatTimeRemaining(expirationTime)\n\t\t: { text: 'Never', isExpired: false, isExpiringSoon: false }\n\n\treturn (\n\t\t<div className=\"text-muted-foreground flex flex-col gap-1 text-xs\">\n\t\t\t<div>\n\t\t\t\tCreated: {createdDate.format('MMM D, YYYY HH:mm:ss')}{' '}\n\t\t\t\t<ClientOnly>{() => `(${createdDate.fromNow()})`}</ClientOnly>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-wrap items-center gap-3\">\n\t\t\t\t{metadata.ttl !== undefined && metadata.ttl !== null ? (\n\t\t\t\t\t<span>\n\t\t\t\t\t\tTTL:{' '}\n\t\t\t\t\t\t{metadata.ttl === Infinity ? (\n\t\t\t\t\t\t\t'Forever'\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<span title={`${metadata.ttl}ms`}>\n\t\t\t\t\t\t\t\t{formatDuration(metadata.ttl)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t{metadata.swr !== undefined ? (\n\t\t\t\t\t<span>\n\t\t\t\t\t\tSWR:{' '}\n\t\t\t\t\t\t<span title={`${metadata.swr}ms`}>\n\t\t\t\t\t\t\t{formatDuration(metadata.swr)}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t<div\n\t\t\t\t\tclassName={`inline-flex w-auto rounded-full px-2 py-[2px] font-medium ${\n\t\t\t\t\t\ttimeRemaining.isExpired\n\t\t\t\t\t\t\t? 'bg-destructive text-destructive-foreground'\n\t\t\t\t\t\t\t: timeRemaining.isExpiringSoon\n\t\t\t\t\t\t\t\t? 'bg-warning text-warning-foreground'\n\t\t\t\t\t\t\t\t: 'text-foreground'\n\t\t\t\t\t}`}\n\t\t\t\t>\n\t\t\t\t\t{expirationTime ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tExpires: {dayjs(expirationTime).format('MMM D, YYYY HH:mm:ss')} (\n\t\t\t\t\t\t\t<span className=\"tabular-nums\">\n\t\t\t\t\t\t\t\t<ClientOnly>{() => timeRemaining.text}</ClientOnly>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t</>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t'Expires: Never'\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Double-check delete button\nfunction DoubleCheckButton({\n\tonConfirm,\n\tchildren,\n\tclassName,\n\t...props\n}: React.ComponentPropsWithoutRef<'button'> & {\n\tonConfirm: () => void\n}) {\n\tconst doubleCheck = useDoubleCheck()\n\n\treturn (\n\t\t<IconButton\n\t\t\t{...doubleCheck.getButtonProps({\n\t\t\t\tonClick: doubleCheck.doubleCheck ? onConfirm : undefined,\n\t\t\t\t...props,\n\t\t\t})}\n\t\t\tclassName={cn(\n\t\t\t\tdoubleCheck.doubleCheck\n\t\t\t\t\t? 'bg-destructive text-destructive-foreground'\n\t\t\t\t\t: null,\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t{doubleCheck.doubleCheck ? '✓' : children}\n\t\t</IconButton>\n\t)\n}\n"],"names":["WorkshopChooser","selectedWorkshops","availableWorkshops","currentWorkshopId","searchParams","setSearchParams","useSearchParams","handleWorkshopChange","workshop","checked","newSelected","filter","w","params","URLSearchParams","length","set","join","delete","jsxs","className","children","jsx","map","type","includes","onChange","e","target","SearchFilter","filterQuery","inputValue","setInputValue","useState","inputRef","useRef","useEffect","handleSearch","query","handleClear","current","focus","ref","placeholder","value","IconButton","onClick","title","Icon","name","InlineEntryEditor","workshopId","cacheName","filename","currentValue","entryKey","fetcher","useFetcher","editValue","setEditValue","JSON","stringify","hasChanges","setHasChanges","handleSave","submit","intent","newValue","method","handleChange","handleReset","Button","varient","disabled","state","SkippedFilesSection","skippedFiles","skippedFile","error","size","formatFileSize","DoubleCheckButton","onConfirm","cache","_UNSAFE_withComponentProps","loaderData","deleteEntry","deleteCache","deleteWorkshopCache","filteredCaches","data","status","message","workshopCache","open","caches","totalSize","entries","reduce","sum","entry","skippedSize","file","grandTotal","key","filepath","CacheMetadata","metadata","href","rel","LaunchEditor","iconButtonClassName","dayjs","useDayjs","setCurrentTime","Date","now","expirationTime","calculateExpirationTime","useInterval","createdDate","createdTime","timeRemaining","formatTimeRemaining","text","isExpired","isExpiringSoon","format","ClientOnly","fromNow","ttl","Infinity","formatDuration","swr","Fragment","props","doubleCheck","useDoubleCheck","getButtonProps","cn"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cache-CxMFS2A_.js","sources":["../../../app/routes/admin+/cache.tsx"],"sourcesContent":["import {\n\tdeleteCacheEntry,\n\tdeleteWorkshopCache,\n\tgetAllWorkshopCaches,\n\tgetGlobalCaches,\n\tglobalCacheDirectoryExists,\n\tupdateCacheEntry,\n} from '@epic-web/workshop-utils/cache.server'\nimport { getEnv } from '@epic-web/workshop-utils/env.server'\nimport { getErrorMessage } from '@epic-web/workshop-utils/utils'\nimport { useEffect, useRef, useState } from 'react'\nimport { href, useFetcher, useSearchParams } from 'react-router'\nimport { ClientOnly } from 'remix-utils/client-only'\nimport { z } from 'zod'\nimport {\n\tButton,\n\tIconButton,\n\ticonButtonClassName,\n} from '#app/components/button.tsx'\nimport { Icon } from '#app/components/icons.tsx'\nimport { LaunchEditor } from '#app/routes/launch-editor.tsx'\nimport {\n\tcalculateExpirationTime,\n\tcn,\n\tensureUndeployed,\n\tformatDuration,\n\tformatFileSize,\n\tformatTimeRemaining,\n\tuseDayjs,\n\tuseDoubleCheck,\n\tuseInterval,\n} from '#app/utils/misc.tsx'\nimport { type Route } from './+types/cache.ts'\n\nexport async function loader({ request }: Route.LoaderArgs) {\n\tensureUndeployed()\n\tconst currentWorkshopId = getEnv().EPICSHOP_WORKSHOP_INSTANCE_ID\n\tconst allWorkshopCaches = await getAllWorkshopCaches()\n\tconst globalCaches = await getGlobalCaches()\n\tconst allCaches = [...allWorkshopCaches, ...globalCaches]\n\n\tconst url = new URL(request.url)\n\tconst filterQuery = url.searchParams.get('q') || ''\n\n\t// Ensure 'global' is always in available workshops if global cache directory exists\n\tconst availableWorkshopIds = new Set(allCaches.map((w) => w.workshopId))\n\tconst globalDirExists = await globalCacheDirectoryExists()\n\tif (globalDirExists) {\n\t\tavailableWorkshopIds.add('global')\n\t}\n\n\tconst selectedWorkshops = url.searchParams\n\t\t.get('workshops')\n\t\t?.split(',')\n\t\t.filter(Boolean) || [\n\t\tcurrentWorkshopId,\n\t\t...(globalDirExists ? ['global'] : []),\n\t]\n\n\t// Filter caches based on search query and selected workshops\n\tconst filteredCaches = allCaches\n\t\t.filter(\n\t\t\t(workshopCache) =>\n\t\t\t\tselectedWorkshops.includes(workshopCache.workshopId) ||\n\t\t\t\tselectedWorkshops.length === 0,\n\t\t)\n\t\t.map((workshopCache) => ({\n\t\t\t...workshopCache,\n\t\t\tcaches: workshopCache.caches\n\t\t\t\t.map((cache) => ({\n\t\t\t\t\t...cache,\n\t\t\t\t\tentries: cache.entries.filter(\n\t\t\t\t\t\t(entry) =>\n\t\t\t\t\t\t\tfilterQuery === '' ||\n\t\t\t\t\t\t\tentry.key.toLowerCase().includes(filterQuery.toLowerCase()) ||\n\t\t\t\t\t\t\tcache.name.toLowerCase().includes(filterQuery.toLowerCase()),\n\t\t\t\t\t),\n\t\t\t\t}))\n\t\t\t\t.filter((cache) => cache.entries.length > 0 || filterQuery === ''),\n\t\t}))\n\t\t.filter(\n\t\t\t(workshopCache) => workshopCache.caches.length > 0 || filterQuery === '',\n\t\t)\n\n\treturn {\n\t\tcurrentWorkshopId,\n\t\tallWorkshopCaches: allCaches,\n\t\tfilteredCaches,\n\t\tfilterQuery,\n\t\tselectedWorkshops,\n\t\tavailableWorkshops: Array.from(availableWorkshopIds),\n\t}\n}\n\nconst ActionSchema = z.discriminatedUnion('intent', [\n\tz.object({\n\t\tintent: z.literal('delete-entry'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t\tfilename: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('delete-cache'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('delete-workshop-cache'),\n\t\tworkshopId: z.string(),\n\t}),\n\tz.object({\n\t\tintent: z.literal('update-entry'),\n\t\tworkshopId: z.string(),\n\t\tcacheName: z.string(),\n\t\tfilename: z.string(),\n\t\tnewValue: z.string(),\n\t}),\n])\n\nexport async function action({ request }: Route.ActionArgs) {\n\tensureUndeployed()\n\n\tconst formData = await request.formData()\n\tconst rawData = Object.fromEntries(formData.entries())\n\tconst result = ActionSchema.safeParse(rawData)\n\n\tif (!result.success) {\n\t\treturn { status: 'error', error: 'Invalid request' } as const\n\t}\n\n\tconst data = result.data\n\n\ttry {\n\t\tswitch (data.intent) {\n\t\t\tcase 'delete-entry': {\n\t\t\t\tconst path = `${data.workshopId}/${data.cacheName}/${data.filename}`\n\t\t\t\tawait deleteCacheEntry(path)\n\t\t\t\treturn { status: 'success', message: 'Cache entry deleted' } as const\n\t\t\t}\n\t\t\tcase 'delete-cache': {\n\t\t\t\tawait deleteWorkshopCache(data.workshopId, data.cacheName)\n\t\t\t\treturn { status: 'success', message: 'Cache deleted' } as const\n\t\t\t}\n\t\t\tcase 'delete-workshop-cache': {\n\t\t\t\tawait deleteWorkshopCache(data.workshopId)\n\t\t\t\treturn { status: 'success', message: 'Workshop cache deleted' } as const\n\t\t\t}\n\t\t\tcase 'update-entry': {\n\t\t\t\tconst path = `${data.workshopId}/${data.cacheName}/${data.filename}`\n\t\t\t\ttry {\n\t\t\t\t\tconst parsedValue = JSON.parse(data.newValue)\n\t\t\t\t\tawait updateCacheEntry(path, parsedValue)\n\t\t\t\t\treturn { status: 'success', message: 'Cache entry updated' } as const\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'error',\n\t\t\t\t\t\terror: getErrorMessage(error, 'Invalid JSON value'),\n\t\t\t\t\t} as const\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconsole.error('Cache action error:', error)\n\t\treturn { status: 'error', error: 'Operation failed' } as const\n\t}\n}\n\nfunction WorkshopChooser({\n\tselectedWorkshops,\n\tavailableWorkshops,\n\tcurrentWorkshopId,\n}: {\n\tselectedWorkshops: string[]\n\tavailableWorkshops: string[]\n\tcurrentWorkshopId: string\n}) {\n\tconst [searchParams, setSearchParams] = useSearchParams()\n\n\tconst handleWorkshopChange = (workshop: string, checked: boolean) => {\n\t\tconst newSelected = checked\n\t\t\t? [...selectedWorkshops, workshop]\n\t\t\t: selectedWorkshops.filter((w) => w !== workshop)\n\n\t\tconst params = new URLSearchParams(searchParams)\n\t\tif (newSelected.length > 0) {\n\t\t\tparams.set('workshops', newSelected.join(','))\n\t\t} else {\n\t\t\tparams.delete('workshops')\n\t\t}\n\t\tsetSearchParams(params)\n\t}\n\n\treturn (\n\t\t<div className=\"mb-6\">\n\t\t\t<h3 className=\"mb-3 text-lg font-semibold\">Workshop Filter</h3>\n\t\t\t<div className=\"flex flex-wrap gap-3\">\n\t\t\t\t{availableWorkshops.map((workshop) => (\n\t\t\t\t\t<label key={workshop} className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={selectedWorkshops.includes(workshop)}\n\t\t\t\t\t\t\tonChange={(e) => handleWorkshopChange(workshop, e.target.checked)}\n\t\t\t\t\t\t\tclassName=\"rounded\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName={`text-sm ${workshop === currentWorkshopId ? 'text-primary font-bold' : ''}`}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{workshop} {workshop === currentWorkshopId ? '(current)' : null}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nfunction SearchFilter({ filterQuery }: { filterQuery: string }) {\n\tconst [searchParams, setSearchParams] = useSearchParams()\n\tconst [inputValue, setInputValue] = useState(filterQuery)\n\tconst inputRef = useRef<HTMLInputElement>(null)\n\n\t// Update input value when filterQuery changes (e.g., from URL)\n\tuseEffect(() => {\n\t\tsetInputValue(filterQuery)\n\t}, [filterQuery])\n\n\tconst handleSearch = (query: string) => {\n\t\tconst params = new URLSearchParams(searchParams)\n\t\tif (query) {\n\t\t\tparams.set('q', query)\n\t\t} else {\n\t\t\tparams.delete('q')\n\t\t}\n\t\tsetSearchParams(params)\n\t}\n\n\tconst handleClear = () => {\n\t\tsetInputValue('')\n\t\thandleSearch('')\n\t\tinputRef.current?.focus()\n\t}\n\n\treturn (\n\t\t<div className=\"mb-6\">\n\t\t\t<h3 className=\"mb-3 text-lg font-semibold\">Search Cache Entries</h3>\n\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t<input\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tplaceholder=\"Search by key or cache name...\"\n\t\t\t\t\tvalue={inputValue}\n\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\tsetInputValue(e.target.value)\n\t\t\t\t\t\thandleSearch(e.target.value)\n\t\t\t\t\t}}\n\t\t\t\t\tclassName=\"border-border bg-background text-foreground focus:ring-ring flex-1 rounded-md border px-3 py-2 focus:ring-2 focus:outline-none\"\n\t\t\t\t/>\n\t\t\t\t{inputValue ? (\n\t\t\t\t\t<IconButton onClick={handleClear} title=\"Clear search\">\n\t\t\t\t\t\t<Icon name=\"Close\" className=\"h-4 w-4\" />\n\t\t\t\t\t</IconButton>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Inline entry editor component\nfunction InlineEntryEditor({\n\tworkshopId,\n\tcacheName,\n\tfilename,\n\tcurrentValue,\n\tentryKey,\n}: {\n\tworkshopId: string\n\tcacheName: string\n\tfilename: string\n\tcurrentValue: any\n\tentryKey: string\n}) {\n\tconst fetcher = useFetcher<typeof action>()\n\tconst [editValue, setEditValue] = useState(\n\t\tJSON.stringify(currentValue, null, 2),\n\t)\n\tconst [hasChanges, setHasChanges] = useState(false)\n\n\tconst handleSave = () => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'update-entry',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t\tfilename,\n\t\t\t\tnewValue: editValue,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t\tsetHasChanges(false)\n\t}\n\n\tconst handleChange = (value: string) => {\n\t\tsetEditValue(value)\n\t\tsetHasChanges(value !== JSON.stringify(currentValue, null, 2))\n\t}\n\n\tconst handleReset = () => {\n\t\tsetEditValue(JSON.stringify(currentValue, null, 2))\n\t\tsetHasChanges(false)\n\t}\n\n\treturn (\n\t\t<details className=\"mt-2\">\n\t\t\t<summary className=\"text-muted-foreground hover:text-foreground cursor-pointer text-sm\">\n\t\t\t\tEdit entry details\n\t\t\t</summary>\n\t\t\t<div className=\"border-border bg-muted mt-2 space-y-3 rounded border p-3\">\n\t\t\t\t<div>\n\t\t\t\t\t<label className=\"mb-1 block text-sm font-medium\">Key:</label>\n\t\t\t\t\t<code className=\"bg-background rounded border px-2 py-1 text-sm\">\n\t\t\t\t\t\t{entryKey}\n\t\t\t\t\t</code>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<label className=\"mb-1 block text-sm font-medium\">Value:</label>\n\t\t\t\t\t<textarea\n\t\t\t\t\t\tvalue={editValue}\n\t\t\t\t\t\tonChange={(e) => handleChange(e.target.value)}\n\t\t\t\t\t\tclassName=\"resize-vertical border-border bg-background text-foreground focus:ring-ring h-32 w-full rounded border p-2 font-mono text-sm focus:ring-2 focus:outline-none\"\n\t\t\t\t\t\tplaceholder=\"Enter JSON value...\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvarient=\"primary\"\n\t\t\t\t\t\tonClick={handleSave}\n\t\t\t\t\t\tdisabled={!hasChanges || fetcher.state !== 'idle'}\n\t\t\t\t\t>\n\t\t\t\t\t\t{fetcher.state !== 'idle' ? 'Saving...' : 'Save'}\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button varient=\"mono\" onClick={handleReset} disabled={!hasChanges}>\n\t\t\t\t\t\tReset\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</details>\n\t)\n}\n\nfunction SkippedFilesSection({\n\tskippedFiles,\n\tworkshopId,\n\tcacheName,\n}: {\n\tskippedFiles: Array<{\n\t\tfilename: string\n\t\terror: string\n\t\tsize: number\n\t\tskipped: true\n\t}>\n\tworkshopId: string\n\tcacheName: string\n}) {\n\tconst fetcher = useFetcher<typeof action>()\n\n\tif (skippedFiles.length === 0) return null\n\n\treturn (\n\t\t<div className=\"border-warning bg-warning mt-4 rounded border p-3\">\n\t\t\t<div className=\"mb-2 flex items-center gap-2\">\n\t\t\t\t<Icon\n\t\t\t\t\tname=\"TriangleAlert\"\n\t\t\t\t\tclassName=\"text-warning-foreground h-4 w-4\"\n\t\t\t\t/>\n\t\t\t\t<h5 className=\"text-warning-foreground font-medium\">\n\t\t\t\t\tSkipped Files ({skippedFiles.length})\n\t\t\t\t</h5>\n\t\t\t</div>\n\t\t\t<p className=\"text-warning-foreground/80 mb-3 text-sm\">\n\t\t\t\tThese cache files were skipped because they exceed the 3MB size limit:\n\t\t\t</p>\n\t\t\t<div className=\"space-y-2\">\n\t\t\t\t{skippedFiles.map((skippedFile) => (\n\t\t\t\t\t<div\n\t\t\t\t\t\tkey={skippedFile.filename}\n\t\t\t\t\t\tclassName=\"border-warning/20 bg-warning/5 flex items-center justify-between rounded border p-2\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t<div className=\"text-warning-foreground truncate font-mono text-sm font-medium\">\n\t\t\t\t\t\t\t\t{skippedFile.filename}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"text-warning-foreground/70 text-xs\">\n\t\t\t\t\t\t\t\t{skippedFile.error} • Size:{' '}\n\t\t\t\t\t\t\t\t<span title={`${skippedFile.size} bytes`}>\n\t\t\t\t\t\t\t\t\t{formatFileSize(skippedFile.size)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"ml-2 flex shrink-0\">\n\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\tonConfirm={() => {\n\t\t\t\t\t\t\t\t\tvoid fetcher.submit(\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tintent: 'delete-entry',\n\t\t\t\t\t\t\t\t\t\t\tworkshopId,\n\t\t\t\t\t\t\t\t\t\t\tcacheName,\n\t\t\t\t\t\t\t\t\t\t\tfilename: skippedFile.filename,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{ method: 'POST' },\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\ttitle=\"Delete large cache file\"\n\t\t\t\t\t\t\t\tclassName=\"text-destructive-foreground hover:bg-destructive/20 hover:text-destructive-foreground\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nexport default function CacheManagement({ loaderData }: Route.ComponentProps) {\n\tconst fetcher = useFetcher<typeof action>()\n\n\tconst deleteEntry = (\n\t\tworkshopId: string,\n\t\tcacheName: string,\n\t\tfilename: string,\n\t) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-entry',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t\tfilename,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst deleteCache = (workshopId: string, cacheName: string) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-cache',\n\t\t\t\tworkshopId,\n\t\t\t\tcacheName,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst deleteWorkshopCache = (workshopId: string) => {\n\t\tvoid fetcher.submit(\n\t\t\t{\n\t\t\t\tintent: 'delete-workshop-cache',\n\t\t\t\tworkshopId,\n\t\t\t},\n\t\t\t{ method: 'POST' },\n\t\t)\n\t}\n\n\tconst {\n\t\tcurrentWorkshopId,\n\t\tfilteredCaches,\n\t\tfilterQuery,\n\t\tselectedWorkshops,\n\t\tavailableWorkshops,\n\t} = loaderData\n\n\treturn (\n\t\t<div className=\"space-y-6\">\n\t\t\t<div>\n\t\t\t\t<h2 className=\"mb-2 text-2xl font-bold\">Cache Management</h2>\n\t\t\t\t<p className=\"text-muted-foreground\">\n\t\t\t\t\tCurrent Workshop:{' '}\n\t\t\t\t\t<span className=\"text-foreground font-semibold\">\n\t\t\t\t\t\t{currentWorkshopId}\n\t\t\t\t\t</span>\n\t\t\t\t</p>\n\t\t\t</div>\n\n\t\t\t<WorkshopChooser\n\t\t\t\tselectedWorkshops={selectedWorkshops}\n\t\t\t\tavailableWorkshops={availableWorkshops}\n\t\t\t\tcurrentWorkshopId={currentWorkshopId}\n\t\t\t/>\n\n\t\t\t<SearchFilter filterQuery={filterQuery} />\n\n\t\t\t{fetcher.data?.status === 'success' ? (\n\t\t\t\t<div className=\"border-border bg-success text-success-foreground rounded border p-4\">\n\t\t\t\t\t{fetcher.data.message}\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{fetcher.data?.status === 'error' ? (\n\t\t\t\t<div className=\"border-border bg-destructive text-destructive-foreground rounded border p-4\">\n\t\t\t\t\t{fetcher.data.error}\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t{filteredCaches.length === 0 ? (\n\t\t\t\t<div className=\"text-muted-foreground py-8 text-center\">\n\t\t\t\t\tNo caches found matching your criteria.\n\t\t\t\t</div>\n\t\t\t) : null}\n\n\t\t\t<div className=\"space-y-6\">\n\t\t\t\t{filteredCaches.map((workshopCache) => (\n\t\t\t\t\t<details\n\t\t\t\t\t\tkey={workshopCache.workshopId}\n\t\t\t\t\t\topen={workshopCache.workshopId === currentWorkshopId}\n\t\t\t\t\t>\n\t\t\t\t\t\t<summary className=\"border-border bg-card hover:bg-accent cursor-pointer rounded-lg border p-4\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t<h3 className=\"text-card-foreground flex items-center gap-2 text-lg font-semibold\">\n\t\t\t\t\t\t\t\t\t<Icon name=\"Files\" className=\"h-5 w-5\" />\n\t\t\t\t\t\t\t\t\t{workshopCache.workshopId === 'global'\n\t\t\t\t\t\t\t\t\t\t? 'Global Caches'\n\t\t\t\t\t\t\t\t\t\t: workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t{workshopCache.workshopId === currentWorkshopId ? (\n\t\t\t\t\t\t\t\t\t\t<span className=\"bg-primary text-primary-foreground rounded px-2 py-1 text-xs\">\n\t\t\t\t\t\t\t\t\t\t\tCurrent\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\tdeleteWorkshopCache(workshopCache.workshopId)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttitle=\"Delete all workshop caches\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</summary>\n\n\t\t\t\t\t\t<div className=\"mt-4 space-y-4 pl-4\">\n\t\t\t\t\t\t\t{workshopCache.caches.map((cache) => {\n\t\t\t\t\t\t\t\tconst totalSize = cache.entries.reduce(\n\t\t\t\t\t\t\t\t\t(sum, entry) => sum + (entry.size || 0),\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst skippedSize = (cache.skippedFiles || []).reduce(\n\t\t\t\t\t\t\t\t\t(sum, file) => sum + file.size,\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tconst grandTotal = totalSize + skippedSize\n\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<details key={cache.name} className=\"bg-muted rounded-md\">\n\t\t\t\t\t\t\t\t\t\t<summary className=\"hover:bg-accent cursor-pointer p-3\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t<h4 className=\"text-muted-foreground flex items-center gap-2 font-medium\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Files\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t{cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t({cache.entries.length} entr\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{cache.entries.length === 1 ? 'y' : 'ies'})\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{grandTotal > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t•{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span title={`${grandTotal} bytes`}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(grandTotal)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttotal\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{skippedSize > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-warning\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span title={`${skippedSize} bytes`}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(skippedSize)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>{' '}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tskipped)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeleteCache(workshopCache.workshopId, cache.name)\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Delete cache\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</summary>\n\n\t\t\t\t\t\t\t\t\t\t<div className=\"p-3 pt-0\">\n\t\t\t\t\t\t\t\t\t\t\t{cache.entries.length === 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tNo entries match your search.\n\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\n\t\t\t\t\t\t\t\t\t\t\t{cache.skippedFiles && cache.skippedFiles.length > 0 ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<SkippedFilesSection\n\t\t\t\t\t\t\t\t\t\t\t\t\tskippedFiles={cache.skippedFiles}\n\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopId={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t\t\tcacheName={cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t) : null}\n\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t{cache.entries.map(\n\t\t\t\t\t\t\t\t\t\t\t\t\t({ key, entry, filename, size, filepath }) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tkey={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"border-border bg-background rounded border p-3\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-start justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"mb-1 flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"truncate font-mono text-sm font-medium\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{size ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"bg-muted text-muted-foreground inline-flex items-center rounded px-1.5 py-0.5 text-xs whitespace-nowrap\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle={`${size} bytes`}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{formatFileSize(size)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CacheMetadata metadata={entry.metadata} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"ml-4 flex shrink-0 gap-1\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thref={href('/admin/cache/*', {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'*': `${workshopCache.workshopId}/${cache.name}/${filename}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"border-border bg-background text-foreground hover:bg-muted focus:ring-ring inline-flex h-8 w-8 items-center justify-center rounded border focus:ring-2 focus:outline-none\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"View JSON\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tname=\"ExternalLink\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"h-4 w-4\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{filepath ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<LaunchEditor\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfile={filepath}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={iconButtonClassName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tname=\"Files\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"h-4 w-4\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Open in editor\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</LaunchEditor>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<DoubleCheckButton\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdeleteEntry(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopCache.workshopId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcache.name,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfilename,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle=\"Delete entry\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Remove\" className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</DoubleCheckButton>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<InlineEntryEditor\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tworkshopId={workshopCache.workshopId}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcacheName={cache.name}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfilename={filename}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcurrentValue={entry.value}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tentryKey={key}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</details>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</details>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Component for displaying cache metadata with live countdown\nfunction CacheMetadata({\n\tmetadata,\n}: {\n\tmetadata: {\n\t\tcreatedTime: number\n\t\tttl?: number | null\n\t\tswr?: number\n\t}\n}) {\n\tconst dayjs = useDayjs()\n\tconst [, setCurrentTime] = useState(Date.now())\n\tconst expirationTime = calculateExpirationTime(metadata)\n\n\t// Update time every second for live countdown\n\tuseInterval(() => {\n\t\tsetCurrentTime(Date.now())\n\t}, 1000)\n\n\tconst createdDate = dayjs(metadata.createdTime)\n\tconst timeRemaining = expirationTime\n\t\t? formatTimeRemaining(expirationTime)\n\t\t: { text: 'Never', isExpired: false, isExpiringSoon: false }\n\n\treturn (\n\t\t<div className=\"text-muted-foreground flex flex-col gap-1 text-xs\">\n\t\t\t<div>\n\t\t\t\tCreated: {createdDate.format('MMM D, YYYY HH:mm:ss')}{' '}\n\t\t\t\t<ClientOnly>{() => `(${createdDate.fromNow()})`}</ClientOnly>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-wrap items-center gap-3\">\n\t\t\t\t{metadata.ttl !== undefined && metadata.ttl !== null ? (\n\t\t\t\t\t<span>\n\t\t\t\t\t\tTTL:{' '}\n\t\t\t\t\t\t{metadata.ttl === Infinity ? (\n\t\t\t\t\t\t\t'Forever'\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<span title={`${metadata.ttl}ms`}>\n\t\t\t\t\t\t\t\t{formatDuration(metadata.ttl)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t{metadata.swr !== undefined ? (\n\t\t\t\t\t<span>\n\t\t\t\t\t\tSWR:{' '}\n\t\t\t\t\t\t<span title={`${metadata.swr}ms`}>\n\t\t\t\t\t\t\t{formatDuration(metadata.swr)}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t) : null}\n\t\t\t\t<div\n\t\t\t\t\tclassName={`inline-flex w-auto rounded-full px-2 py-[2px] font-medium ${\n\t\t\t\t\t\ttimeRemaining.isExpired\n\t\t\t\t\t\t\t? 'bg-destructive text-destructive-foreground'\n\t\t\t\t\t\t\t: timeRemaining.isExpiringSoon\n\t\t\t\t\t\t\t\t? 'bg-warning text-warning-foreground'\n\t\t\t\t\t\t\t\t: 'text-foreground'\n\t\t\t\t\t}`}\n\t\t\t\t>\n\t\t\t\t\t{expirationTime ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tExpires: {dayjs(expirationTime).format('MMM D, YYYY HH:mm:ss')} (\n\t\t\t\t\t\t\t<span className=\"tabular-nums\">\n\t\t\t\t\t\t\t\t<ClientOnly>{() => timeRemaining.text}</ClientOnly>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t</>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t'Expires: Never'\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Double-check delete button\nfunction DoubleCheckButton({\n\tonConfirm,\n\tchildren,\n\tclassName,\n\t...props\n}: React.ComponentPropsWithoutRef<'button'> & {\n\tonConfirm: () => void\n}) {\n\tconst doubleCheck = useDoubleCheck()\n\n\treturn (\n\t\t<IconButton\n\t\t\t{...doubleCheck.getButtonProps({\n\t\t\t\tonClick: doubleCheck.doubleCheck ? onConfirm : undefined,\n\t\t\t\t...props,\n\t\t\t})}\n\t\t\tclassName={cn(\n\t\t\t\tdoubleCheck.doubleCheck\n\t\t\t\t\t? 'bg-destructive text-destructive-foreground'\n\t\t\t\t\t: null,\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t{doubleCheck.doubleCheck ? '✓' : children}\n\t\t</IconButton>\n\t)\n}\n"],"names":["WorkshopChooser","selectedWorkshops","availableWorkshops","currentWorkshopId","searchParams","setSearchParams","useSearchParams","handleWorkshopChange","workshop","checked","newSelected","filter","w","params","URLSearchParams","length","set","join","delete","jsxs","className","children","jsx","map","type","includes","onChange","e","target","SearchFilter","filterQuery","inputValue","setInputValue","useState","inputRef","useRef","useEffect","handleSearch","query","handleClear","current","focus","ref","placeholder","value","IconButton","onClick","title","Icon","name","InlineEntryEditor","workshopId","cacheName","filename","currentValue","entryKey","fetcher","useFetcher","editValue","setEditValue","JSON","stringify","hasChanges","setHasChanges","handleSave","submit","intent","newValue","method","handleChange","handleReset","Button","varient","disabled","state","SkippedFilesSection","skippedFiles","skippedFile","error","size","formatFileSize","DoubleCheckButton","onConfirm","cache","_UNSAFE_withComponentProps","loaderData","deleteEntry","deleteCache","deleteWorkshopCache","filteredCaches","data","status","message","workshopCache","open","caches","totalSize","entries","reduce","sum","entry","skippedSize","file","grandTotal","key","filepath","CacheMetadata","metadata","href","rel","LaunchEditor","iconButtonClassName","dayjs","useDayjs","setCurrentTime","Date","now","expirationTime","calculateExpirationTime","useInterval","createdDate","createdTime","timeRemaining","formatTimeRemaining","text","isExpired","isExpiringSoon","format","ClientOnly","fromNow","ttl","Infinity","formatDuration","swr","Fragment","props","doubleCheck","useDoubleCheck","getButtonProps","cn"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{w as s}from"./chunk-EPOLDU6W-BCLmut3y.js";import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{I as a}from"./misc-W4055b-0.js";import{S as i}from"./tooltip-Tlsyx2YO.js";import{L as n}from"./launch-editor-
|
|
2
|
-
//# sourceMappingURL=db-
|
|
1
|
+
import{w as s}from"./chunk-EPOLDU6W-BCLmut3y.js";import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{I as a}from"./misc-W4055b-0.js";import{S as i}from"./tooltip-Tlsyx2YO.js";import{L as n}from"./launch-editor-0oPpbFQe.js";import"./index-CqIc3cxq.js";import"./index-vDCSPjrM.js";import"./progress-bar-DpWhcyhC.js";import"./pe-CIZUOJMr.js";import"./root-loader-BmUqzUDN.js";const g=s(function({loaderData:r}){const{db:o,filepath:t}=r;return e.jsxs("main",{children:[e.jsx("h1",{className:"text-2xl font-bold",children:"Database"}),e.jsx("div",{className:"prose mt-4",children:e.jsxs("callout-danger",{children:[e.jsx("strong",{children:"Warning:"})," Editing the database directly can corrupt your data. Be very careful!"]})}),e.jsxs(n,{file:t,className:"flex max-w-full items-center gap-2",children:[e.jsx(i,{content:"Open the database file in your editor",children:e.jsx(a,{name:"Files",className:"h-4 w-4",title:"Open in editor"})}),e.jsx("span",{className:"truncate",title:t,children:t})]}),e.jsx("pre",{className:"bg-muted text-foreground mt-4 max-h-[400px] w-full overflow-auto rounded border p-4 text-sm",children:JSON.stringify(o,null,2)})]})});export{g as default};
|
|
2
|
+
//# sourceMappingURL=db-BZ-tFzMp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db-
|
|
1
|
+
{"version":3,"file":"db-BZ-tFzMp.js","sources":["../../../app/routes/admin+/db.tsx"],"sourcesContent":["import { loadJSON } from '@epic-web/workshop-utils/data-storage.server'\nimport { readDb } from '@epic-web/workshop-utils/db.server'\nimport { Icon } from '#app/components/icons.tsx'\nimport { SimpleTooltip } from '#app/components/ui/tooltip.tsx'\nimport { ensureUndeployed } from '#app/utils/misc.tsx'\nimport { LaunchEditor } from '../launch-editor.tsx'\nimport { type Route } from './+types/db.ts'\n\nexport async function loader() {\n\tensureUndeployed()\n\tconst db = await readDb()\n\tconst { path: filepath } = await loadJSON()\n\n\treturn { db, filepath }\n}\n\nexport default function DbRoute({ loaderData }: Route.ComponentProps) {\n\tconst { db, filepath } = loaderData\n\treturn (\n\t\t<main>\n\t\t\t<h1 className=\"text-2xl font-bold\">Database</h1>\n\t\t\t<div className=\"prose mt-4\">\n\t\t\t\t<callout-danger>\n\t\t\t\t\t<strong>Warning:</strong> Editing the database directly can corrupt\n\t\t\t\t\tyour data. Be very careful!\n\t\t\t\t</callout-danger>\n\t\t\t</div>\n\t\t\t<LaunchEditor\n\t\t\t\tfile={filepath}\n\t\t\t\tclassName=\"flex max-w-full items-center gap-2\"\n\t\t\t>\n\t\t\t\t<SimpleTooltip content=\"Open the database file in your editor\">\n\t\t\t\t\t<Icon name=\"Files\" className=\"h-4 w-4\" title=\"Open in editor\" />\n\t\t\t\t</SimpleTooltip>\n\t\t\t\t<span className=\"truncate\" title={filepath}>\n\t\t\t\t\t{filepath}\n\t\t\t\t</span>\n\t\t\t</LaunchEditor>\n\t\t\t<pre className=\"bg-muted text-foreground mt-4 max-h-[400px] w-full overflow-auto rounded border p-4 text-sm\">\n\t\t\t\t{JSON.stringify(db, null, 2)}\n\t\t\t</pre>\n\t\t</main>\n\t)\n}\n"],"names":["db","_UNSAFE_withComponentProps","loaderData","filepath","children","jsx","className","jsxs","LaunchEditor","file","SimpleTooltip","content","Icon","name","title","stringify"],"mappings":"sXAgBA,MAAAA,EAAAC,EAAA,SAAgC,CAAEC,WAAAA,CAAW,EAAyB,CACrE,KAAM,CAAEF,GAAAA,EAAIG,SAAAA,CAAS,EAAID,EACzB,cACE,OAAA,CACAE,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAGC,UAAU,qBAAqBF,SAAA,UAAA,CAAQ,EAC3CC,EAAAA,IAAC,MAAA,CAAIC,UAAU,aACdF,gBAAC,iBAAA,CACAA,SAAA,CAAAC,EAAAA,IAAC,UAAOD,SAAA,WAAQ,EAAS,wEAAA,EAE1B,CAAA,CACD,EACAG,EAAAA,KAACC,EAAA,CACAC,KAAMN,EACNG,UAAU,qCAEVF,SAAA,CAAAC,EAAAA,IAACK,EAAA,CAAcC,QAAQ,wCACtBP,SAAAC,EAAAA,IAACO,EAAA,CAAKC,KAAK,QAAQP,UAAU,UAAUQ,MAAM,iBAAiB,CAAA,CAC/D,QACC,OAAA,CAAKR,UAAU,WAAWQ,MAAOX,EAChCC,SAAAD,CAAA,CACF,CAAA,CAAA,CACD,EACAE,EAAAA,IAAC,OAAIC,UAAU,8FACbF,cAAKW,UAAUf,EAAI,KAAM,CAAC,CAAA,CAC5B,CAAA,CAAA,CACD,CAEF,CAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{w as o,b as a,d as m,k as p}from"./chunk-EPOLDU6W-BCLmut3y.js";import{j as r}from"./jsx-runtime-C5WNSv3b.js";import{d as n,c}from"./misc-W4055b-0.js";import{U as f}from"./diff-
|
|
2
|
-
//# sourceMappingURL=diff-
|
|
1
|
+
import{w as o,b as a,d as m,k as p}from"./chunk-EPOLDU6W-BCLmut3y.js";import{j as r}from"./jsx-runtime-C5WNSv3b.js";import{d as n,c}from"./misc-W4055b-0.js";import{U as f}from"./diff-CC_jaH50.js";import{N as d}from"./nav-chevrons-Dk4GtZwQ.js";import"./index-CqIc3cxq.js";import"./accordion-CQ7oujC6.js";import"./tooltip-Tlsyx2YO.js";import"./index-vDCSPjrM.js";import"./index-CJDOQ1bl.js";import"./index-ynYvVAOK.js";import"./mdx-DwvWT4Ou.js";import"./index-DzdDahau.js";import"./epic-video-D4jBdSKs.js";import"./use-event-source-BuD4_2SF.js";import"./index-BrVvTrwg.js";import"./root-loader-BmUqzUDN.js";import"./pe-CIZUOJMr.js";import"./schemas-Uj5SZtvt.js";import"./online-DVk-W8Cr.js";import"./loading-CaCCsk9k.js";import"./format-CZ5n8p10.js";import"./user-CYXKquT7.js";import"./workshop-config-BMWaKPZT.js";import"./preload-helper-BXl3LOEh.js";import"./launch-editor-0oPpbFQe.js";import"./progress-bar-DpWhcyhC.js";import"./revalidation-ws-iocj9Mrl.js";const J=o(function(){const s=a(),[i]=m();new URLSearchParams(i).set("forceFresh","diff");const t=p(),e=n.useSpinDelay(t.state!=="idle",{delay:200,minDuration:200});return r.jsxs("div",{className:c("h-screen-safe relative",{"cursor-wait opacity-30":e}),children:[r.jsx("div",{className:"h-full pb-16",children:r.jsx(f,{diff:s.diff,allApps:s.allApps,userHasAccessPromise:s.userHasAccessPromise})}),r.jsx("div",{className:"bg-background fixed inset-x-0 bottom-0 z-10 flex h-16 items-center justify-end border-t",children:r.jsx("div",{className:"flex h-full items-center justify-end",children:r.jsx(d,{prev:s.prevLink,next:s.nextLink})})})]})});export{J as default};
|
|
2
|
+
//# sourceMappingURL=diff-0VO9C_Sf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-
|
|
1
|
+
{"version":3,"file":"diff-0VO9C_Sf.js","sources":["../../../app/routes/diff.tsx"],"sourcesContent":["import {\n\tgetAppByName,\n\tgetAppDisplayName,\n\tgetApps,\n\tisExerciseStepApp,\n} from '@epic-web/workshop-utils/apps.server'\nimport { getDiffCode } from '@epic-web/workshop-utils/diff.server'\nimport { userHasAccessToWorkshop } from '@epic-web/workshop-utils/epic-api.server'\nimport { makeTimings } from '@epic-web/workshop-utils/timing.server'\nimport {\n\ttype LoaderFunctionArgs,\n\tuseLoaderData,\n\tuseNavigation,\n\tuseSearchParams,\n} from 'react-router'\nimport { useSpinDelay } from 'spin-delay'\nimport { Diff } from '#app/components/diff.tsx'\nimport { NavChevrons } from '#app/components/nav-chevrons.tsx'\nimport { cn } from '#app/utils/misc.tsx'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n\tconst reqUrl = new URL(request.url)\n\tconst searchParams = reqUrl.searchParams\n\tconst timings = makeTimings('diffLoader')\n\tconst cacheOptions = { request, timings }\n\tconst allAppsFull = await getApps()\n\tconst app1Name = reqUrl.searchParams.get('app1')\n\tconst app2Name = reqUrl.searchParams.get('app2')\n\n\tconst usingDefaultApp1 = !app1Name\n\n\t// defaults to first problem app\n\tconst app1 = app1Name\n\t\t? await getAppByName(app1Name)\n\t\t: allAppsFull.filter(isExerciseStepApp).at(0)\n\n\t// defaults to last exercise step app\n\tconst app2 = app2Name\n\t\t? await getAppByName(app2Name)\n\t\t: allAppsFull.filter(isExerciseStepApp).at(-1)\n\n\tasync function getDiffProp() {\n\t\tif (!app1 || !app2) {\n\t\t\treturn {\n\t\t\t\tapp1: app1?.name,\n\t\t\t\tapp2: app2?.name,\n\t\t\t\tdiffCode: null,\n\t\t\t}\n\t\t}\n\t\tconst diffCode = await getDiffCode(app1, app2, {\n\t\t\t...cacheOptions,\n\t\t\tforceFresh: searchParams.get('forceFresh') === 'diff',\n\t\t}).catch((e) => {\n\t\t\tconsole.error(e)\n\t\t\treturn null\n\t\t})\n\t\treturn {\n\t\t\tapp1: app1.name,\n\t\t\tapp2: app2.name,\n\t\t\tdiffCode,\n\t\t}\n\t}\n\n\tconst allApps = allAppsFull\n\t\t.filter((a, i, ar) => ar.findIndex((b) => a.name === b.name) === i)\n\t\t.map((a) => ({\n\t\t\tdisplayName: getAppDisplayName(a, allAppsFull),\n\t\t\tname: a.name,\n\t\t\ttitle: a.title,\n\t\t\ttype: a.type,\n\t\t}))\n\n\tconst diff = getDiffProp()\n\tconst app1Index = allApps.findIndex((a) => a.name === app1?.name)\n\tconst prevApp1Index = usingDefaultApp1\n\t\t? allApps.length - 2\n\t\t: app1Index === 0\n\t\t\t? -2\n\t\t\t: app1Index - 1\n\tconst prevApp2Index = prevApp1Index + 1\n\tconst nextApp1Index = usingDefaultApp1\n\t\t? 0\n\t\t: app1Index + 1 < allApps.length\n\t\t\t? app1Index + 1\n\t\t\t: -2\n\tconst nextApp2Index = nextApp1Index + 1\n\tconst prevApp1 = allAppsFull[prevApp1Index]?.name\n\tconst prevApp2 = allAppsFull[prevApp2Index]?.name\n\tconst nextApp1 = allAppsFull[nextApp1Index]?.name\n\tconst nextApp2 = allAppsFull[nextApp2Index]?.name\n\tconst prevSearchParams = new URLSearchParams(reqUrl.searchParams)\n\tprevSearchParams.set('app1', prevApp1 ?? '')\n\tprevSearchParams.set('app2', prevApp2 ?? '')\n\tconst nextSearchParams = new URLSearchParams(reqUrl.searchParams)\n\tnextSearchParams.set('app1', nextApp1 ?? '')\n\tnextSearchParams.set('app2', nextApp2 ?? '')\n\treturn {\n\t\tuserHasAccessPromise: userHasAccessToWorkshop({ request, timings }),\n\t\tallApps,\n\t\tdiff,\n\t\tprevLink:\n\t\t\tprevApp1 && prevApp2\n\t\t\t\t? { to: `/diff?${prevSearchParams}`, 'aria-label': 'Previous App' }\n\t\t\t\t: { to: '/diff' },\n\t\tnextLink:\n\t\t\tnextApp1 && nextApp2\n\t\t\t\t? { to: `/diff?${nextSearchParams}`, 'aria-label': 'Next App' }\n\t\t\t\t: { to: '/diff' },\n\t}\n}\n\nexport default function DiffViewer() {\n\tconst data = useLoaderData<typeof loader>()\n\tconst [params] = useSearchParams()\n\tconst paramsWithForcedRefresh = new URLSearchParams(params)\n\tparamsWithForcedRefresh.set('forceFresh', 'diff')\n\tconst navigation = useNavigation()\n\tconst isNavigating = useSpinDelay(navigation.state !== 'idle', {\n\t\tdelay: 200,\n\t\tminDuration: 200,\n\t})\n\n\treturn (\n\t\t<div\n\t\t\tclassName={cn('h-screen-safe relative', {\n\t\t\t\t'cursor-wait opacity-30': isNavigating,\n\t\t\t})}\n\t\t>\n\t\t\t<div className=\"h-full pb-16\">\n\t\t\t\t<Diff\n\t\t\t\t\tdiff={data.diff}\n\t\t\t\t\tallApps={data.allApps}\n\t\t\t\t\tuserHasAccessPromise={data.userHasAccessPromise}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div className=\"bg-background fixed inset-x-0 bottom-0 z-10 flex h-16 items-center justify-end border-t\">\n\t\t\t\t<div className=\"flex h-full items-center justify-end\">\n\t\t\t\t\t<NavChevrons prev={data.prevLink} next={data.nextLink} />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],"names":["diff","_UNSAFE_withComponentProps","data","useLoaderData","params","useSearchParams","URLSearchParams","set","navigation","useNavigation","isNavigating","useSpinDelay","state","delay","minDuration","jsxs","className","cn","children","jsx","Diff","allApps","userHasAccessPromise","NavChevrons","prev","prevLink","next","nextLink"],"mappings":"+7BA+GA,MAAAA,EAAAC,EAAA,UAAqC,CACpC,MAAMC,EAAOC,EAAA,EACP,CAACC,CAAM,EAAIC,EAAA,EACe,IAAIC,gBAAgBF,CAAM,EAClCG,IAAI,aAAc,MAAM,EAChD,MAAMC,EAAaC,EAAA,EACbC,EAAeC,EAAAA,aAAaH,EAAWI,QAAU,OAAQ,CAC9DC,MAAO,IACPC,YAAa,GACd,CAAC,EAED,OACCC,EAAAA,KAAC,MAAA,CACAC,UAAWC,EAAG,yBAA0B,CACvC,yBAA0BP,CAC3B,CAAC,EAEDQ,SAAA,CAAAC,EAAAA,IAAC,MAAA,CAAIH,UAAU,eACdE,SAAAC,EAAAA,IAACC,EAAA,CACApB,KAAME,EAAKF,KACXqB,QAASnB,EAAKmB,QACdC,qBAAsBpB,EAAKoB,qBAC5B,CAAA,CACD,QACC,MAAA,CAAIN,UAAU,0FACdE,SAAAC,EAAAA,IAAC,OAAIH,UAAU,uCACdE,SAAAC,EAAAA,IAACI,EAAA,CAAYC,KAAMtB,EAAKuB,SAAUC,KAAMxB,EAAKyB,SAAU,EACxD,CAAA,CACD,CAAA,CAAA,CACD,CAEF,CAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{m as j,R as g,A as N}from"./accordion-CQ7oujC6.js";import{R as
|
|
2
|
-
//# sourceMappingURL=diff-
|
|
1
|
+
import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{m as j,R as g,A as N}from"./accordion-CQ7oujC6.js";import{R as w,T as b,V as v,I as y,P as S,C as k,S as R,a as D,G as C,L as I,c as A,d as T,e as L,f as P}from"./index-CJDOQ1bl.js";import{I as o,d as $,c as E,a as x}from"./misc-W4055b-0.js";import{r as f}from"./index-CqIc3cxq.js";import{A as h,n as V,d as F,k as U,L as B,F as G}from"./chunk-EPOLDU6W-BCLmut3y.js";import{M}from"./mdx-DwvWT4Ou.js";import{a as W}from"./root-loader-BmUqzUDN.js";import{D as z}from"./epic-video-D4jBdSKs.js";import{u as H}from"./revalidation-ws-iocj9Mrl.js";import{S as p}from"./tooltip-Tlsyx2YO.js";const Y=r=>e.jsx("pre",{...r}),q={Accordion:N,pre:Y};function J({app1:r,app2:a}){const t=W(),l=t.find(n=>n.name===r),c=t.find(n=>n.name===a);return H({watchPaths:[l?.fullPath,c?.fullPath].filter(Boolean)}),null}function oe({userHasAccessPromise:r,diff:a,allApps:t}){return e.jsx(j,{fallbackRender:()=>e.jsx("div",{className:"w-full p-12",children:e.jsxs("div",{className:"flex w-full flex-col gap-4 text-center",children:[e.jsx("p",{className:"text-2xl font-bold",children:"Error"}),e.jsx("p",{className:"text-lg",children:"There was an error loading the user access."})]})}),children:e.jsx(f.Suspense,{fallback:e.jsx("div",{className:"flex items-center justify-center p-8",children:e.jsx(p,{content:"Loading user access",children:e.jsx(o,{name:"Refresh",className:"animate-spin"})})}),children:e.jsx(h,{resolve:r,children:l=>l?e.jsx(K,{diff:a,allApps:t}):e.jsxs("div",{className:"w-full p-12",children:[e.jsxs("div",{className:"flex w-full flex-col gap-4 text-center",children:[e.jsx("p",{className:"text-2xl font-bold",children:"Access Denied"}),e.jsx("p",{className:"text-lg",children:"You must login or register for the workshop to view the diff."})]}),e.jsx("div",{className:"h-16"}),e.jsx("p",{className:"pb-4",children:"Check out this video to see how the diff tab works."}),e.jsx(z,{url:"https://www.epicweb.dev/tips/epic-workshop-diff-tab-demo"})]})})})})}function K({diff:r,allApps:a}){const t=V(),[l]=F(),c=new URLSearchParams(l);c.set("forceFresh","diff");const n=U(),u=$.useSpinDelay(n.state!=="idle",{delay:0,minDuration:1e3}),m=[];for(const[s,i]of l.entries())s==="app1"||s==="app2"||m.push(e.jsx("input",{type:"hidden",name:s,value:i},s));return e.jsx(f.Suspense,{fallback:e.jsx("div",{className:"flex items-center justify-center p-8",children:e.jsx(p,{content:"Loading diff",children:e.jsx(o,{name:"Refresh",className:"animate-spin"})})}),children:e.jsx(h,{resolve:r,errorElement:e.jsx("p",{className:"text-foreground-destructive p-6",children:"There was an error calculating the diff. Sorry."}),children:s=>e.jsxs("div",{className:"flex h-full w-full flex-col",children:[e.jsxs("div",{className:"flex h-14 min-h-14 w-full overflow-x-hidden border-b",children:[e.jsx("div",{className:"border-r",children:e.jsx(p,{content:"Reload diff",children:e.jsx(B,{to:`.?${c}`,className:"flex h-full w-14 items-center justify-center",children:e.jsx(o,{name:"Refresh",className:E({"animate-spin":u})})})})}),e.jsxs(G,{onChange:i=>t(i.currentTarget),className:"scrollbar-thin scrollbar-thumb-scrollbar flex h-full flex-1 items-center overflow-x-auto",children:[m,e.jsx(d,{name:"app1",label:"App 1",className:"border-r",allApps:a,defaultValue:s.app1}),e.jsx(d,{name:"app2",label:"App 2",allApps:a,defaultValue:s.app2})]},`${s.app1}${s.app2}`)]}),e.jsx("div",{className:"scrollbar-thin scrollbar-thumb-scrollbar grow overflow-y-scroll",children:s.diffCode?e.jsx("div",{children:e.jsx(g,{className:"w-full",type:"multiple",children:e.jsx(M,{code:s.diffCode,components:q})})}):s.app1&&s.app2?e.jsx("p",{className:"bg-foreground text-background m-5 inline-flex items-center justify-center px-1 py-0.5 font-mono text-sm uppercase",children:"There was a problem generating the diff"}):e.jsx("p",{className:"bg-foreground text-background m-5 inline-flex items-center justify-center px-1 py-0.5 font-mono text-sm uppercase",children:"Select two apps to compare"})}),e.jsx(J,{app1:s.app1,app2:s.app2})]})})})}function d({name:r,label:a,className:t,allApps:l,defaultValue:c}){return e.jsxs(w,{name:r,defaultValue:c,children:[e.jsxs(b,{className:x("radix-placeholder:text-muted-foreground flex h-full w-full max-w-[50%] items-center justify-between px-3 text-left focus-visible:outline-none",t),"aria-label":`Select ${a} for git Diff`,children:[e.jsxs("span",{className:"truncate",children:[a,":"," ",e.jsx(v,{placeholder:`Select ${a}`,className:"inline-block w-40 truncate"})]}),e.jsx(y,{className:"",children:e.jsx(o,{name:"TriangleDownSmall"})})]}),e.jsx(S,{children:e.jsxs(k,{position:"popper",align:"start",className:"bg-popover text-popover-foreground z-20 max-h-[50vh] lg:max-h-[70vh]",children:[e.jsx(R,{className:"flex h-5 cursor-default items-center justify-center",children:e.jsx(o,{name:"ChevronUp"})}),e.jsx(D,{className:"p-3",children:e.jsxs(C,{children:[e.jsx(I,{className:"px-5 pb-3 font-mono uppercase",children:a}),l.map(n=>e.jsx(O,{value:n.name,children:n.displayName},n.name))]})}),e.jsx(A,{className:"flex h-5 cursor-default items-center justify-center",children:e.jsx(o,{name:"ChevronDown"})})]})})]})}const O=({ref:r,children:a,className:t,...l})=>e.jsxs(T,{className:x("radix-disabled:text-muted-foreground radix-highlighted:opacity-100 radix-highlighted:outline-none radix-state-checked:opacity-100 relative flex cursor-pointer items-center rounded px-10 py-2 leading-none opacity-80 select-none",t),...l,ref:r,children:[e.jsx(L,{children:a}),e.jsx(P,{className:"absolute left-0 inline-flex w-[25px] items-center justify-center",children:e.jsx(o,{name:"CheckSmall"})})]});export{oe as U};
|
|
2
|
+
//# sourceMappingURL=diff-CC_jaH50.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-Col_iM3X.js","sources":["../../../app/components/diff.tsx"],"sourcesContent":["import * as Accordion from '@radix-ui/react-accordion'\nimport * as Select from '@radix-ui/react-select'\nimport { clsx } from 'clsx'\nimport React, { Suspense } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport {\n\tAwait,\n\tForm,\n\tLink,\n\tuseNavigation,\n\tuseSearchParams,\n\tuseSubmit,\n} from 'react-router'\nimport { useSpinDelay } from 'spin-delay'\nimport AccordionComponent from '#app/components/accordion.tsx'\nimport { Mdx } from '#app/utils/mdx.tsx'\nimport { cn } from '#app/utils/misc.tsx'\nimport { useApps } from '#app/utils/root-loader.ts'\nimport { DeferredEpicVideo } from './epic-video.tsx'\nimport { Icon } from './icons.tsx'\nimport { useRevalidationWS } from './revalidation-ws.tsx'\nimport { SimpleTooltip } from './ui/tooltip.tsx'\n\ntype diffProp = {\n\tapp1?: string\n\tapp2?: string\n\tdiffCode?: string | null\n}\n\nconst pre = (props: any) => <pre {...props} />\n\nconst mdxComponents = {\n\tAccordion: AccordionComponent,\n\t// override the pre-with-buttons\n\tpre,\n}\n\nfunction RevalidateApps({\n\tapp1: app1Name,\n\tapp2: app2Name,\n}: {\n\tapp1?: string\n\tapp2?: string\n}) {\n\tconst apps = useApps()\n\tconst app1 = apps.find((app) => app.name === app1Name)\n\tconst app2 = apps.find((app) => app.name === app2Name)\n\n\tuseRevalidationWS({\n\t\twatchPaths: [app1?.fullPath, app2?.fullPath].filter(Boolean),\n\t})\n\treturn null\n}\n\nexport function UserHasAccessDiff({\n\tuserHasAccessPromise,\n\tdiff,\n\tallApps,\n}: {\n\tuserHasAccessPromise: Promise<boolean>\n\tdiff: Promise<diffProp> | diffProp\n\tallApps: Array<{ name: string; displayName: string }>\n}) {\n\treturn (\n\t\t<ErrorBoundary\n\t\t\tfallbackRender={() => (\n\t\t\t\t<div className=\"w-full p-12\">\n\t\t\t\t\t<div className=\"flex w-full flex-col gap-4 text-center\">\n\t\t\t\t\t\t<p className=\"text-2xl font-bold\">Error</p>\n\t\t\t\t\t\t<p className=\"text-lg\">\n\t\t\t\t\t\t\tThere was an error loading the user access.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t>\n\t\t\t<Suspense\n\t\t\t\tfallback={\n\t\t\t\t\t<div className=\"flex items-center justify-center p-8\">\n\t\t\t\t\t\t<SimpleTooltip content=\"Loading user access\">\n\t\t\t\t\t\t\t<Icon name=\"Refresh\" className=\"animate-spin\" />\n\t\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t<Await resolve={userHasAccessPromise}>\n\t\t\t\t\t{(userHasAccess) =>\n\t\t\t\t\t\tuserHasAccess ? (\n\t\t\t\t\t\t\t<DiffImplementation diff={diff} allApps={allApps} />\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<div className=\"w-full p-12\">\n\t\t\t\t\t\t\t\t<div className=\"flex w-full flex-col gap-4 text-center\">\n\t\t\t\t\t\t\t\t\t<p className=\"text-2xl font-bold\">Access Denied</p>\n\t\t\t\t\t\t\t\t\t<p className=\"text-lg\">\n\t\t\t\t\t\t\t\t\t\tYou must login or register for the workshop to view the\n\t\t\t\t\t\t\t\t\t\tdiff.\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div className=\"h-16\" />\n\t\t\t\t\t\t\t\t<p className=\"pb-4\">\n\t\t\t\t\t\t\t\t\tCheck out this video to see how the diff tab works.\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t<DeferredEpicVideo url=\"https://www.epicweb.dev/tips/epic-workshop-diff-tab-demo\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t</Await>\n\t\t\t</Suspense>\n\t\t</ErrorBoundary>\n\t)\n}\n\nexport { UserHasAccessDiff as Diff }\n\nexport function DiffImplementation({\n\tdiff,\n\tallApps,\n}: {\n\tdiff: Promise<diffProp> | diffProp\n\tallApps: Array<{ name: string; displayName: string }>\n}) {\n\tconst submit = useSubmit()\n\tconst [params] = useSearchParams()\n\tconst paramsWithForcedRefresh = new URLSearchParams(params)\n\tparamsWithForcedRefresh.set('forceFresh', 'diff')\n\tconst navigation = useNavigation()\n\tconst spinnerNavigating = useSpinDelay(navigation.state !== 'idle', {\n\t\tdelay: 0,\n\t\tminDuration: 1000,\n\t})\n\n\tconst hiddenInputs: Array<React.ReactNode> = []\n\tfor (const [key, value] of params.entries()) {\n\t\tif (key === 'app1' || key === 'app2') continue\n\t\thiddenInputs.push(\n\t\t\t<input key={key} type=\"hidden\" name={key} value={value} />,\n\t\t)\n\t}\n\n\treturn (\n\t\t<Suspense\n\t\t\tfallback={\n\t\t\t\t<div className=\"flex items-center justify-center p-8\">\n\t\t\t\t\t<SimpleTooltip content=\"Loading diff\">\n\t\t\t\t\t\t<Icon name=\"Refresh\" className=\"animate-spin\" />\n\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t</div>\n\t\t\t}\n\t\t>\n\t\t\t<Await\n\t\t\t\tresolve={diff}\n\t\t\t\terrorElement={\n\t\t\t\t\t<p className=\"text-foreground-destructive p-6\">\n\t\t\t\t\t\tThere was an error calculating the diff. Sorry.\n\t\t\t\t\t</p>\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t{(diff) => (\n\t\t\t\t\t<div className=\"flex h-full w-full flex-col\">\n\t\t\t\t\t\t<div className=\"flex h-14 min-h-14 w-full overflow-x-hidden border-b\">\n\t\t\t\t\t\t\t<div className=\"border-r\">\n\t\t\t\t\t\t\t\t<SimpleTooltip content=\"Reload diff\">\n\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\tto={`.?${paramsWithForcedRefresh}`}\n\t\t\t\t\t\t\t\t\t\tclassName=\"flex h-full w-14 items-center justify-center\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\tname=\"Refresh\"\n\t\t\t\t\t\t\t\t\t\t\tclassName={cn({ 'animate-spin': spinnerNavigating })}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<Form\n\t\t\t\t\t\t\t\tonChange={(e) => submit(e.currentTarget)}\n\t\t\t\t\t\t\t\tclassName=\"scrollbar-thin scrollbar-thumb-scrollbar flex h-full flex-1 items-center overflow-x-auto\"\n\t\t\t\t\t\t\t\tkey={`${diff.app1}${diff.app2}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{hiddenInputs}\n\t\t\t\t\t\t\t\t<SelectFileToDiff\n\t\t\t\t\t\t\t\t\tname=\"app1\"\n\t\t\t\t\t\t\t\t\tlabel=\"App 1\"\n\t\t\t\t\t\t\t\t\tclassName=\"border-r\"\n\t\t\t\t\t\t\t\t\tallApps={allApps}\n\t\t\t\t\t\t\t\t\tdefaultValue={diff.app1}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<SelectFileToDiff\n\t\t\t\t\t\t\t\t\tname=\"app2\"\n\t\t\t\t\t\t\t\t\tlabel=\"App 2\"\n\t\t\t\t\t\t\t\t\tallApps={allApps}\n\t\t\t\t\t\t\t\t\tdefaultValue={diff.app2}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</Form>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"scrollbar-thin scrollbar-thumb-scrollbar grow overflow-y-scroll\">\n\t\t\t\t\t\t\t{diff.diffCode ? (\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<Accordion.Root className=\"w-full\" type=\"multiple\">\n\t\t\t\t\t\t\t\t\t\t<Mdx code={diff.diffCode} components={mdxComponents} />\n\t\t\t\t\t\t\t\t\t</Accordion.Root>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : diff.app1 && diff.app2 ? (\n\t\t\t\t\t\t\t\t<p className=\"bg-foreground text-background m-5 inline-flex items-center justify-center px-1 py-0.5 font-mono text-sm uppercase\">\n\t\t\t\t\t\t\t\t\tThere was a problem generating the diff\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<p className=\"bg-foreground text-background m-5 inline-flex items-center justify-center px-1 py-0.5 font-mono text-sm uppercase\">\n\t\t\t\t\t\t\t\t\tSelect two apps to compare\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<RevalidateApps app1={diff.app1} app2={diff.app2} />\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</Await>\n\t\t</Suspense>\n\t)\n}\n\nfunction SelectFileToDiff({\n\tname,\n\tlabel,\n\tclassName,\n\tallApps,\n\tdefaultValue,\n}: {\n\tname: string\n\tlabel: string\n\tclassName?: string\n\tallApps: Array<{ name: string; displayName: string }>\n\tdefaultValue?: string\n}) {\n\treturn (\n\t\t<Select.Root name={name} defaultValue={defaultValue}>\n\t\t\t<Select.Trigger\n\t\t\t\tclassName={clsx(\n\t\t\t\t\t'radix-placeholder:text-muted-foreground flex h-full w-full max-w-[50%] items-center justify-between px-3 text-left focus-visible:outline-none',\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\taria-label={`Select ${label} for git Diff`}\n\t\t\t>\n\t\t\t\t<span className=\"truncate\">\n\t\t\t\t\t{label}:{' '}\n\t\t\t\t\t<Select.Value\n\t\t\t\t\t\tplaceholder={`Select ${label}`}\n\t\t\t\t\t\tclassName=\"inline-block w-40 truncate\"\n\t\t\t\t\t/>\n\t\t\t\t</span>\n\t\t\t\t<Select.Icon className=\"\">\n\t\t\t\t\t<Icon name=\"TriangleDownSmall\" />\n\t\t\t\t</Select.Icon>\n\t\t\t</Select.Trigger>\n\t\t\t<Select.Portal>\n\t\t\t\t<Select.Content\n\t\t\t\t\tposition=\"popper\"\n\t\t\t\t\talign=\"start\"\n\t\t\t\t\tclassName=\"bg-popover text-popover-foreground z-20 max-h-[50vh] lg:max-h-[70vh]\"\n\t\t\t\t>\n\t\t\t\t\t<Select.ScrollUpButton className=\"flex h-5 cursor-default items-center justify-center\">\n\t\t\t\t\t\t<Icon name=\"ChevronUp\" />\n\t\t\t\t\t</Select.ScrollUpButton>\n\t\t\t\t\t<Select.Viewport className=\"p-3\">\n\t\t\t\t\t\t<Select.Group>\n\t\t\t\t\t\t\t<Select.Label className=\"px-5 pb-3 font-mono uppercase\">\n\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t</Select.Label>\n\t\t\t\t\t\t\t{allApps.map((app) => {\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<SelectItem key={app.name} value={app.name}>\n\t\t\t\t\t\t\t\t\t\t{app.displayName}\n\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</Select.Group>\n\t\t\t\t\t</Select.Viewport>\n\t\t\t\t\t<Select.ScrollDownButton className=\"flex h-5 cursor-default items-center justify-center\">\n\t\t\t\t\t\t<Icon name=\"ChevronDown\" />\n\t\t\t\t\t</Select.ScrollDownButton>\n\t\t\t\t</Select.Content>\n\t\t\t</Select.Portal>\n\t\t</Select.Root>\n\t)\n}\n\nconst SelectItem: React.FC<any> = ({\n\tref: forwardedRef,\n\tchildren,\n\tclassName,\n\t...props\n}) => {\n\treturn (\n\t\t<Select.Item\n\t\t\tclassName={clsx(\n\t\t\t\t'radix-disabled:text-muted-foreground radix-highlighted:opacity-100 radix-highlighted:outline-none radix-state-checked:opacity-100 relative flex cursor-pointer items-center rounded px-10 py-2 leading-none opacity-80 select-none',\n\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t\tref={forwardedRef}\n\t\t>\n\t\t\t<Select.ItemText>{children}</Select.ItemText>\n\t\t\t<Select.ItemIndicator className=\"absolute left-0 inline-flex w-[25px] items-center justify-center\">\n\t\t\t\t<Icon name=\"CheckSmall\" />\n\t\t\t</Select.ItemIndicator>\n\t\t</Select.Item>\n\t)\n}\n"],"names":["pre","props","jsx","mdxComponents","AccordionComponent","RevalidateApps","app1Name","app2Name","apps","useApps","app1","app","app2","useRevalidationWS","UserHasAccessDiff","userHasAccessPromise","diff","allApps","ErrorBoundary","jsxs","Suspense","SimpleTooltip","Icon","Await","userHasAccess","DiffImplementation","DeferredEpicVideo","submit","useSubmit","params","useSearchParams","paramsWithForcedRefresh","navigation","useNavigation","spinnerNavigating","useSpinDelay","hiddenInputs","key","value","Link","cn","Form","e","SelectFileToDiff","Accordion.Root","Mdx","name","label","className","defaultValue","Select.Root","Select.Trigger","clsx","Select.Value","Select.Icon","Select.Portal","Select.Content","Select.ScrollUpButton","Select.Viewport","Select.Group","Select.Label","SelectItem","Select.ScrollDownButton","forwardedRef","children","Select.Item","Select.ItemText","Select.ItemIndicator"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"diff-CC_jaH50.js","sources":["../../../app/components/diff.tsx"],"sourcesContent":["import * as Accordion from '@radix-ui/react-accordion'\nimport * as Select from '@radix-ui/react-select'\nimport { clsx } from 'clsx'\nimport React, { Suspense } from 'react'\nimport { ErrorBoundary } from 'react-error-boundary'\nimport {\n\tAwait,\n\tForm,\n\tLink,\n\tuseNavigation,\n\tuseSearchParams,\n\tuseSubmit,\n} from 'react-router'\nimport { useSpinDelay } from 'spin-delay'\nimport AccordionComponent from '#app/components/accordion.tsx'\nimport { Mdx } from '#app/utils/mdx.tsx'\nimport { cn } from '#app/utils/misc.tsx'\nimport { useApps } from '#app/utils/root-loader.ts'\nimport { DeferredEpicVideo } from './epic-video.tsx'\nimport { Icon } from './icons.tsx'\nimport { useRevalidationWS } from './revalidation-ws.tsx'\nimport { SimpleTooltip } from './ui/tooltip.tsx'\n\ntype diffProp = {\n\tapp1?: string\n\tapp2?: string\n\tdiffCode?: string | null\n}\n\nconst pre = (props: any) => <pre {...props} />\n\nconst mdxComponents = {\n\tAccordion: AccordionComponent,\n\t// override the pre-with-buttons\n\tpre,\n}\n\nfunction RevalidateApps({\n\tapp1: app1Name,\n\tapp2: app2Name,\n}: {\n\tapp1?: string\n\tapp2?: string\n}) {\n\tconst apps = useApps()\n\tconst app1 = apps.find((app) => app.name === app1Name)\n\tconst app2 = apps.find((app) => app.name === app2Name)\n\n\tuseRevalidationWS({\n\t\twatchPaths: [app1?.fullPath, app2?.fullPath].filter(Boolean),\n\t})\n\treturn null\n}\n\nexport function UserHasAccessDiff({\n\tuserHasAccessPromise,\n\tdiff,\n\tallApps,\n}: {\n\tuserHasAccessPromise: Promise<boolean>\n\tdiff: Promise<diffProp> | diffProp\n\tallApps: Array<{ name: string; displayName: string }>\n}) {\n\treturn (\n\t\t<ErrorBoundary\n\t\t\tfallbackRender={() => (\n\t\t\t\t<div className=\"w-full p-12\">\n\t\t\t\t\t<div className=\"flex w-full flex-col gap-4 text-center\">\n\t\t\t\t\t\t<p className=\"text-2xl font-bold\">Error</p>\n\t\t\t\t\t\t<p className=\"text-lg\">\n\t\t\t\t\t\t\tThere was an error loading the user access.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t>\n\t\t\t<Suspense\n\t\t\t\tfallback={\n\t\t\t\t\t<div className=\"flex items-center justify-center p-8\">\n\t\t\t\t\t\t<SimpleTooltip content=\"Loading user access\">\n\t\t\t\t\t\t\t<Icon name=\"Refresh\" className=\"animate-spin\" />\n\t\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t<Await resolve={userHasAccessPromise}>\n\t\t\t\t\t{(userHasAccess) =>\n\t\t\t\t\t\tuserHasAccess ? (\n\t\t\t\t\t\t\t<DiffImplementation diff={diff} allApps={allApps} />\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<div className=\"w-full p-12\">\n\t\t\t\t\t\t\t\t<div className=\"flex w-full flex-col gap-4 text-center\">\n\t\t\t\t\t\t\t\t\t<p className=\"text-2xl font-bold\">Access Denied</p>\n\t\t\t\t\t\t\t\t\t<p className=\"text-lg\">\n\t\t\t\t\t\t\t\t\t\tYou must login or register for the workshop to view the\n\t\t\t\t\t\t\t\t\t\tdiff.\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div className=\"h-16\" />\n\t\t\t\t\t\t\t\t<p className=\"pb-4\">\n\t\t\t\t\t\t\t\t\tCheck out this video to see how the diff tab works.\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t<DeferredEpicVideo url=\"https://www.epicweb.dev/tips/epic-workshop-diff-tab-demo\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t</Await>\n\t\t\t</Suspense>\n\t\t</ErrorBoundary>\n\t)\n}\n\nexport { UserHasAccessDiff as Diff }\n\nexport function DiffImplementation({\n\tdiff,\n\tallApps,\n}: {\n\tdiff: Promise<diffProp> | diffProp\n\tallApps: Array<{ name: string; displayName: string }>\n}) {\n\tconst submit = useSubmit()\n\tconst [params] = useSearchParams()\n\tconst paramsWithForcedRefresh = new URLSearchParams(params)\n\tparamsWithForcedRefresh.set('forceFresh', 'diff')\n\tconst navigation = useNavigation()\n\tconst spinnerNavigating = useSpinDelay(navigation.state !== 'idle', {\n\t\tdelay: 0,\n\t\tminDuration: 1000,\n\t})\n\n\tconst hiddenInputs: Array<React.ReactNode> = []\n\tfor (const [key, value] of params.entries()) {\n\t\tif (key === 'app1' || key === 'app2') continue\n\t\thiddenInputs.push(\n\t\t\t<input key={key} type=\"hidden\" name={key} value={value} />,\n\t\t)\n\t}\n\n\treturn (\n\t\t<Suspense\n\t\t\tfallback={\n\t\t\t\t<div className=\"flex items-center justify-center p-8\">\n\t\t\t\t\t<SimpleTooltip content=\"Loading diff\">\n\t\t\t\t\t\t<Icon name=\"Refresh\" className=\"animate-spin\" />\n\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t</div>\n\t\t\t}\n\t\t>\n\t\t\t<Await\n\t\t\t\tresolve={diff}\n\t\t\t\terrorElement={\n\t\t\t\t\t<p className=\"text-foreground-destructive p-6\">\n\t\t\t\t\t\tThere was an error calculating the diff. Sorry.\n\t\t\t\t\t</p>\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t{(diff) => (\n\t\t\t\t\t<div className=\"flex h-full w-full flex-col\">\n\t\t\t\t\t\t<div className=\"flex h-14 min-h-14 w-full overflow-x-hidden border-b\">\n\t\t\t\t\t\t\t<div className=\"border-r\">\n\t\t\t\t\t\t\t\t<SimpleTooltip content=\"Reload diff\">\n\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\tto={`.?${paramsWithForcedRefresh}`}\n\t\t\t\t\t\t\t\t\t\tclassName=\"flex h-full w-14 items-center justify-center\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Icon\n\t\t\t\t\t\t\t\t\t\t\tname=\"Refresh\"\n\t\t\t\t\t\t\t\t\t\t\tclassName={cn({ 'animate-spin': spinnerNavigating })}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t</SimpleTooltip>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<Form\n\t\t\t\t\t\t\t\tonChange={(e) => submit(e.currentTarget)}\n\t\t\t\t\t\t\t\tclassName=\"scrollbar-thin scrollbar-thumb-scrollbar flex h-full flex-1 items-center overflow-x-auto\"\n\t\t\t\t\t\t\t\tkey={`${diff.app1}${diff.app2}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{hiddenInputs}\n\t\t\t\t\t\t\t\t<SelectFileToDiff\n\t\t\t\t\t\t\t\t\tname=\"app1\"\n\t\t\t\t\t\t\t\t\tlabel=\"App 1\"\n\t\t\t\t\t\t\t\t\tclassName=\"border-r\"\n\t\t\t\t\t\t\t\t\tallApps={allApps}\n\t\t\t\t\t\t\t\t\tdefaultValue={diff.app1}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<SelectFileToDiff\n\t\t\t\t\t\t\t\t\tname=\"app2\"\n\t\t\t\t\t\t\t\t\tlabel=\"App 2\"\n\t\t\t\t\t\t\t\t\tallApps={allApps}\n\t\t\t\t\t\t\t\t\tdefaultValue={diff.app2}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</Form>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"scrollbar-thin scrollbar-thumb-scrollbar grow overflow-y-scroll\">\n\t\t\t\t\t\t\t{diff.diffCode ? (\n\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t<Accordion.Root className=\"w-full\" type=\"multiple\">\n\t\t\t\t\t\t\t\t\t\t<Mdx code={diff.diffCode} components={mdxComponents} />\n\t\t\t\t\t\t\t\t\t</Accordion.Root>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : diff.app1 && diff.app2 ? (\n\t\t\t\t\t\t\t\t<p className=\"bg-foreground text-background m-5 inline-flex items-center justify-center px-1 py-0.5 font-mono text-sm uppercase\">\n\t\t\t\t\t\t\t\t\tThere was a problem generating the diff\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<p className=\"bg-foreground text-background m-5 inline-flex items-center justify-center px-1 py-0.5 font-mono text-sm uppercase\">\n\t\t\t\t\t\t\t\t\tSelect two apps to compare\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<RevalidateApps app1={diff.app1} app2={diff.app2} />\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</Await>\n\t\t</Suspense>\n\t)\n}\n\nfunction SelectFileToDiff({\n\tname,\n\tlabel,\n\tclassName,\n\tallApps,\n\tdefaultValue,\n}: {\n\tname: string\n\tlabel: string\n\tclassName?: string\n\tallApps: Array<{ name: string; displayName: string }>\n\tdefaultValue?: string\n}) {\n\treturn (\n\t\t<Select.Root name={name} defaultValue={defaultValue}>\n\t\t\t<Select.Trigger\n\t\t\t\tclassName={clsx(\n\t\t\t\t\t'radix-placeholder:text-muted-foreground flex h-full w-full max-w-[50%] items-center justify-between px-3 text-left focus-visible:outline-none',\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\taria-label={`Select ${label} for git Diff`}\n\t\t\t>\n\t\t\t\t<span className=\"truncate\">\n\t\t\t\t\t{label}:{' '}\n\t\t\t\t\t<Select.Value\n\t\t\t\t\t\tplaceholder={`Select ${label}`}\n\t\t\t\t\t\tclassName=\"inline-block w-40 truncate\"\n\t\t\t\t\t/>\n\t\t\t\t</span>\n\t\t\t\t<Select.Icon className=\"\">\n\t\t\t\t\t<Icon name=\"TriangleDownSmall\" />\n\t\t\t\t</Select.Icon>\n\t\t\t</Select.Trigger>\n\t\t\t<Select.Portal>\n\t\t\t\t<Select.Content\n\t\t\t\t\tposition=\"popper\"\n\t\t\t\t\talign=\"start\"\n\t\t\t\t\tclassName=\"bg-popover text-popover-foreground z-20 max-h-[50vh] lg:max-h-[70vh]\"\n\t\t\t\t>\n\t\t\t\t\t<Select.ScrollUpButton className=\"flex h-5 cursor-default items-center justify-center\">\n\t\t\t\t\t\t<Icon name=\"ChevronUp\" />\n\t\t\t\t\t</Select.ScrollUpButton>\n\t\t\t\t\t<Select.Viewport className=\"p-3\">\n\t\t\t\t\t\t<Select.Group>\n\t\t\t\t\t\t\t<Select.Label className=\"px-5 pb-3 font-mono uppercase\">\n\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t</Select.Label>\n\t\t\t\t\t\t\t{allApps.map((app) => {\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<SelectItem key={app.name} value={app.name}>\n\t\t\t\t\t\t\t\t\t\t{app.displayName}\n\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</Select.Group>\n\t\t\t\t\t</Select.Viewport>\n\t\t\t\t\t<Select.ScrollDownButton className=\"flex h-5 cursor-default items-center justify-center\">\n\t\t\t\t\t\t<Icon name=\"ChevronDown\" />\n\t\t\t\t\t</Select.ScrollDownButton>\n\t\t\t\t</Select.Content>\n\t\t\t</Select.Portal>\n\t\t</Select.Root>\n\t)\n}\n\nconst SelectItem: React.FC<any> = ({\n\tref: forwardedRef,\n\tchildren,\n\tclassName,\n\t...props\n}) => {\n\treturn (\n\t\t<Select.Item\n\t\t\tclassName={clsx(\n\t\t\t\t'radix-disabled:text-muted-foreground radix-highlighted:opacity-100 radix-highlighted:outline-none radix-state-checked:opacity-100 relative flex cursor-pointer items-center rounded px-10 py-2 leading-none opacity-80 select-none',\n\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t\tref={forwardedRef}\n\t\t>\n\t\t\t<Select.ItemText>{children}</Select.ItemText>\n\t\t\t<Select.ItemIndicator className=\"absolute left-0 inline-flex w-[25px] items-center justify-center\">\n\t\t\t\t<Icon name=\"CheckSmall\" />\n\t\t\t</Select.ItemIndicator>\n\t\t</Select.Item>\n\t)\n}\n"],"names":["pre","props","jsx","mdxComponents","AccordionComponent","RevalidateApps","app1Name","app2Name","apps","useApps","app1","app","app2","useRevalidationWS","UserHasAccessDiff","userHasAccessPromise","diff","allApps","ErrorBoundary","jsxs","Suspense","SimpleTooltip","Icon","Await","userHasAccess","DiffImplementation","DeferredEpicVideo","submit","useSubmit","params","useSearchParams","paramsWithForcedRefresh","navigation","useNavigation","spinnerNavigating","useSpinDelay","hiddenInputs","key","value","Link","cn","Form","e","SelectFileToDiff","Accordion.Root","Mdx","name","label","className","defaultValue","Select.Root","Select.Trigger","clsx","Select.Value","Select.Icon","Select.Portal","Select.Content","Select.ScrollUpButton","Select.Viewport","Select.Group","Select.Label","SelectItem","Select.ScrollDownButton","forwardedRef","children","Select.Item","Select.ItemText","Select.ItemIndicator"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{d as f,h}from"./discord-XhHqPI49.js";import"./chunk-EPOLDU6W-BCLmut3y.js";import"./index-CqIc3cxq.js";import"./jsx-runtime-C5WNSv3b.js";import"./misc-W4055b-0.js";import"./user-CYXKquT7.js";import"./root-loader-BmUqzUDN.js";import"./workshop-config-BMWaKPZT.js";export{f as default,h as handle};
|
|
2
|
+
//# sourceMappingURL=discord-BVfxP4Rd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-BVfxP4Rd.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{w as d,L as s}from"./chunk-EPOLDU6W-BCLmut3y.js";import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{I as t}from"./misc-W4055b-0.js";import{c as i,a as c}from"./user-
|
|
2
|
-
//# sourceMappingURL=discord-
|
|
1
|
+
import{w as d,L as s}from"./chunk-EPOLDU6W-BCLmut3y.js";import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{I as t}from"./misc-W4055b-0.js";import{c as i,a as c}from"./user-CYXKquT7.js";import{u as a}from"./workshop-config-BMWaKPZT.js";const j={getSitemapEntries:()=>null};function l(){const{product:{host:r}}=a();return`https://${r}/discord`}function g(){const r=i(),n=c(),o=l();return r?n?"https://discord.com/channels/715220730605731931/1161045224907341972":o:"/login"}function h(){const r=i(),n=c(),o=l();return r?n?e.jsxs("div",{className:"flex items-center justify-center gap-2 text-xl underline",children:[e.jsx(s,{to:"discord://discord.com/channels/715220730605731931/1161045224907341972",children:e.jsx(t,{name:"Discord",size:"2xl"})}),e.jsx(s,{to:"https://discord.com/channels/715220730605731931/1161045224907341972",target:"_blank",rel:"noreferrer noopener",children:"Open Discord"})]}):e.jsxs("div",{className:"flex flex-wrap items-center justify-center gap-2 text-xl",children:[e.jsxs(s,{to:o,className:"flex items-center gap-2 underline",children:[e.jsx(t,{name:"Discord",size:"2xl"}),"Connect Discord"]})," ",e.jsxs("span",{children:["to get access to the exclusive"," ",e.jsx(s,{to:"/discord",className:"underline",children:"discord channel"}),"."]})]}):e.jsxs("div",{className:"flex flex-wrap items-center justify-center gap-2 text-xl",children:[e.jsxs(s,{to:"/login",className:"inline-flex items-center gap-2 underline",children:[e.jsx(t,{name:"Discord",size:"2xl"}),"Login"]})," ",e.jsxs("span",{children:["to get access to the exclusive"," ",e.jsx(s,{to:"/discord",className:"underline",children:"discord channel"}),"."]})]})}const D=d(function(){return e.jsxs("div",{className:"container flex h-full max-w-3xl flex-col items-center justify-center gap-4 p-12",children:[e.jsx(h,{}),e.jsxs("p",{children:["The"," ",e.jsx(s,{target:"_blank",rel:"noreferrer noopener",className:"underline",to:"https://kentcdodds.com/discord",children:"Epic Web Community on Discord"})," ","is a great place to hang out with other developers who are working through this workshop. You can ask questions, get help, and solidify what you're learning by helping others."]}),e.jsx("p",{children:e.jsxs("small",{className:"text-sm",children:["If you've not joined the Epic Web Community on Discord yet, you'll be required to go through a short onboarding process first. A friendly bot will explain the process when you"," ",e.jsx(s,{to:"https://kcd.im/discord",target:"_blank",rel:"noreferrer noopener",className:"underline",children:"join"}),"."]})})]})});export{h as D,D as d,j as h,g as u};
|
|
2
|
+
//# sourceMappingURL=discord-XhHqPI49.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discord-
|
|
1
|
+
{"version":3,"file":"discord-XhHqPI49.js","sources":["../../../app/routes/_app+/discord.tsx"],"sourcesContent":["import { type SEOHandle } from '@nasa-gcn/remix-seo'\nimport { Link } from 'react-router'\nimport { Icon } from '#app/components/icons.tsx'\nimport {\n\tuseOptionalDiscordMember,\n\tuseOptionalUser,\n} from '#app/components/user.tsx'\nimport { useWorkshopConfig } from '#app/components/workshop-config.tsx'\n\nexport const handle: SEOHandle = {\n\tgetSitemapEntries: () => null,\n}\n\nfunction useConnectDiscordURL() {\n\tconst {\n\t\tproduct: { host },\n\t} = useWorkshopConfig()\n\treturn `https://${host}/discord`\n}\n\nexport function useDiscordCTALink() {\n\tconst user = useOptionalUser()\n\tconst discordMember = useOptionalDiscordMember()\n\tconst connectDiscordURL = useConnectDiscordURL()\n\n\tif (!user) {\n\t\treturn '/login'\n\t}\n\tif (!discordMember) {\n\t\treturn connectDiscordURL\n\t}\n\n\treturn 'https://discord.com/channels/715220730605731931/1161045224907341972'\n}\n\nexport function DiscordCTA() {\n\tconst user = useOptionalUser()\n\tconst discordMember = useOptionalDiscordMember()\n\tconst connectDiscordURL = useConnectDiscordURL()\n\tif (!user) {\n\t\treturn (\n\t\t\t<div className=\"flex flex-wrap items-center justify-center gap-2 text-xl\">\n\t\t\t\t<Link to=\"/login\" className=\"inline-flex items-center gap-2 underline\">\n\t\t\t\t\t<Icon name=\"Discord\" size=\"2xl\" />\n\t\t\t\t\tLogin\n\t\t\t\t</Link>{' '}\n\t\t\t\t<span>\n\t\t\t\t\tto get access to the exclusive{' '}\n\t\t\t\t\t<Link to=\"/discord\" className=\"underline\">\n\t\t\t\t\t\tdiscord channel\n\t\t\t\t\t</Link>\n\t\t\t\t\t.\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t)\n\t}\n\tif (!discordMember) {\n\t\treturn (\n\t\t\t<div className=\"flex flex-wrap items-center justify-center gap-2 text-xl\">\n\t\t\t\t<Link\n\t\t\t\t\tto={connectDiscordURL}\n\t\t\t\t\tclassName=\"flex items-center gap-2 underline\"\n\t\t\t\t>\n\t\t\t\t\t<Icon name=\"Discord\" size=\"2xl\" />\n\t\t\t\t\tConnect Discord\n\t\t\t\t</Link>{' '}\n\t\t\t\t<span>\n\t\t\t\t\tto get access to the exclusive{' '}\n\t\t\t\t\t<Link to=\"/discord\" className=\"underline\">\n\t\t\t\t\t\tdiscord channel\n\t\t\t\t\t</Link>\n\t\t\t\t\t.\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t)\n\t}\n\n\treturn (\n\t\t<div className=\"flex items-center justify-center gap-2 text-xl underline\">\n\t\t\t<Link to=\"discord://discord.com/channels/715220730605731931/1161045224907341972\">\n\t\t\t\t<Icon name=\"Discord\" size=\"2xl\" />\n\t\t\t</Link>\n\t\t\t<Link\n\t\t\t\tto=\"https://discord.com/channels/715220730605731931/1161045224907341972\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noreferrer noopener\"\n\t\t\t>\n\t\t\t\tOpen Discord\n\t\t\t</Link>\n\t\t</div>\n\t)\n}\n\nexport default function DiscordRoute() {\n\treturn (\n\t\t<div className=\"container flex h-full max-w-3xl flex-col items-center justify-center gap-4 p-12\">\n\t\t\t<DiscordCTA />\n\t\t\t<p>\n\t\t\t\tThe{' '}\n\t\t\t\t<Link\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noreferrer noopener\"\n\t\t\t\t\tclassName=\"underline\"\n\t\t\t\t\tto=\"https://kentcdodds.com/discord\"\n\t\t\t\t>\n\t\t\t\t\tEpic Web Community on Discord\n\t\t\t\t</Link>{' '}\n\t\t\t\tis a great place to hang out with other developers who are working\n\t\t\t\tthrough this workshop. You can ask questions, get help, and solidify\n\t\t\t\twhat you're learning by helping others.\n\t\t\t</p>\n\t\t\t<p>\n\t\t\t\t<small className=\"text-sm\">\n\t\t\t\t\tIf you've not joined the Epic Web Community on Discord yet, you'll be\n\t\t\t\t\trequired to go through a short onboarding process first. A friendly\n\t\t\t\t\tbot will explain the process when you{' '}\n\t\t\t\t\t<Link\n\t\t\t\t\t\tto=\"https://kcd.im/discord\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noreferrer noopener\"\n\t\t\t\t\t\tclassName=\"underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\tjoin\n\t\t\t\t\t</Link>\n\t\t\t\t\t.\n\t\t\t\t</small>\n\t\t\t</p>\n\t\t</div>\n\t)\n}\n"],"names":["handle","getSitemapEntries","useConnectDiscordURL","product","host","useWorkshopConfig","useDiscordCTALink","user","useOptionalUser","discordMember","useOptionalDiscordMember","connectDiscordURL","DiscordCTA","jsxs","className","children","jsx","Link","to","Icon","name","size","target","rel","discord","_UNSAFE_withComponentProps"],"mappings":"6OASO,MAAMA,EAAoB,CAChCC,kBAAmBA,IAAM,IAC1B,EAEA,SAASC,GAAuB,CAC/B,KAAM,CACLC,QAAS,CAAEC,KAAAA,CAAK,GACbC,EAAA,EACJ,MAAO,WAAWD,CAAI,UACvB,CAEO,SAASE,GAAoB,CACnC,MAAMC,EAAOC,EAAA,EACPC,EAAgBC,EAAA,EAChBC,EAAoBT,EAAA,EAE1B,OAAKK,EAGAE,EAIE,sEAHCE,EAHA,QAOT,CAEO,SAASC,GAAa,CAC5B,MAAML,EAAOC,EAAA,EACPC,EAAgBC,EAAA,EAChBC,EAAoBT,EAAA,EAC1B,OAAKK,EAiBAE,EAsBJI,EAAAA,KAAC,MAAA,CAAIC,UAAU,2DACdC,SAAA,CAAAC,EAAAA,IAACC,EAAA,CAAKC,GAAG,wEACRH,SAAAC,EAAAA,IAACG,GAAKC,KAAK,UAAUC,KAAK,MAAM,CAAA,CACjC,EACAL,EAAAA,IAACC,EAAA,CACAC,GAAG,sEACHI,OAAO,SACPC,IAAI,sBACJR,SAAA,cAAA,CAED,CAAA,CAAA,CACD,EA/BCF,EAAAA,KAAC,MAAA,CAAIC,UAAU,2DACdC,SAAA,CAAAF,EAAAA,KAACI,EAAA,CACAC,GAAIP,EACJG,UAAU,oCAEVC,SAAA,CAAAC,EAAAA,IAACG,EAAA,CAAKC,KAAK,UAAUC,KAAK,MAAM,EAAE,iBAAA,EAEnC,EAAQ,WACP,OAAA,CAAKN,SAAA,CAAA,iCAC0B,UAC9BE,EAAA,CAAKC,GAAG,WAAWJ,UAAU,YAAYC,SAAA,kBAE1C,EAAO,GAAA,CAAA,CAER,CAAA,CAAA,CACD,EAhCAF,EAAAA,KAAC,MAAA,CAAIC,UAAU,2DACdC,SAAA,CAAAF,EAAAA,KAACI,EAAA,CAAKC,GAAG,SAASJ,UAAU,2CAC3BC,SAAA,CAAAC,EAAAA,IAACG,EAAA,CAAKC,KAAK,UAAUC,KAAK,MAAM,EAAE,OAAA,EAEnC,EAAQ,WACP,OAAA,CAAKN,SAAA,CAAA,iCAC0B,UAC9BE,EAAA,CAAKC,GAAG,WAAWJ,UAAU,YAAYC,SAAA,kBAE1C,EAAO,GAAA,CAAA,CAER,CAAA,CAAA,CACD,CAsCH,CAEA,MAAAS,EAAAC,EAAA,UAAuC,CACtC,OACCZ,EAAAA,KAAC,MAAA,CAAIC,UAAU,kFACdC,SAAA,CAAAC,EAAAA,IAACJ,EAAA,CAAA,CAAW,SACX,IAAA,CAAEG,SAAA,CAAA,MACE,IACJC,EAAAA,IAACC,EAAA,CACAK,OAAO,SACPC,IAAI,sBACJT,UAAU,YACVI,GAAG,iCACHH,SAAA,+BAAA,CAED,EAAQ,IAAI,iLAAA,CAAA,CAIb,EACAC,EAAAA,IAAC,IAAA,CACAD,SAAAF,EAAAA,KAAC,QAAA,CAAMC,UAAU,UAAUC,SAAA,CAAA,kLAGY,IACtCC,EAAAA,IAACC,EAAA,CACAC,GAAG,yBACHI,OAAO,SACPC,IAAI,sBACJT,UAAU,YACVC,SAAA,OAED,EAAO,GAAA,EAER,CAAA,CACD,CAAA,CAAA,CACD,CAEF,CAAA"}
|