@epic-web/workshop-app 6.88.1 → 6.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/build/client/assets/{_exerciseNumber-BkoIHU4s.js → _exerciseNumber-BZvzAp9f.js} +2 -2
  2. package/build/client/assets/{_exerciseNumber-BkoIHU4s.js.map → _exerciseNumber-BZvzAp9f.js.map} +1 -1
  3. package/build/client/assets/{_exerciseNumber_.finished-BRspQYAo.js → _exerciseNumber_.finished-D5eMZ_ok.js} +2 -2
  4. package/build/client/assets/{_exerciseNumber_.finished-BRspQYAo.js.map → _exerciseNumber_.finished-D5eMZ_ok.js.map} +1 -1
  5. package/build/client/assets/{_extra-qH2jDBa4.js → _extra-Bd0-gThk.js} +2 -2
  6. package/build/client/assets/{_extra-qH2jDBa4.js.map → _extra-Bd0-gThk.js.map} +1 -1
  7. package/build/client/assets/{_layout-Bg1RFS9D.js → _layout-CbP-ZGKA.js} +2 -2
  8. package/build/client/assets/{_layout-Bg1RFS9D.js.map → _layout-CbP-ZGKA.js.map} +1 -1
  9. package/build/client/assets/{app-BH96aYJx.js → app-BTlBQWy_.js} +2 -2
  10. package/build/client/assets/{app-BH96aYJx.js.map → app-BTlBQWy_.js.map} +1 -1
  11. package/build/client/assets/{cache-5tXauWqp.js → cache-Cjjb4Th1.js} +2 -2
  12. package/build/client/assets/{cache-5tXauWqp.js.map → cache-Cjjb4Th1.js.map} +1 -1
  13. package/build/client/assets/{db-CPB2ZdAy.js → db-n83JPsm9.js} +2 -2
  14. package/build/client/assets/{db-CPB2ZdAy.js.map → db-n83JPsm9.js.map} +1 -1
  15. package/build/client/assets/{diff-BncP0Faq.js → diff-C4LoXlDb.js} +3 -3
  16. package/build/client/assets/diff-C4LoXlDb.js.map +1 -0
  17. package/build/client/assets/{diff-IVQnB3Pk.js → diff-Fut2J8f5.js} +2 -2
  18. package/build/client/assets/{diff-IVQnB3Pk.js.map → diff-Fut2J8f5.js.map} +1 -1
  19. package/build/client/assets/{finished-CYdLPdvJ.js → finished-CsRM8483.js} +2 -2
  20. package/build/client/assets/{finished-CYdLPdvJ.js.map → finished-CsRM8483.js.map} +1 -1
  21. package/build/client/assets/{index-n4CWCwSA.js → index-BnHkUMN_.js} +2 -2
  22. package/build/client/assets/{index-n4CWCwSA.js.map → index-BnHkUMN_.js.map} +1 -1
  23. package/build/client/assets/{index-CKhNevM7.js → index-Bo0DKzj5.js} +2 -2
  24. package/build/client/assets/{index-CKhNevM7.js.map → index-Bo0DKzj5.js.map} +1 -1
  25. package/build/client/assets/{index-Cb8U85TK.js → index-m9K7ACZe.js} +2 -2
  26. package/build/client/assets/{index-Cb8U85TK.js.map → index-m9K7ACZe.js.map} +1 -1
  27. package/build/client/assets/launch-editor-C-8ah8qM.js +2 -0
  28. package/build/client/assets/launch-editor-C-8ah8qM.js.map +1 -0
  29. package/build/client/assets/{manifest-d6d8ad00.js → manifest-72875289.js} +1 -1
  30. package/build/client/assets/{mdx-Bvb9XNyL.js → mdx-C3Jl4O5D.js} +2 -2
  31. package/build/client/assets/{mdx-Bvb9XNyL.js.map → mdx-C3Jl4O5D.js.map} +1 -1
  32. package/build/client/assets/{playground-CEUo0DCy.js → playground-BV2y1ifv.js} +2 -2
  33. package/build/client/assets/{playground-CEUo0DCy.js.map → playground-BV2y1ifv.js.map} +1 -1
  34. package/build/client/assets/{preview-BS9wGiJ3.js → preview-Dav9KrHN.js} +2 -2
  35. package/build/client/assets/{preview-BS9wGiJ3.js.map → preview-Dav9KrHN.js.map} +1 -1
  36. package/build/server/index.js +36 -4
  37. package/build/server/index.js.map +1 -1
  38. package/package.json +3 -3
  39. package/build/client/assets/diff-BncP0Faq.js.map +0 -1
  40. package/build/client/assets/launch-editor-C8OwTN5L.js +0 -2
  41. package/build/client/assets/launch-editor-C8OwTN5L.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"_layout-Bg1RFS9D.js","sources":["../../../app/routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/__shared/step-mdx.tsx","../../../app/routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/__shared/touched-files.tsx","../../../app/routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout.tsx"],"sourcesContent":["import * as React from 'react'\nimport { type PropsWithChildren } from 'react'\nimport {\n\tLink,\n\tuseLoaderData,\n\tuseSearchParams,\n\ttype LinkProps,\n} from 'react-router'\nimport iconsSvg from '#app/assets/icons.svg'\nimport { EpicVideoInfoProvider } from '#app/components/epic-video.tsx'\nimport { Icon } from '#app/components/icons.tsx'\nimport { type InBrowserBrowserRef } from '#app/components/in-browser-browser.tsx'\nimport { SimpleTooltip } from '#app/components/ui/tooltip.tsx'\nimport { LaunchEditor } from '#app/routes/launch-editor.tsx'\nimport { Mdx } from '#app/utils/mdx.tsx'\nimport { cn, getBaseUrl } from '#app/utils/misc.tsx'\nimport { useRequestInfo } from '#app/utils/root-loader.ts'\nimport { type loader } from '../_layout.tsx'\n\ntype StepContextType = {\n\tinBrowserBrowserRef: React.RefObject<InBrowserBrowserRef | null>\n}\nconst StepContext = React.createContext<StepContextType | null>(null)\n\nfunction useStepContext() {\n\tconst context = React.useContext(StepContext)\n\tif (!context) {\n\t\tthrow new Error('useStepContext must be used within a StepContextProvider')\n\t}\n\treturn context\n}\n\nfunction StepContextProvider({\n\tchildren,\n\tinBrowserBrowserRef,\n}: {\n\tchildren: React.ReactNode\n\tinBrowserBrowserRef: React.RefObject<InBrowserBrowserRef | null>\n}) {\n\treturn <StepContext value={{ inBrowserBrowserRef }}>{children}</StepContext>\n}\n\nconst stepMdxComponents = {\n\tDiffLink,\n\tPrevDiffLink,\n\tNextDiffLink,\n\tInlineFile,\n\tLinkToApp,\n}\n\nexport function StepMdx({\n\tinBrowserBrowserRef,\n}: {\n\tinBrowserBrowserRef: React.RefObject<InBrowserBrowserRef | null>\n}) {\n\tconst data = useLoaderData<typeof loader>()\n\tif (!data.exerciseStepApp.instructionsCode) return null\n\treturn (\n\t\t<StepContextProvider inBrowserBrowserRef={inBrowserBrowserRef}>\n\t\t\t<EpicVideoInfoProvider epicVideoInfosPromise={data.epicVideoInfosPromise}>\n\t\t\t\t<div className=\"prose dark:prose-invert sm:prose-lg\">\n\t\t\t\t\t<Mdx\n\t\t\t\t\t\tcode={data.exerciseStepApp.instructionsCode}\n\t\t\t\t\t\tcomponents={stepMdxComponents}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</EpicVideoInfoProvider>\n\t\t</StepContextProvider>\n\t)\n}\n\nfunction withParam(\n\tsearchParams: URLSearchParams,\n\tkey: string,\n\tvalue: string | null,\n) {\n\tconst newSearchParams = new URLSearchParams(searchParams)\n\tif (value === null) {\n\t\tnewSearchParams.delete(key)\n\t} else {\n\t\tnewSearchParams.set(key, value)\n\t}\n\treturn newSearchParams\n}\n\nfunction NextDiffLink({\n\tapp = 0,\n\tfullPage = false,\n\tchildren,\n}: {\n\tapp: number\n\tfullPage?: boolean\n\tchildren?: React.ReactNode\n}) {\n\treturn (\n\t\t<DiffLink app1={app} app2={app + 1} fullPage={fullPage}>\n\t\t\t{children}\n\t\t</DiffLink>\n\t)\n}\n\nfunction PrevDiffLink({\n\tapp = -1,\n\tfullPage = false,\n\tchildren,\n}: {\n\tapp: number\n\tfullPage?: boolean\n\tchildren?: React.ReactNode\n}) {\n\treturn (\n\t\t<DiffLink app1={app} app2={app + 1} fullPage={fullPage}>\n\t\t\t{children}\n\t\t</DiffLink>\n\t)\n}\n\nfunction DiffLink({\n\tapp1 = 0,\n\tapp2 = 1,\n\tchildren,\n\tfullPage = false,\n\tto,\n}: {\n\tapp1?: string | number | null\n\tapp2?: string | number | null\n\tto?: string\n\tfullPage?: boolean\n\tchildren?: React.ReactNode\n}) {\n\tconst data = useLoaderData<typeof loader>()\n\tif (!to && !app1 && !app2) {\n\t\treturn (\n\t\t\t<callout-danger className=\"notification\">\n\t\t\t\t<div className=\"title\">DiffLink Error: invalid input</div>\n\t\t\t</callout-danger>\n\t\t)\n\t}\n\n\tfunction getAppName(input: typeof app1) {\n\t\tif (typeof input === 'number') {\n\t\t\tconst stepIndex = data.exerciseIndex + input\n\t\t\treturn data.allApps[stepIndex]?.name\n\t\t}\n\t\tif (!input) return null\n\t\tfor (const { name, stepName } of data.allApps) {\n\t\t\tif (input === name || input === stepName) {\n\t\t\t\treturn name\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\tif (to) {\n\t\tconst params = new URLSearchParams(to)\n\t\tapp1 = params.get('app1')\n\t\tapp2 = params.get('app2')\n\t}\n\tconst app1Name = getAppName(app1)\n\tconst app2Name = getAppName(app2)\n\tif (!app1Name || !app2Name) {\n\t\treturn (\n\t\t\t<callout-danger className=\"notification\">\n\t\t\t\t<div className=\"title\">DiffLink Error: invalid input</div>\n\t\t\t\t{!app1Name && <div>app1: \"{app1}\" is not a valid app name</div>}\n\t\t\t\t{!app2Name && <div>app2: \"{app2}\" is not a valid app name</div>}\n\t\t\t</callout-danger>\n\t\t)\n\t}\n\n\tif (!to) {\n\t\tto = `app1=${app1Name}&app2=${app2Name}`\n\t}\n\tconst pathToDiff = fullPage\n\t\t? `/diff?${to}`\n\t\t: `?${decodeURIComponent(\n\t\t\t\twithParam(new URLSearchParams(), 'preview', `diff&${to}`).toString(),\n\t\t\t)}`\n\n\tif (!children) {\n\t\tchildren = (\n\t\t\t<span>\n\t\t\t\tGo to Diff {fullPage ? '' : 'Preview'} from: <code>{app1Name}</code> to:{' '}\n\t\t\t\t<code>{app2Name}</code>\n\t\t\t</span>\n\t\t)\n\t}\n\n\treturn <Link to={pathToDiff}>{children}</Link>\n}\n\nfunction InlineFile({\n\tfile,\n\ttype = 'playground',\n\tchildren = <code>{file}</code>,\n\t...props\n}: Omit<PropsWithChildren<typeof LaunchEditor>, 'appName'> & {\n\tfile: string\n\ttype?: 'playground' | 'solution' | 'problem'\n}) {\n\tconst data = useLoaderData<typeof loader>()\n\tconst app = data[type] || data[data.type]\n\tconst expectedPlaygroundAppName = data.problem?.name ?? data[data.type]?.name\n\tconst isPlaygroundReady =\n\t\ttype !== 'playground' ||\n\t\t(Boolean(expectedPlaygroundAppName) &&\n\t\t\tdata.playground?.appName === expectedPlaygroundAppName)\n\tconst disabledReason =\n\t\ttype === 'playground' && !isPlaygroundReady\n\t\t\t? 'Set the playground to this step before opening a file.'\n\t\t\t: null\n\n\tconst info = (\n\t\t<div className=\"launch-editor-button-wrapper flex underline underline-offset-4\">\n\t\t\t{children}{' '}\n\t\t\t<svg height={24} width={24}>\n\t\t\t\t<use href={`${iconsSvg}#Keyboard`} />\n\t\t\t</svg>\n\t\t</div>\n\t)\n\n\tif (disabledReason) {\n\t\treturn (\n\t\t\t<SimpleTooltip content={disabledReason}>\n\t\t\t\t<div className=\"inline-block grow cursor-not-allowed\">{info}</div>\n\t\t\t</SimpleTooltip>\n\t\t)\n\t}\n\n\treturn ENV.EPICSHOP_DEPLOYED && app ? (\n\t\t<div className=\"inline-block grow\">\n\t\t\t<LaunchEditor appFile={file} appName={app.name} {...props}>\n\t\t\t\t{info}\n\t\t\t</LaunchEditor>\n\t\t</div>\n\t) : app ? (\n\t\t<div className=\"inline-block grow\">\n\t\t\t<LaunchEditor appFile={file} appName={app.name} {...props}>\n\t\t\t\t{info}\n\t\t\t</LaunchEditor>\n\t\t</div>\n\t) : (\n\t\t<>{children}</>\n\t)\n}\n\nfunction getPreviewType(\n\tpreview: string | null,\n): 'playground' | 'problem' | 'solution' {\n\tif (preview === 'problem') return 'problem'\n\tif (preview === 'solution') return 'solution'\n\treturn 'playground'\n}\n\nfunction LinkToApp({\n\tto: appTo,\n\tchildren = <code>{appTo.toString()}</code>,\n\t...props\n}: LinkProps) {\n\tconst [searchParams] = useSearchParams()\n\tconst to = `?${withParam(\n\t\tsearchParams,\n\t\t'pathname',\n\t\tappTo.toString(),\n\t).toString()}`\n\tconst data = useLoaderData<typeof loader>()\n\tconst type = getPreviewType(searchParams.get('preview'))\n\tconst requestInfo = useRequestInfo()\n\tconst app = data[type]\n\tconst previewAppUrl =\n\t\tapp?.dev.type === 'script'\n\t\t\t? getBaseUrl({\n\t\t\t\t\tdomain: requestInfo.domain,\n\t\t\t\t\tport: app.dev.portNumber,\n\t\t\t\t})\n\t\t\t: data.playground?.dev.type === 'browser' ||\n\t\t\t\t data.playground?.dev.type === 'export'\n\t\t\t\t? data.playground.dev.pathname\n\t\t\t\t: null\n\tconst { inBrowserBrowserRef } = useStepContext()\n\tconst href = previewAppUrl\n\t\t? previewAppUrl.slice(0, -1) + appTo.toString()\n\t\t: null\n\treturn (\n\t\t<div className=\"inline-flex items-center justify-between gap-1\">\n\t\t\t<Link\n\t\t\t\tto={to}\n\t\t\t\t{...props}\n\t\t\t\tclassName={cn(props.className, {\n\t\t\t\t\t'cursor-not-allowed': ENV.EPICSHOP_DEPLOYED,\n\t\t\t\t})}\n\t\t\t\ttitle={\n\t\t\t\t\tENV.EPICSHOP_DEPLOYED\n\t\t\t\t\t\t? 'Cannot link to app in deployed version'\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tonClick={(event) => {\n\t\t\t\t\tif (ENV.EPICSHOP_DEPLOYED) event.preventDefault()\n\n\t\t\t\t\tprops.onClick?.(event)\n\t\t\t\t\tinBrowserBrowserRef.current?.handleExtrnalNavigation(appTo.toString())\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</Link>\n\t\t\t{href ? (\n\t\t\t\t<SimpleTooltip content=\"Open in new tab\">\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={href}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noreferrer\"\n\t\t\t\t\t\tclassName={cn('flex aspect-square items-center justify-center', {\n\t\t\t\t\t\t\t'cursor-not-allowed': ENV.EPICSHOP_DEPLOYED,\n\t\t\t\t\t\t})}\n\t\t\t\t\t\ttitle={\n\t\t\t\t\t\t\tENV.EPICSHOP_DEPLOYED\n\t\t\t\t\t\t\t\t? 'Cannot link to app in deployed version'\n\t\t\t\t\t\t\t\t: 'Open in new tab'\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\tif (ENV.EPICSHOP_DEPLOYED) event.preventDefault()\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon name=\"ExternalLink\" />\n\t\t\t\t\t</a>\n\t\t\t\t</SimpleTooltip>\n\t\t\t) : null}\n\t\t</div>\n\t)\n}\n","import * as Popover from '@radix-ui/react-popover'\nimport * as React from 'react'\nimport { Await, Link, useLoaderData } from 'react-router'\nimport { Icon } from '#app/components/icons.tsx'\nimport {\n\tOnboardingBadge,\n\tuseOnboardingIndicator,\n} from '#app/components/onboarding-indicator.tsx'\nimport { SimpleTooltip } from '#app/components/ui/tooltip.tsx'\nimport { LaunchEditor } from '#app/routes/launch-editor.tsx'\nimport { SetAppToPlayground } from '#app/routes/set-playground.tsx'\nimport { type Route as LayoutRoute } from '../+types/_layout.tsx'\n\nfunction TouchedFiles({\n\tdiffFilesPromise,\n}: {\n\tdiffFilesPromise: LayoutRoute.ComponentProps['loaderData']['diffFiles']\n}) {\n\tconst data = useLoaderData<LayoutRoute.ComponentProps['loaderData']>()\n\tconst [showFilesBadge, dismissFilesBadge] =\n\t\tuseOnboardingIndicator('files-popover')\n\n\tconst [open, setOpen] = React.useState(false)\n\tconst contentRef = React.useRef<HTMLDivElement>(null)\n\n\tfunction handleOpenChange(isOpen: boolean) {\n\t\tsetOpen(isOpen)\n\t\t// Mark as complete when opening the popover for the first time\n\t\tif (isOpen) {\n\t\t\tdismissFilesBadge()\n\t\t}\n\t}\n\n\tfunction handleLaunchUpdate() {\n\t\tsetOpen(false)\n\t}\n\n\tconst appName = data.playground?.appName\n\n\treturn (\n\t\t<>\n\t\t\t<Popover.Root open={open} onOpenChange={handleOpenChange}>\n\t\t\t\t<Popover.Trigger asChild>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"relative flex h-full items-center gap-1 border-r px-6 py-3 font-mono text-sm uppercase\"\n\t\t\t\t\t\taria-label=\"Relevant Files\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon name=\"Files\" />\n\t\t\t\t\t\t<span className=\"hidden @min-[640px]:inline\">Files</span>\n\t\t\t\t\t\t{showFilesBadge ? (\n\t\t\t\t\t\t\t<OnboardingBadge tooltip=\"Click to see which files to edit!\" />\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</button>\n\t\t\t\t</Popover.Trigger>\n\t\t\t\t<Popover.Portal>\n\t\t\t\t\t<Popover.Content\n\t\t\t\t\t\tref={contentRef}\n\t\t\t\t\t\tclassName=\"slideRightContent lg:slideUpContent invert-theme bg-background text-foreground z-10 rounded px-9 py-8 select-none\"\n\t\t\t\t\t\talign=\"start\"\n\t\t\t\t\t\tsideOffset={5}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"launch-editor-wrapper\">\n\t\t\t\t\t\t\t<strong className=\"inline-block px-2 pb-2 font-semibold uppercase\">\n\t\t\t\t\t\t\t\tRelevant Files\n\t\t\t\t\t\t\t</strong>\n\t\t\t\t\t\t\t<p className=\"text-muted-foreground mb-4 max-w-2xs px-2 text-sm\">\n\t\t\t\t\t\t\t\tThese are the files you'll need to modify for this exercise.\n\t\t\t\t\t\t\t\tClick any file to open it directly in your editor at the right\n\t\t\t\t\t\t\t\tlocation.{' '}\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\tto=\"/guide#file-links\"\n\t\t\t\t\t\t\t\t\tclassName=\"text-highlight underline\"\n\t\t\t\t\t\t\t\t\tonClick={() => setOpen(false)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tLearn more →\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t{data.problem &&\n\t\t\t\t\t\t\tdata.playground?.appName !== data.problem.name ? (\n\t\t\t\t\t\t\t\t<div className=\"mb-2 rounded p-1 font-mono font-medium\">\n\t\t\t\t\t\t\t\t\t<SetAppToPlayground appName={data.problem.name} />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t<div id=\"files\">\n\t\t\t\t\t\t\t\t<React.Suspense\n\t\t\t\t\t\t\t\t\tfallback={\n\t\t\t\t\t\t\t\t\t\t<SimpleTooltip content=\"Loading diff\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex justify-center\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Refresh\" className=\"h-8 w-8 animate-spin\" />\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</SimpleTooltip>\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\t\t<Await\n\t\t\t\t\t\t\t\t\t\tresolve={diffFilesPromise}\n\t\t\t\t\t\t\t\t\t\terrorElement={\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-foreground-destructive\">\n\t\t\t\t\t\t\t\t\t\t\t\tSomething went wrong.\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{(diffFiles) => {\n\t\t\t\t\t\t\t\t\t\t\tif (!diffFiles) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-foreground-destructive\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tUnable to determine diff\n\t\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\t)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tif (typeof diffFiles === 'string') {\n\t\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-foreground-destructive\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{diffFiles}\n\t\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\t)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tif (!diffFiles.length) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn <p>No files changed</p>\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\tconst props =\n\t\t\t\t\t\t\t\t\t\t\t\tappName || ENV.EPICSHOP_GITHUB_ROOT\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\t: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"You must 'Set to Playground' before opening a file\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName: 'not-allowed',\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\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t<ul {...props}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{diffFiles.length > 1 && !ENV.EPICSHOP_DEPLOYED ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"border-opacity-50 mb-2 border-b border-b-gray-50 pb-2 font-sans\">\n\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\tappFile={diffFiles.map(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(file) => `${file.path},${file.line},1`,\n\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\tappName=\"playground\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonUpdate={handleLaunchUpdate}\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\t<p>Open All Files</p>\n\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</div>\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\t{diffFiles.map((file) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<li key={file.path} data-state={file.status}>\n\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\tappFile={`${file.path},${file.line},1`}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tappName={\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tENV.EPICSHOP_DEPLOYED\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? (data.problem?.name ?? 'playground')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: 'playground'\n\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\tonUpdate={handleLaunchUpdate}\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\t<code>{file.path}</code>\n\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</li>\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</ul>\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t</Await>\n\t\t\t\t\t\t\t\t</React.Suspense>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Popover.Content>\n\t\t\t\t</Popover.Portal>\n\t\t\t</Popover.Root>\n\t\t</>\n\t)\n}\n\nexport default TouchedFiles\n","import { ElementScrollRestoration } from '@epic-web/restore-scroll'\nimport {\n\tgetAppDisplayName,\n\tgetAppPageRoute,\n\tgetApps,\n\tgetExerciseApp,\n\tgetNextExerciseApp,\n\tgetPrevExerciseApp,\n\tisExerciseStepApp,\n\tisPlaygroundApp,\n\trequireExercise,\n\trequireExerciseApp,\n\ttype App,\n\ttype ExerciseStepApp,\n} from '@epic-web/workshop-utils/apps.server'\nimport { getWorkshopConfig } from '@epic-web/workshop-utils/config.server'\nimport { getDiffFiles } from '@epic-web/workshop-utils/diff.server'\nimport { getEpicVideoInfos } from '@epic-web/workshop-utils/epic-api.server'\nimport {\n\tcombineServerTimings,\n\tgetServerTimeHeader,\n\tmakeTimings,\n} from '@epic-web/workshop-utils/timing.server'\nimport slugify from '@sindresorhus/slugify'\nimport { useRef, useState } from 'react'\nimport {\n\tLink,\n\tOutlet,\n\tdata,\n\tredirect,\n\ttype HeadersFunction,\n} from 'react-router'\nimport { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'\nimport { type InBrowserBrowserRef } from '#app/components/in-browser-browser.tsx'\nimport { NavChevrons } from '#app/components/nav-chevrons.tsx'\nimport { useRevalidationWS } from '#app/components/revalidation-ws.tsx'\nimport { EditFileOnGitHub } from '#app/routes/launch-editor.tsx'\nimport { ProgressToggle } from '#app/routes/progress.tsx'\nimport { SetAppToPlayground } from '#app/routes/set-playground.tsx'\nimport { getExercisePath } from '#app/utils/misc.tsx'\nimport { getRootMatchLoaderData, useApps } from '#app/utils/root-loader.ts'\nimport { getSeoMetaTags } from '#app/utils/seo.ts'\nimport {\n\tgetSplitPercentFromRequest,\n\tsetSplitPercentCookie,\n\tstartSplitDrag,\n} from '#app/utils/split-layout.ts'\nimport { getStep404Data } from '../__shared/error-boundary.server.ts'\nimport { Exercise404ErrorBoundary } from '../__shared/error-boundary.tsx'\nimport { type Route } from './+types/_layout.tsx'\nimport { StepMdx } from './__shared/step-mdx.tsx'\nimport TouchedFiles from './__shared/touched-files.tsx'\n\nfunction pageTitle(\n\tdata: Awaited<Route.ComponentProps['loaderData']> | undefined,\n\tworkshopTitle?: string,\n) {\n\tconst exerciseNumber =\n\t\tdata?.exerciseStepApp.exerciseNumber.toString().padStart(2, '0') ?? '00'\n\tconst stepNumber =\n\t\tdata?.exerciseStepApp.stepNumber.toString().padStart(2, '0') ?? '00'\n\tconst emoji = (\n\t\t{\n\t\t\tproblem: '💪',\n\t\t\tsolution: '🏁',\n\t\t} as const\n\t)[data?.type ?? 'problem']\n\tconst title = data?.[data.type]?.title ?? 'N/A'\n\treturn {\n\t\temoji,\n\t\tstepNumber,\n\t\ttitle,\n\t\texerciseNumber,\n\t\texerciseTitle: data?.exerciseTitle ?? 'Unknown exercise',\n\t\tworkshopTitle,\n\t\ttype: data?.type ?? 'problem',\n\t}\n}\n\nexport const meta: Route.MetaFunction = ({ loaderData, matches, params }) => {\n\tconst rootData = getRootMatchLoaderData(matches)\n\tif (!loaderData || !rootData) return [{ title: '🦉 | Error' }]\n\tconst { emoji, stepNumber, title, exerciseNumber, exerciseTitle } =\n\t\tpageTitle(loaderData)\n\n\treturn getSeoMetaTags({\n\t\ttitle: `${emoji} | ${stepNumber}. ${title} | ${exerciseNumber}. ${exerciseTitle} | ${rootData.workshopTitle}`,\n\t\tdescription: `${params.type} step for exercise ${exerciseNumber}. ${exerciseTitle}`,\n\t\togTitle: title,\n\t\togDescription: `${exerciseTitle} step ${Number(stepNumber)} ${params.type}`,\n\t\tinstructor: rootData.instructor,\n\t\trequestInfo: rootData.requestInfo,\n\t})\n}\n\nexport async function loader({ request, params }: Route.LoaderArgs) {\n\tconst timings = makeTimings('exerciseStepTypeLayoutLoader')\n\tconst url = new URL(request.url)\n\tconst { type } = params\n\tconst { title: workshopTitle } = getWorkshopConfig()\n\n\tconst cacheOptions = { request, timings }\n\n\tconst [allAppsFull, problemApp, solutionApp] = await Promise.all([\n\t\tgetApps(cacheOptions),\n\t\tgetExerciseApp({ ...params, type: 'problem' }, cacheOptions),\n\t\tgetExerciseApp({ ...params, type: 'solution' }, cacheOptions),\n\t])\n\n\tconst reqUrl = new URL(request.url)\n\tconst pathnameParam = reqUrl.searchParams.get('pathname')\n\tif (pathnameParam === '' || pathnameParam === '/') {\n\t\treqUrl.searchParams.delete('pathname')\n\t\tthrow redirect(reqUrl.toString())\n\t}\n\n\tif (\n\t\t(type === 'problem' && !problemApp) ||\n\t\t(type === 'solution' && !solutionApp)\n\t) {\n\t\tconst errorData = await getStep404Data({\n\t\t\texerciseNumber: params.exerciseNumber,\n\t\t})\n\t\tthrow Response.json(errorData, { status: 404 })\n\t}\n\n\tconst exerciseStepApp = await requireExerciseApp(params, cacheOptions)\n\n\tconst playgroundApp = allAppsFull.find(isPlaygroundApp)\n\n\tfunction getStepId(a: ExerciseStepApp) {\n\t\treturn (\n\t\t\ta.exerciseNumber * 1000 +\n\t\t\ta.stepNumber * 10 +\n\t\t\t(a.type === 'problem' ? 0 : 1)\n\t\t)\n\t}\n\n\tfunction getStepNameAndId(a: App) {\n\t\tif (isExerciseStepApp(a)) {\n\t\t\tconst exerciseNumberStr = String(a.exerciseNumber).padStart(2, '0')\n\t\t\tconst stepNumberStr = String(a.stepNumber).padStart(2, '0')\n\n\t\t\treturn {\n\t\t\t\tstepName: `${exerciseNumberStr}/${stepNumberStr}.${a.type}`,\n\t\t\t\tstepId: getStepId(a),\n\t\t\t}\n\t\t}\n\t\treturn { stepName: '', stepId: -1 }\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\t...getStepNameAndId(a),\n\t\t}))\n\n\tallApps.sort((a, b) => {\n\t\t// order them by their stepId\n\t\tif (a.stepId > 0 && b.stepId > 0) return a.stepId - b.stepId\n\n\t\t// non-step apps should come after step apps\n\t\tif (a.stepId > 0) return -1\n\t\tif (b.stepId > 0) return 1\n\n\t\treturn 0\n\t})\n\tconst exerciseId = getStepId(exerciseStepApp)\n\tconst exerciseIndex = allApps.findIndex((step) => step.stepId === exerciseId)\n\n\t// These depend on exerciseStepApp\n\tconst [exercise, nextApp, prevApp] = await Promise.all([\n\t\trequireExercise(exerciseStepApp.exerciseNumber, cacheOptions),\n\t\tgetNextExerciseApp(exerciseStepApp, cacheOptions),\n\t\tgetPrevExerciseApp(exerciseStepApp, cacheOptions),\n\t])\n\n\tconst exerciseApps = allAppsFull\n\t\t.filter(isExerciseStepApp)\n\t\t.filter((app) => app.exerciseNumber === exerciseStepApp.exerciseNumber)\n\tconst isLastStep =\n\t\texerciseApps[exerciseApps.length - 1]?.name === exerciseStepApp.name\n\tconst isFirstStep = exerciseApps[0]?.name === exerciseStepApp.name\n\n\tconst articleId = `workshop-${slugify(workshopTitle)}-${\n\t\texercise.exerciseNumber\n\t}-${exerciseStepApp.stepNumber}-${exerciseStepApp.type}`\n\n\tconst subroute = url.pathname.split(\n\t\t`/exercise/${params.exerciseNumber}/${params.stepNumber}/${params.type}/`,\n\t)[1]\n\n\t// read persisted split percentage from cookie (10-90, default 50)\n\tconst splitPercent = getSplitPercentFromRequest(request, 50)\n\n\treturn data(\n\t\t{\n\t\t\tarticleId,\n\t\t\ttype: params.type as 'problem' | 'solution',\n\t\t\texerciseStepApp,\n\t\t\texerciseTitle: exercise.title,\n\t\t\tepicVideoInfosPromise: getEpicVideoInfos(exerciseStepApp.epicVideoEmbeds),\n\t\t\texerciseIndex,\n\t\t\tallApps,\n\t\t\tsplitPercent,\n\t\t\tprevStepLink: isFirstStep\n\t\t\t\t? {\n\t\t\t\t\t\tto: `/exercise/${exerciseStepApp.exerciseNumber\n\t\t\t\t\t\t\t.toString()\n\t\t\t\t\t\t\t.padStart(2, '0')}`,\n\t\t\t\t\t}\n\t\t\t\t: prevApp\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tto: getAppPageRoute(prevApp, {\n\t\t\t\t\t\t\t\tsubroute,\n\t\t\t\t\t\t\t\tsearchParams: url.searchParams,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t: null,\n\t\t\tnextStepLink: isLastStep\n\t\t\t\t? {\n\t\t\t\t\t\tto: `/exercise/${exerciseStepApp.exerciseNumber\n\t\t\t\t\t\t\t.toString()\n\t\t\t\t\t\t\t.padStart(2, '0')}/finished`,\n\t\t\t\t\t}\n\t\t\t\t: nextApp\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tto: getAppPageRoute(nextApp, {\n\t\t\t\t\t\t\t\tsubroute,\n\t\t\t\t\t\t\t\tsearchParams: url.searchParams,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t: null,\n\t\t\tplayground: playgroundApp\n\t\t\t\t? ({\n\t\t\t\t\t\ttype: 'playground',\n\t\t\t\t\t\tappName: playgroundApp.appName,\n\t\t\t\t\t\tname: playgroundApp.name,\n\t\t\t\t\t\tfullPath: playgroundApp.fullPath,\n\t\t\t\t\t\tdev: playgroundApp.dev,\n\t\t\t\t\t\tisUpToDate: playgroundApp.isUpToDate,\n\t\t\t\t\t} as const)\n\t\t\t\t: null,\n\t\t\tproblem: problemApp\n\t\t\t\t? ({\n\t\t\t\t\t\ttype: 'problem',\n\t\t\t\t\t\ttitle: problemApp.title,\n\t\t\t\t\t\tname: problemApp.name,\n\t\t\t\t\t\tfullPath: problemApp.fullPath,\n\t\t\t\t\t\tdev: problemApp.dev,\n\t\t\t\t\t} as const)\n\t\t\t\t: null,\n\t\t\tsolution: solutionApp\n\t\t\t\t? ({\n\t\t\t\t\t\ttype: 'solution',\n\t\t\t\t\t\ttitle: solutionApp.title,\n\t\t\t\t\t\tname: solutionApp.name,\n\t\t\t\t\t\tfullPath: solutionApp.fullPath,\n\t\t\t\t\t\tdev: solutionApp.dev,\n\t\t\t\t\t} as const)\n\t\t\t\t: null,\n\t\t\tdiffFiles:\n\t\t\t\tproblemApp && solutionApp\n\t\t\t\t\t? getDiffFiles(problemApp, solutionApp, {\n\t\t\t\t\t\t\t...cacheOptions,\n\t\t\t\t\t\t\tforceFresh: url.searchParams.get('forceFresh') === 'diff',\n\t\t\t\t\t\t}).catch((e) => {\n\t\t\t\t\t\t\tconsole.error(e)\n\t\t\t\t\t\t\treturn 'There was a problem generating the diff (check the terminal output)'\n\t\t\t\t\t\t})\n\t\t\t\t\t: 'No diff available',\n\t\t} as const,\n\t\t{\n\t\t\theaders: {\n\t\t\t\t'Server-Timing': getServerTimeHeader(timings),\n\t\t\t},\n\t\t},\n\t)\n}\n\nexport const headers: HeadersFunction = ({ loaderHeaders, parentHeaders }) => {\n\tconst headers = {\n\t\t'Server-Timing': combineServerTimings(loaderHeaders, parentHeaders),\n\t}\n\treturn headers\n}\n\nexport default function ExercisePartRoute({\n\tloaderData: data,\n}: Route.ComponentProps) {\n\tconst apps = useApps()\n\tconst inBrowserBrowserRef = useRef<InBrowserBrowserRef>(null)\n\tconst containerRef = useRef<HTMLDivElement>(null)\n\tconst leftPaneRef = useRef<HTMLDivElement>(null)\n\tconst [splitPercent, setSplitPercent] = useState<number>(data.splitPercent)\n\n\tconst titleBits = pageTitle(data)\n\n\tconst playgroundBasePath = apps.find(\n\t\t(app) => app.name === data.playground?.appName,\n\t)?.fullPath\n\n\tuseRevalidationWS({\n\t\twatchPaths: [\n\t\t\t`${data.exerciseStepApp.relativePath}/README.mdx`,\n\t\t\tplaygroundBasePath,\n\t\t].filter((path): path is string => Boolean(path)),\n\t})\n\n\tconst showPlaygroundIndicator = data.problem\n\t\t? data.playground?.appName !== data.problem.name\n\t\t: false\n\n\treturn (\n\t\t<div className=\"flex max-w-full grow flex-col\">\n\t\t\t<main\n\t\t\t\tref={containerRef}\n\t\t\t\tclassName=\"flex grow flex-col overflow-y-auto sm:h-full sm:min-h-[800px] md:min-h-[unset] lg:flex-row lg:overflow-y-hidden\"\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"relative flex min-w-0 flex-none basis-auto flex-col sm:col-span-1 sm:row-span-1 lg:h-full lg:basis-(--split-pct)\"\n\t\t\t\t\tstyle={{ ['--split-pct' as any]: `${splitPercent}%` }}\n\t\t\t\t\tref={leftPaneRef}\n\t\t\t\t>\n\t\t\t\t\t<h1 className=\"scrollbar-thin scrollbar-thumb-scrollbar @container h-14 max-w-full overflow-x-auto overflow-y-hidden border-b pr-5 pl-10 text-sm leading-tight font-medium\">\n\t\t\t\t\t\t<div className=\"flex h-14 items-center justify-between gap-x-2 py-2 whitespace-nowrap\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-start gap-x-2 uppercase\">\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\tto={getExercisePath(data.exerciseStepApp.exerciseNumber)}\n\t\t\t\t\t\t\t\t\tclassName=\"hover:underline\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span>{titleBits.exerciseNumber}.</span>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden @min-[500px]:inline\">\n\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t{titleBits.exerciseTitle}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t<span>/</span>\n\t\t\t\t\t\t\t\t<Link to=\".\" className=\"hover:underline\">\n\t\t\t\t\t\t\t\t\t<span>{titleBits.stepNumber}.</span>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden @min-[300px]:inline\">\n\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t{titleBits.title}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<span> ({titleBits.emoji}</span>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden @min-[400px]:inline\">\n\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t{titleBits.type}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<span>)</span>\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t{data.problem &&\n\t\t\t\t\t\t\t(data.playground?.appName !== data.problem.name ||\n\t\t\t\t\t\t\t\t!data.playground?.isUpToDate) ? (\n\t\t\t\t\t\t\t\t<SetAppToPlayground\n\t\t\t\t\t\t\t\t\tappName={data.problem.name}\n\t\t\t\t\t\t\t\t\tisOutdated={data.playground?.isUpToDate === false}\n\t\t\t\t\t\t\t\t\thideTextOnNarrow\n\t\t\t\t\t\t\t\t\tshowOnboardingIndicator={showPlaygroundIndicator}\n\t\t\t\t\t\t\t\t\tdata-keyboard-action=\"s+p\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</h1>\n\t\t\t\t\t<article\n\t\t\t\t\t\tid={data.articleId}\n\t\t\t\t\t\tkey={data.articleId}\n\t\t\t\t\t\tclassName=\"shadow-on-scrollbox scrollbar-thin scrollbar-thumb-scrollbar flex w-full max-w-none scroll-pt-6 flex-col justify-between space-y-6 p-2 sm:p-10 sm:pt-8 lg:h-full lg:flex-1 lg:overflow-y-auto\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{data.exerciseStepApp.instructionsCode ? (\n\t\t\t\t\t\t\t<StepMdx inBrowserBrowserRef={inBrowserBrowserRef} />\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<div className=\"flex h-full items-center justify-center text-lg\">\n\t\t\t\t\t\t\t\t<p>No instructions yet...</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t<div className=\"mt-auto flex justify-between\">\n\t\t\t\t\t\t\t{data.prevStepLink ? (\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\tto={data.prevStepLink.to}\n\t\t\t\t\t\t\t\t\taria-label=\"Previous Step\"\n\t\t\t\t\t\t\t\t\tdata-keyboard-action=\"g+p\"\n\t\t\t\t\t\t\t\t\tprefetch=\"intent\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span aria-hidden>←</span>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden xl:inline\"> Previous</span>\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span />\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{data.nextStepLink ? (\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\tto={data.nextStepLink.to}\n\t\t\t\t\t\t\t\t\taria-label=\"Next Step\"\n\t\t\t\t\t\t\t\t\tdata-keyboard-action=\"g+n\"\n\t\t\t\t\t\t\t\t\tprefetch=\"intent\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden xl:inline\">Next </span>\n\t\t\t\t\t\t\t\t\t<span aria-hidden>→</span>\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span />\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</article>\n\t\t\t\t\t<ElementScrollRestoration\n\t\t\t\t\t\telementQuery={`#${data.articleId}`}\n\t\t\t\t\t\tkey={`scroll-${data.articleId}`}\n\t\t\t\t\t/>\n\t\t\t\t\t{data.type === 'solution' ? (\n\t\t\t\t\t\t<ProgressToggle\n\t\t\t\t\t\t\ttype=\"step\"\n\t\t\t\t\t\t\texerciseNumber={data.exerciseStepApp.exerciseNumber}\n\t\t\t\t\t\t\tstepNumber={data.exerciseStepApp.stepNumber}\n\t\t\t\t\t\t\tclassName=\"h-14 border-t px-6\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<div className=\"@container flex h-16 justify-between border-t border-b-4 lg:border-b-0\">\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<div className=\"h-full\">\n\t\t\t\t\t\t\t\t<TouchedFiles diffFilesPromise={data.diffFiles} />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<EditFileOnGitHub\n\t\t\t\t\t\t\tappName={data.exerciseStepApp.name}\n\t\t\t\t\t\t\trelativePath={`${data.exerciseStepApp.relativePath}/README.mdx`}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<NavChevrons\n\t\t\t\t\t\t\tprev={\n\t\t\t\t\t\t\t\tdata.prevStepLink\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tto: data.prevStepLink.to,\n\t\t\t\t\t\t\t\t\t\t\t'aria-label': 'Previous Step',\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnext={\n\t\t\t\t\t\t\t\tdata.nextStepLink\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tto: data.nextStepLink.to,\n\t\t\t\t\t\t\t\t\t\t\t'aria-label': 'Next Step',\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\trole=\"separator\"\n\t\t\t\t\taria-orientation=\"vertical\"\n\t\t\t\t\ttitle=\"Drag to resize\"\n\t\t\t\t\tclassName=\"bg-border hover:bg-accent hidden w-1 cursor-col-resize lg:block\"\n\t\t\t\t\tonMouseDown={(event) =>\n\t\t\t\t\t\tstartSplitDrag({\n\t\t\t\t\t\t\tcontainer: containerRef.current,\n\t\t\t\t\t\t\tinitialClientX: event.clientX,\n\t\t\t\t\t\t\tsetSplitPercent,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tonDoubleClick={() => {\n\t\t\t\t\t\tsetSplitPercent(setSplitPercentCookie(50))\n\t\t\t\t\t}}\n\t\t\t\t\tonTouchStart={(event) => {\n\t\t\t\t\t\tconst firstTouch = event.touches?.[0]\n\t\t\t\t\t\tif (!firstTouch) return\n\t\t\t\t\t\tstartSplitDrag({\n\t\t\t\t\t\t\tcontainer: containerRef.current,\n\t\t\t\t\t\t\tinitialClientX: firstTouch.clientX,\n\t\t\t\t\t\t\tsetSplitPercent,\n\t\t\t\t\t\t})\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<div className=\"flex min-h-[50vh] min-w-0 flex-none lg:min-h-0 lg:flex-1\">\n\t\t\t\t\t<Outlet context={{ inBrowserBrowserRef }} />\n\t\t\t\t</div>\n\t\t\t</main>\n\t\t</div>\n\t)\n}\n\nexport function ErrorBoundary() {\n\treturn (\n\t\t<GeneralErrorBoundary\n\t\t\tclassName=\"container flex items-center justify-center\"\n\t\t\tstatusHandlers={{\n\t\t\t\t404: Exercise404ErrorBoundary,\n\t\t\t}}\n\t\t/>\n\t)\n}\n"],"names":["StepContext","React.createContext","useStepContext","context","React.useContext","StepContextProvider","children","inBrowserBrowserRef","stepMdxComponents","DiffLink","PrevDiffLink","NextDiffLink","InlineFile","LinkToApp","StepMdx","data","useLoaderData","jsx","EpicVideoInfoProvider","Mdx","withParam","searchParams","key","value","newSearchParams","app","fullPage","app1","app2","to","getAppName","input","stepIndex","name","stepName","params","app1Name","app2Name","jsxs","pathToDiff","Link","file","type","props","expectedPlaygroundAppName","isPlaygroundReady","disabledReason","info","iconsSvg","SimpleTooltip","LaunchEditor","getPreviewType","preview","appTo","useSearchParams","requestInfo","useRequestInfo","previewAppUrl","getBaseUrl","href","cn","event","Icon","TouchedFiles","diffFilesPromise","showFilesBadge","dismissFilesBadge","useOnboardingIndicator","open","setOpen","React.useState","contentRef","React.useRef","handleOpenChange","isOpen","handleLaunchUpdate","appName","Popover.Root","Popover.Trigger","OnboardingBadge","Popover.Portal","Popover.Content","SetAppToPlayground","React.Suspense","Await","diffFiles","pageTitle","workshopTitle","exerciseNumber","exerciseStepApp","toString","padStart","stepNumber","emoji","problem","solution","title","exerciseTitle","meta","loaderData","matches","rootData","getRootMatchLoaderData","getSeoMetaTags","description","ogTitle","ogDescription","Number","instructor","_layout","_UNSAFE_withComponentProps","apps","useApps","useRef","containerRef","leftPaneRef","splitPercent","setSplitPercent","useState","titleBits","playgroundBasePath","find","playground","fullPath","useRevalidationWS","watchPaths","relativePath","filter","path","Boolean","showPlaygroundIndicator","className","ref","style","getExercisePath","isUpToDate","isOutdated","hideTextOnNarrow","showOnboardingIndicator","id","articleId","instructionsCode","prevStepLink","prefetch","nextStepLink","ElementScrollRestoration","elementQuery","ProgressToggle","EditFileOnGitHub","NavChevrons","prev","next","role","onMouseDown","startSplitDrag","container","current","initialClientX","clientX","onDoubleClick","setSplitPercentCookie","onTouchStart","firstTouch","touches","Outlet","ErrorBoundary","_UNSAFE_withErrorBoundaryProps","GeneralErrorBoundary","statusHandlers","Exercise404ErrorBoundary"],"mappings":"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"}
1
+ {"version":3,"file":"_layout-CbP-ZGKA.js","sources":["../../../app/routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/__shared/step-mdx.tsx","../../../app/routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/__shared/touched-files.tsx","../../../app/routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/_layout.tsx"],"sourcesContent":["import * as React from 'react'\nimport { type PropsWithChildren } from 'react'\nimport {\n\tLink,\n\tuseLoaderData,\n\tuseSearchParams,\n\ttype LinkProps,\n} from 'react-router'\nimport iconsSvg from '#app/assets/icons.svg'\nimport { EpicVideoInfoProvider } from '#app/components/epic-video.tsx'\nimport { Icon } from '#app/components/icons.tsx'\nimport { type InBrowserBrowserRef } from '#app/components/in-browser-browser.tsx'\nimport { SimpleTooltip } from '#app/components/ui/tooltip.tsx'\nimport { LaunchEditor } from '#app/routes/launch-editor.tsx'\nimport { Mdx } from '#app/utils/mdx.tsx'\nimport { cn, getBaseUrl } from '#app/utils/misc.tsx'\nimport { useRequestInfo } from '#app/utils/root-loader.ts'\nimport { type loader } from '../_layout.tsx'\n\ntype StepContextType = {\n\tinBrowserBrowserRef: React.RefObject<InBrowserBrowserRef | null>\n}\nconst StepContext = React.createContext<StepContextType | null>(null)\n\nfunction useStepContext() {\n\tconst context = React.useContext(StepContext)\n\tif (!context) {\n\t\tthrow new Error('useStepContext must be used within a StepContextProvider')\n\t}\n\treturn context\n}\n\nfunction StepContextProvider({\n\tchildren,\n\tinBrowserBrowserRef,\n}: {\n\tchildren: React.ReactNode\n\tinBrowserBrowserRef: React.RefObject<InBrowserBrowserRef | null>\n}) {\n\treturn <StepContext value={{ inBrowserBrowserRef }}>{children}</StepContext>\n}\n\nconst stepMdxComponents = {\n\tDiffLink,\n\tPrevDiffLink,\n\tNextDiffLink,\n\tInlineFile,\n\tLinkToApp,\n}\n\nexport function StepMdx({\n\tinBrowserBrowserRef,\n}: {\n\tinBrowserBrowserRef: React.RefObject<InBrowserBrowserRef | null>\n}) {\n\tconst data = useLoaderData<typeof loader>()\n\tif (!data.exerciseStepApp.instructionsCode) return null\n\treturn (\n\t\t<StepContextProvider inBrowserBrowserRef={inBrowserBrowserRef}>\n\t\t\t<EpicVideoInfoProvider epicVideoInfosPromise={data.epicVideoInfosPromise}>\n\t\t\t\t<div className=\"prose dark:prose-invert sm:prose-lg\">\n\t\t\t\t\t<Mdx\n\t\t\t\t\t\tcode={data.exerciseStepApp.instructionsCode}\n\t\t\t\t\t\tcomponents={stepMdxComponents}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</EpicVideoInfoProvider>\n\t\t</StepContextProvider>\n\t)\n}\n\nfunction withParam(\n\tsearchParams: URLSearchParams,\n\tkey: string,\n\tvalue: string | null,\n) {\n\tconst newSearchParams = new URLSearchParams(searchParams)\n\tif (value === null) {\n\t\tnewSearchParams.delete(key)\n\t} else {\n\t\tnewSearchParams.set(key, value)\n\t}\n\treturn newSearchParams\n}\n\nfunction NextDiffLink({\n\tapp = 0,\n\tfullPage = false,\n\tchildren,\n}: {\n\tapp: number\n\tfullPage?: boolean\n\tchildren?: React.ReactNode\n}) {\n\treturn (\n\t\t<DiffLink app1={app} app2={app + 1} fullPage={fullPage}>\n\t\t\t{children}\n\t\t</DiffLink>\n\t)\n}\n\nfunction PrevDiffLink({\n\tapp = -1,\n\tfullPage = false,\n\tchildren,\n}: {\n\tapp: number\n\tfullPage?: boolean\n\tchildren?: React.ReactNode\n}) {\n\treturn (\n\t\t<DiffLink app1={app} app2={app + 1} fullPage={fullPage}>\n\t\t\t{children}\n\t\t</DiffLink>\n\t)\n}\n\nfunction DiffLink({\n\tapp1 = 0,\n\tapp2 = 1,\n\tchildren,\n\tfullPage = false,\n\tto,\n}: {\n\tapp1?: string | number | null\n\tapp2?: string | number | null\n\tto?: string\n\tfullPage?: boolean\n\tchildren?: React.ReactNode\n}) {\n\tconst data = useLoaderData<typeof loader>()\n\tif (!to && !app1 && !app2) {\n\t\treturn (\n\t\t\t<callout-danger className=\"notification\">\n\t\t\t\t<div className=\"title\">DiffLink Error: invalid input</div>\n\t\t\t</callout-danger>\n\t\t)\n\t}\n\n\tfunction getAppName(input: typeof app1) {\n\t\tif (typeof input === 'number') {\n\t\t\tconst stepIndex = data.exerciseIndex + input\n\t\t\treturn data.allApps[stepIndex]?.name\n\t\t}\n\t\tif (!input) return null\n\t\tfor (const { name, stepName } of data.allApps) {\n\t\t\tif (input === name || input === stepName) {\n\t\t\t\treturn name\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\tif (to) {\n\t\tconst params = new URLSearchParams(to)\n\t\tapp1 = params.get('app1')\n\t\tapp2 = params.get('app2')\n\t}\n\tconst app1Name = getAppName(app1)\n\tconst app2Name = getAppName(app2)\n\tif (!app1Name || !app2Name) {\n\t\treturn (\n\t\t\t<callout-danger className=\"notification\">\n\t\t\t\t<div className=\"title\">DiffLink Error: invalid input</div>\n\t\t\t\t{!app1Name && <div>app1: \"{app1}\" is not a valid app name</div>}\n\t\t\t\t{!app2Name && <div>app2: \"{app2}\" is not a valid app name</div>}\n\t\t\t</callout-danger>\n\t\t)\n\t}\n\n\tif (!to) {\n\t\tto = `app1=${app1Name}&app2=${app2Name}`\n\t}\n\tconst pathToDiff = fullPage\n\t\t? `/diff?${to}`\n\t\t: `?${decodeURIComponent(\n\t\t\t\twithParam(new URLSearchParams(), 'preview', `diff&${to}`).toString(),\n\t\t\t)}`\n\n\tif (!children) {\n\t\tchildren = (\n\t\t\t<span>\n\t\t\t\tGo to Diff {fullPage ? '' : 'Preview'} from: <code>{app1Name}</code> to:{' '}\n\t\t\t\t<code>{app2Name}</code>\n\t\t\t</span>\n\t\t)\n\t}\n\n\treturn <Link to={pathToDiff}>{children}</Link>\n}\n\nfunction InlineFile({\n\tfile,\n\ttype = 'playground',\n\tchildren = <code>{file}</code>,\n\t...props\n}: Omit<PropsWithChildren<typeof LaunchEditor>, 'appName'> & {\n\tfile: string\n\ttype?: 'playground' | 'solution' | 'problem'\n}) {\n\tconst data = useLoaderData<typeof loader>()\n\tconst app = data[type] || data[data.type]\n\tconst expectedPlaygroundAppName = data.problem?.name ?? data[data.type]?.name\n\tconst isPlaygroundReady =\n\t\ttype !== 'playground' ||\n\t\t(Boolean(expectedPlaygroundAppName) &&\n\t\t\tdata.playground?.appName === expectedPlaygroundAppName)\n\tconst disabledReason =\n\t\ttype === 'playground' && !isPlaygroundReady\n\t\t\t? 'Set the playground to this step before opening a file.'\n\t\t\t: null\n\n\tconst info = (\n\t\t<div className=\"launch-editor-button-wrapper flex underline underline-offset-4\">\n\t\t\t{children}{' '}\n\t\t\t<svg height={24} width={24}>\n\t\t\t\t<use href={`${iconsSvg}#Keyboard`} />\n\t\t\t</svg>\n\t\t</div>\n\t)\n\n\tif (disabledReason) {\n\t\treturn (\n\t\t\t<SimpleTooltip content={disabledReason}>\n\t\t\t\t<div className=\"inline-block grow cursor-not-allowed\">{info}</div>\n\t\t\t</SimpleTooltip>\n\t\t)\n\t}\n\n\treturn ENV.EPICSHOP_DEPLOYED && app ? (\n\t\t<div className=\"inline-block grow\">\n\t\t\t<LaunchEditor appFile={file} appName={app.name} {...props}>\n\t\t\t\t{info}\n\t\t\t</LaunchEditor>\n\t\t</div>\n\t) : app ? (\n\t\t<div className=\"inline-block grow\">\n\t\t\t<LaunchEditor appFile={file} appName={app.name} {...props}>\n\t\t\t\t{info}\n\t\t\t</LaunchEditor>\n\t\t</div>\n\t) : (\n\t\t<>{children}</>\n\t)\n}\n\nfunction getPreviewType(\n\tpreview: string | null,\n): 'playground' | 'problem' | 'solution' {\n\tif (preview === 'problem') return 'problem'\n\tif (preview === 'solution') return 'solution'\n\treturn 'playground'\n}\n\nfunction LinkToApp({\n\tto: appTo,\n\tchildren = <code>{appTo.toString()}</code>,\n\t...props\n}: LinkProps) {\n\tconst [searchParams] = useSearchParams()\n\tconst to = `?${withParam(\n\t\tsearchParams,\n\t\t'pathname',\n\t\tappTo.toString(),\n\t).toString()}`\n\tconst data = useLoaderData<typeof loader>()\n\tconst type = getPreviewType(searchParams.get('preview'))\n\tconst requestInfo = useRequestInfo()\n\tconst app = data[type]\n\tconst previewAppUrl =\n\t\tapp?.dev.type === 'script'\n\t\t\t? getBaseUrl({\n\t\t\t\t\tdomain: requestInfo.domain,\n\t\t\t\t\tport: app.dev.portNumber,\n\t\t\t\t})\n\t\t\t: data.playground?.dev.type === 'browser' ||\n\t\t\t\t data.playground?.dev.type === 'export'\n\t\t\t\t? data.playground.dev.pathname\n\t\t\t\t: null\n\tconst { inBrowserBrowserRef } = useStepContext()\n\tconst href = previewAppUrl\n\t\t? previewAppUrl.slice(0, -1) + appTo.toString()\n\t\t: null\n\treturn (\n\t\t<div className=\"inline-flex items-center justify-between gap-1\">\n\t\t\t<Link\n\t\t\t\tto={to}\n\t\t\t\t{...props}\n\t\t\t\tclassName={cn(props.className, {\n\t\t\t\t\t'cursor-not-allowed': ENV.EPICSHOP_DEPLOYED,\n\t\t\t\t})}\n\t\t\t\ttitle={\n\t\t\t\t\tENV.EPICSHOP_DEPLOYED\n\t\t\t\t\t\t? 'Cannot link to app in deployed version'\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tonClick={(event) => {\n\t\t\t\t\tif (ENV.EPICSHOP_DEPLOYED) event.preventDefault()\n\n\t\t\t\t\tprops.onClick?.(event)\n\t\t\t\t\tinBrowserBrowserRef.current?.handleExtrnalNavigation(appTo.toString())\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</Link>\n\t\t\t{href ? (\n\t\t\t\t<SimpleTooltip content=\"Open in new tab\">\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={href}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noreferrer\"\n\t\t\t\t\t\tclassName={cn('flex aspect-square items-center justify-center', {\n\t\t\t\t\t\t\t'cursor-not-allowed': ENV.EPICSHOP_DEPLOYED,\n\t\t\t\t\t\t})}\n\t\t\t\t\t\ttitle={\n\t\t\t\t\t\t\tENV.EPICSHOP_DEPLOYED\n\t\t\t\t\t\t\t\t? 'Cannot link to app in deployed version'\n\t\t\t\t\t\t\t\t: 'Open in new tab'\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\tif (ENV.EPICSHOP_DEPLOYED) event.preventDefault()\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon name=\"ExternalLink\" />\n\t\t\t\t\t</a>\n\t\t\t\t</SimpleTooltip>\n\t\t\t) : null}\n\t\t</div>\n\t)\n}\n","import * as Popover from '@radix-ui/react-popover'\nimport * as React from 'react'\nimport { Await, Link, useLoaderData } from 'react-router'\nimport { Icon } from '#app/components/icons.tsx'\nimport {\n\tOnboardingBadge,\n\tuseOnboardingIndicator,\n} from '#app/components/onboarding-indicator.tsx'\nimport { SimpleTooltip } from '#app/components/ui/tooltip.tsx'\nimport { LaunchEditor } from '#app/routes/launch-editor.tsx'\nimport { SetAppToPlayground } from '#app/routes/set-playground.tsx'\nimport { type Route as LayoutRoute } from '../+types/_layout.tsx'\n\nfunction TouchedFiles({\n\tdiffFilesPromise,\n}: {\n\tdiffFilesPromise: LayoutRoute.ComponentProps['loaderData']['diffFiles']\n}) {\n\tconst data = useLoaderData<LayoutRoute.ComponentProps['loaderData']>()\n\tconst [showFilesBadge, dismissFilesBadge] =\n\t\tuseOnboardingIndicator('files-popover')\n\n\tconst [open, setOpen] = React.useState(false)\n\tconst contentRef = React.useRef<HTMLDivElement>(null)\n\n\tfunction handleOpenChange(isOpen: boolean) {\n\t\tsetOpen(isOpen)\n\t\t// Mark as complete when opening the popover for the first time\n\t\tif (isOpen) {\n\t\t\tdismissFilesBadge()\n\t\t}\n\t}\n\n\tfunction handleLaunchUpdate() {\n\t\tsetOpen(false)\n\t}\n\n\tconst appName = data.playground?.appName\n\n\treturn (\n\t\t<>\n\t\t\t<Popover.Root open={open} onOpenChange={handleOpenChange}>\n\t\t\t\t<Popover.Trigger asChild>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"relative flex h-full items-center gap-1 border-r px-6 py-3 font-mono text-sm uppercase\"\n\t\t\t\t\t\taria-label=\"Relevant Files\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon name=\"Files\" />\n\t\t\t\t\t\t<span className=\"hidden @min-[640px]:inline\">Files</span>\n\t\t\t\t\t\t{showFilesBadge ? (\n\t\t\t\t\t\t\t<OnboardingBadge tooltip=\"Click to see which files to edit!\" />\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</button>\n\t\t\t\t</Popover.Trigger>\n\t\t\t\t<Popover.Portal>\n\t\t\t\t\t<Popover.Content\n\t\t\t\t\t\tref={contentRef}\n\t\t\t\t\t\tclassName=\"slideRightContent lg:slideUpContent invert-theme bg-background text-foreground z-10 rounded px-9 py-8 select-none\"\n\t\t\t\t\t\talign=\"start\"\n\t\t\t\t\t\tsideOffset={5}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"launch-editor-wrapper\">\n\t\t\t\t\t\t\t<strong className=\"inline-block px-2 pb-2 font-semibold uppercase\">\n\t\t\t\t\t\t\t\tRelevant Files\n\t\t\t\t\t\t\t</strong>\n\t\t\t\t\t\t\t<p className=\"text-muted-foreground mb-4 max-w-2xs px-2 text-sm\">\n\t\t\t\t\t\t\t\tThese are the files you'll need to modify for this exercise.\n\t\t\t\t\t\t\t\tClick any file to open it directly in your editor at the right\n\t\t\t\t\t\t\t\tlocation.{' '}\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\tto=\"/guide#file-links\"\n\t\t\t\t\t\t\t\t\tclassName=\"text-highlight underline\"\n\t\t\t\t\t\t\t\t\tonClick={() => setOpen(false)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tLearn more →\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t{data.problem &&\n\t\t\t\t\t\t\tdata.playground?.appName !== data.problem.name ? (\n\t\t\t\t\t\t\t\t<div className=\"mb-2 rounded p-1 font-mono font-medium\">\n\t\t\t\t\t\t\t\t\t<SetAppToPlayground appName={data.problem.name} />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t<div id=\"files\">\n\t\t\t\t\t\t\t\t<React.Suspense\n\t\t\t\t\t\t\t\t\tfallback={\n\t\t\t\t\t\t\t\t\t\t<SimpleTooltip content=\"Loading diff\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex justify-center\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Icon name=\"Refresh\" className=\"h-8 w-8 animate-spin\" />\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</SimpleTooltip>\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\t\t<Await\n\t\t\t\t\t\t\t\t\t\tresolve={diffFilesPromise}\n\t\t\t\t\t\t\t\t\t\terrorElement={\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"text-foreground-destructive\">\n\t\t\t\t\t\t\t\t\t\t\t\tSomething went wrong.\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{(diffFiles) => {\n\t\t\t\t\t\t\t\t\t\t\tif (!diffFiles) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-foreground-destructive\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tUnable to determine diff\n\t\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\t)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tif (typeof diffFiles === 'string') {\n\t\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-foreground-destructive\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{diffFiles}\n\t\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\t)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tif (!diffFiles.length) {\n\t\t\t\t\t\t\t\t\t\t\t\treturn <p>No files changed</p>\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\tconst props =\n\t\t\t\t\t\t\t\t\t\t\t\tappName || ENV.EPICSHOP_GITHUB_ROOT\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\t: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"You must 'Set to Playground' before opening a file\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName: 'not-allowed',\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\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t<ul {...props}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{diffFiles.length > 1 && !ENV.EPICSHOP_DEPLOYED ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"border-opacity-50 mb-2 border-b border-b-gray-50 pb-2 font-sans\">\n\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\tappFile={diffFiles.map(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(file) => `${file.path},${file.line},1`,\n\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\tappName=\"playground\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonUpdate={handleLaunchUpdate}\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\t<p>Open All Files</p>\n\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</div>\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\t{diffFiles.map((file) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<li key={file.path} data-state={file.status}>\n\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\tappFile={`${file.path},${file.line},1`}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tappName={\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tENV.EPICSHOP_DEPLOYED\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? (data.problem?.name ?? 'playground')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: 'playground'\n\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\tonUpdate={handleLaunchUpdate}\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\t<code>{file.path}</code>\n\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</li>\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</ul>\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t</Await>\n\t\t\t\t\t\t\t\t</React.Suspense>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Popover.Content>\n\t\t\t\t</Popover.Portal>\n\t\t\t</Popover.Root>\n\t\t</>\n\t)\n}\n\nexport default TouchedFiles\n","import { ElementScrollRestoration } from '@epic-web/restore-scroll'\nimport {\n\tgetAppDisplayName,\n\tgetAppPageRoute,\n\tgetApps,\n\tgetExerciseApp,\n\tgetNextExerciseApp,\n\tgetPrevExerciseApp,\n\tisExerciseStepApp,\n\tisPlaygroundApp,\n\trequireExercise,\n\trequireExerciseApp,\n\ttype App,\n\ttype ExerciseStepApp,\n} from '@epic-web/workshop-utils/apps.server'\nimport { getWorkshopConfig } from '@epic-web/workshop-utils/config.server'\nimport { getDiffFiles } from '@epic-web/workshop-utils/diff.server'\nimport { getEpicVideoInfos } from '@epic-web/workshop-utils/epic-api.server'\nimport {\n\tcombineServerTimings,\n\tgetServerTimeHeader,\n\tmakeTimings,\n} from '@epic-web/workshop-utils/timing.server'\nimport slugify from '@sindresorhus/slugify'\nimport { useRef, useState } from 'react'\nimport {\n\tLink,\n\tOutlet,\n\tdata,\n\tredirect,\n\ttype HeadersFunction,\n} from 'react-router'\nimport { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'\nimport { type InBrowserBrowserRef } from '#app/components/in-browser-browser.tsx'\nimport { NavChevrons } from '#app/components/nav-chevrons.tsx'\nimport { useRevalidationWS } from '#app/components/revalidation-ws.tsx'\nimport { EditFileOnGitHub } from '#app/routes/launch-editor.tsx'\nimport { ProgressToggle } from '#app/routes/progress.tsx'\nimport { SetAppToPlayground } from '#app/routes/set-playground.tsx'\nimport { getExercisePath } from '#app/utils/misc.tsx'\nimport { getRootMatchLoaderData, useApps } from '#app/utils/root-loader.ts'\nimport { getSeoMetaTags } from '#app/utils/seo.ts'\nimport {\n\tgetSplitPercentFromRequest,\n\tsetSplitPercentCookie,\n\tstartSplitDrag,\n} from '#app/utils/split-layout.ts'\nimport { getStep404Data } from '../__shared/error-boundary.server.ts'\nimport { Exercise404ErrorBoundary } from '../__shared/error-boundary.tsx'\nimport { type Route } from './+types/_layout.tsx'\nimport { StepMdx } from './__shared/step-mdx.tsx'\nimport TouchedFiles from './__shared/touched-files.tsx'\n\nfunction pageTitle(\n\tdata: Awaited<Route.ComponentProps['loaderData']> | undefined,\n\tworkshopTitle?: string,\n) {\n\tconst exerciseNumber =\n\t\tdata?.exerciseStepApp.exerciseNumber.toString().padStart(2, '0') ?? '00'\n\tconst stepNumber =\n\t\tdata?.exerciseStepApp.stepNumber.toString().padStart(2, '0') ?? '00'\n\tconst emoji = (\n\t\t{\n\t\t\tproblem: '💪',\n\t\t\tsolution: '🏁',\n\t\t} as const\n\t)[data?.type ?? 'problem']\n\tconst title = data?.[data.type]?.title ?? 'N/A'\n\treturn {\n\t\temoji,\n\t\tstepNumber,\n\t\ttitle,\n\t\texerciseNumber,\n\t\texerciseTitle: data?.exerciseTitle ?? 'Unknown exercise',\n\t\tworkshopTitle,\n\t\ttype: data?.type ?? 'problem',\n\t}\n}\n\nexport const meta: Route.MetaFunction = ({ loaderData, matches, params }) => {\n\tconst rootData = getRootMatchLoaderData(matches)\n\tif (!loaderData || !rootData) return [{ title: '🦉 | Error' }]\n\tconst { emoji, stepNumber, title, exerciseNumber, exerciseTitle } =\n\t\tpageTitle(loaderData)\n\n\treturn getSeoMetaTags({\n\t\ttitle: `${emoji} | ${stepNumber}. ${title} | ${exerciseNumber}. ${exerciseTitle} | ${rootData.workshopTitle}`,\n\t\tdescription: `${params.type} step for exercise ${exerciseNumber}. ${exerciseTitle}`,\n\t\togTitle: title,\n\t\togDescription: `${exerciseTitle} step ${Number(stepNumber)} ${params.type}`,\n\t\tinstructor: rootData.instructor,\n\t\trequestInfo: rootData.requestInfo,\n\t})\n}\n\nexport async function loader({ request, params }: Route.LoaderArgs) {\n\tconst timings = makeTimings('exerciseStepTypeLayoutLoader')\n\tconst url = new URL(request.url)\n\tconst { type } = params\n\tconst { title: workshopTitle } = getWorkshopConfig()\n\n\tconst cacheOptions = { request, timings }\n\n\tconst [allAppsFull, problemApp, solutionApp] = await Promise.all([\n\t\tgetApps(cacheOptions),\n\t\tgetExerciseApp({ ...params, type: 'problem' }, cacheOptions),\n\t\tgetExerciseApp({ ...params, type: 'solution' }, cacheOptions),\n\t])\n\n\tconst reqUrl = new URL(request.url)\n\tconst pathnameParam = reqUrl.searchParams.get('pathname')\n\tif (pathnameParam === '' || pathnameParam === '/') {\n\t\treqUrl.searchParams.delete('pathname')\n\t\tthrow redirect(reqUrl.toString())\n\t}\n\n\tif (\n\t\t(type === 'problem' && !problemApp) ||\n\t\t(type === 'solution' && !solutionApp)\n\t) {\n\t\tconst errorData = await getStep404Data({\n\t\t\texerciseNumber: params.exerciseNumber,\n\t\t})\n\t\tthrow Response.json(errorData, { status: 404 })\n\t}\n\n\tconst exerciseStepApp = await requireExerciseApp(params, cacheOptions)\n\n\tconst playgroundApp = allAppsFull.find(isPlaygroundApp)\n\n\tfunction getStepId(a: ExerciseStepApp) {\n\t\treturn (\n\t\t\ta.exerciseNumber * 1000 +\n\t\t\ta.stepNumber * 10 +\n\t\t\t(a.type === 'problem' ? 0 : 1)\n\t\t)\n\t}\n\n\tfunction getStepNameAndId(a: App) {\n\t\tif (isExerciseStepApp(a)) {\n\t\t\tconst exerciseNumberStr = String(a.exerciseNumber).padStart(2, '0')\n\t\t\tconst stepNumberStr = String(a.stepNumber).padStart(2, '0')\n\n\t\t\treturn {\n\t\t\t\tstepName: `${exerciseNumberStr}/${stepNumberStr}.${a.type}`,\n\t\t\t\tstepId: getStepId(a),\n\t\t\t}\n\t\t}\n\t\treturn { stepName: '', stepId: -1 }\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\t...getStepNameAndId(a),\n\t\t}))\n\n\tallApps.sort((a, b) => {\n\t\t// order them by their stepId\n\t\tif (a.stepId > 0 && b.stepId > 0) return a.stepId - b.stepId\n\n\t\t// non-step apps should come after step apps\n\t\tif (a.stepId > 0) return -1\n\t\tif (b.stepId > 0) return 1\n\n\t\treturn 0\n\t})\n\tconst exerciseId = getStepId(exerciseStepApp)\n\tconst exerciseIndex = allApps.findIndex((step) => step.stepId === exerciseId)\n\n\t// These depend on exerciseStepApp\n\tconst [exercise, nextApp, prevApp] = await Promise.all([\n\t\trequireExercise(exerciseStepApp.exerciseNumber, cacheOptions),\n\t\tgetNextExerciseApp(exerciseStepApp, cacheOptions),\n\t\tgetPrevExerciseApp(exerciseStepApp, cacheOptions),\n\t])\n\n\tconst exerciseApps = allAppsFull\n\t\t.filter(isExerciseStepApp)\n\t\t.filter((app) => app.exerciseNumber === exerciseStepApp.exerciseNumber)\n\tconst isLastStep =\n\t\texerciseApps[exerciseApps.length - 1]?.name === exerciseStepApp.name\n\tconst isFirstStep = exerciseApps[0]?.name === exerciseStepApp.name\n\n\tconst articleId = `workshop-${slugify(workshopTitle)}-${\n\t\texercise.exerciseNumber\n\t}-${exerciseStepApp.stepNumber}-${exerciseStepApp.type}`\n\n\tconst subroute = url.pathname.split(\n\t\t`/exercise/${params.exerciseNumber}/${params.stepNumber}/${params.type}/`,\n\t)[1]\n\n\t// read persisted split percentage from cookie (10-90, default 50)\n\tconst splitPercent = getSplitPercentFromRequest(request, 50)\n\n\treturn data(\n\t\t{\n\t\t\tarticleId,\n\t\t\ttype: params.type as 'problem' | 'solution',\n\t\t\texerciseStepApp,\n\t\t\texerciseTitle: exercise.title,\n\t\t\tepicVideoInfosPromise: getEpicVideoInfos(exerciseStepApp.epicVideoEmbeds),\n\t\t\texerciseIndex,\n\t\t\tallApps,\n\t\t\tsplitPercent,\n\t\t\tprevStepLink: isFirstStep\n\t\t\t\t? {\n\t\t\t\t\t\tto: `/exercise/${exerciseStepApp.exerciseNumber\n\t\t\t\t\t\t\t.toString()\n\t\t\t\t\t\t\t.padStart(2, '0')}`,\n\t\t\t\t\t}\n\t\t\t\t: prevApp\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tto: getAppPageRoute(prevApp, {\n\t\t\t\t\t\t\t\tsubroute,\n\t\t\t\t\t\t\t\tsearchParams: url.searchParams,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t: null,\n\t\t\tnextStepLink: isLastStep\n\t\t\t\t? {\n\t\t\t\t\t\tto: `/exercise/${exerciseStepApp.exerciseNumber\n\t\t\t\t\t\t\t.toString()\n\t\t\t\t\t\t\t.padStart(2, '0')}/finished`,\n\t\t\t\t\t}\n\t\t\t\t: nextApp\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tto: getAppPageRoute(nextApp, {\n\t\t\t\t\t\t\t\tsubroute,\n\t\t\t\t\t\t\t\tsearchParams: url.searchParams,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t: null,\n\t\t\tplayground: playgroundApp\n\t\t\t\t? ({\n\t\t\t\t\t\ttype: 'playground',\n\t\t\t\t\t\tappName: playgroundApp.appName,\n\t\t\t\t\t\tname: playgroundApp.name,\n\t\t\t\t\t\tfullPath: playgroundApp.fullPath,\n\t\t\t\t\t\tdev: playgroundApp.dev,\n\t\t\t\t\t\tisUpToDate: playgroundApp.isUpToDate,\n\t\t\t\t\t} as const)\n\t\t\t\t: null,\n\t\t\tproblem: problemApp\n\t\t\t\t? ({\n\t\t\t\t\t\ttype: 'problem',\n\t\t\t\t\t\ttitle: problemApp.title,\n\t\t\t\t\t\tname: problemApp.name,\n\t\t\t\t\t\tfullPath: problemApp.fullPath,\n\t\t\t\t\t\tdev: problemApp.dev,\n\t\t\t\t\t} as const)\n\t\t\t\t: null,\n\t\t\tsolution: solutionApp\n\t\t\t\t? ({\n\t\t\t\t\t\ttype: 'solution',\n\t\t\t\t\t\ttitle: solutionApp.title,\n\t\t\t\t\t\tname: solutionApp.name,\n\t\t\t\t\t\tfullPath: solutionApp.fullPath,\n\t\t\t\t\t\tdev: solutionApp.dev,\n\t\t\t\t\t} as const)\n\t\t\t\t: null,\n\t\t\tdiffFiles:\n\t\t\t\tproblemApp && solutionApp\n\t\t\t\t\t? getDiffFiles(problemApp, solutionApp, {\n\t\t\t\t\t\t\t...cacheOptions,\n\t\t\t\t\t\t\tforceFresh: url.searchParams.get('forceFresh') === 'diff',\n\t\t\t\t\t\t}).catch((e) => {\n\t\t\t\t\t\t\tconsole.error(e)\n\t\t\t\t\t\t\treturn 'There was a problem generating the diff (check the terminal output)'\n\t\t\t\t\t\t})\n\t\t\t\t\t: 'No diff available',\n\t\t} as const,\n\t\t{\n\t\t\theaders: {\n\t\t\t\t'Server-Timing': getServerTimeHeader(timings),\n\t\t\t},\n\t\t},\n\t)\n}\n\nexport const headers: HeadersFunction = ({ loaderHeaders, parentHeaders }) => {\n\tconst headers = {\n\t\t'Server-Timing': combineServerTimings(loaderHeaders, parentHeaders),\n\t}\n\treturn headers\n}\n\nexport default function ExercisePartRoute({\n\tloaderData: data,\n}: Route.ComponentProps) {\n\tconst apps = useApps()\n\tconst inBrowserBrowserRef = useRef<InBrowserBrowserRef>(null)\n\tconst containerRef = useRef<HTMLDivElement>(null)\n\tconst leftPaneRef = useRef<HTMLDivElement>(null)\n\tconst [splitPercent, setSplitPercent] = useState<number>(data.splitPercent)\n\n\tconst titleBits = pageTitle(data)\n\n\tconst playgroundBasePath = apps.find(\n\t\t(app) => app.name === data.playground?.appName,\n\t)?.fullPath\n\n\tuseRevalidationWS({\n\t\twatchPaths: [\n\t\t\t`${data.exerciseStepApp.relativePath}/README.mdx`,\n\t\t\tplaygroundBasePath,\n\t\t].filter((path): path is string => Boolean(path)),\n\t})\n\n\tconst showPlaygroundIndicator = data.problem\n\t\t? data.playground?.appName !== data.problem.name\n\t\t: false\n\n\treturn (\n\t\t<div className=\"flex max-w-full grow flex-col\">\n\t\t\t<main\n\t\t\t\tref={containerRef}\n\t\t\t\tclassName=\"flex grow flex-col overflow-y-auto sm:h-full sm:min-h-[800px] md:min-h-[unset] lg:flex-row lg:overflow-y-hidden\"\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"relative flex min-w-0 flex-none basis-auto flex-col sm:col-span-1 sm:row-span-1 lg:h-full lg:basis-(--split-pct)\"\n\t\t\t\t\tstyle={{ ['--split-pct' as any]: `${splitPercent}%` }}\n\t\t\t\t\tref={leftPaneRef}\n\t\t\t\t>\n\t\t\t\t\t<h1 className=\"scrollbar-thin scrollbar-thumb-scrollbar @container h-14 max-w-full overflow-x-auto overflow-y-hidden border-b pr-5 pl-10 text-sm leading-tight font-medium\">\n\t\t\t\t\t\t<div className=\"flex h-14 items-center justify-between gap-x-2 py-2 whitespace-nowrap\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-start gap-x-2 uppercase\">\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\tto={getExercisePath(data.exerciseStepApp.exerciseNumber)}\n\t\t\t\t\t\t\t\t\tclassName=\"hover:underline\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span>{titleBits.exerciseNumber}.</span>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden @min-[500px]:inline\">\n\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t{titleBits.exerciseTitle}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t<span>/</span>\n\t\t\t\t\t\t\t\t<Link to=\".\" className=\"hover:underline\">\n\t\t\t\t\t\t\t\t\t<span>{titleBits.stepNumber}.</span>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden @min-[300px]:inline\">\n\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t{titleBits.title}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<span> ({titleBits.emoji}</span>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden @min-[400px]:inline\">\n\t\t\t\t\t\t\t\t\t\t{' '}\n\t\t\t\t\t\t\t\t\t\t{titleBits.type}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t<span>)</span>\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t{data.problem &&\n\t\t\t\t\t\t\t(data.playground?.appName !== data.problem.name ||\n\t\t\t\t\t\t\t\t!data.playground?.isUpToDate) ? (\n\t\t\t\t\t\t\t\t<SetAppToPlayground\n\t\t\t\t\t\t\t\t\tappName={data.problem.name}\n\t\t\t\t\t\t\t\t\tisOutdated={data.playground?.isUpToDate === false}\n\t\t\t\t\t\t\t\t\thideTextOnNarrow\n\t\t\t\t\t\t\t\t\tshowOnboardingIndicator={showPlaygroundIndicator}\n\t\t\t\t\t\t\t\t\tdata-keyboard-action=\"s+p\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</h1>\n\t\t\t\t\t<article\n\t\t\t\t\t\tid={data.articleId}\n\t\t\t\t\t\tkey={data.articleId}\n\t\t\t\t\t\tclassName=\"shadow-on-scrollbox scrollbar-thin scrollbar-thumb-scrollbar flex w-full max-w-none scroll-pt-6 flex-col justify-between space-y-6 p-2 sm:p-10 sm:pt-8 lg:h-full lg:flex-1 lg:overflow-y-auto\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{data.exerciseStepApp.instructionsCode ? (\n\t\t\t\t\t\t\t<StepMdx inBrowserBrowserRef={inBrowserBrowserRef} />\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<div className=\"flex h-full items-center justify-center text-lg\">\n\t\t\t\t\t\t\t\t<p>No instructions yet...</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t<div className=\"mt-auto flex justify-between\">\n\t\t\t\t\t\t\t{data.prevStepLink ? (\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\tto={data.prevStepLink.to}\n\t\t\t\t\t\t\t\t\taria-label=\"Previous Step\"\n\t\t\t\t\t\t\t\t\tdata-keyboard-action=\"g+p\"\n\t\t\t\t\t\t\t\t\tprefetch=\"intent\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span aria-hidden>←</span>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden xl:inline\"> Previous</span>\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span />\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{data.nextStepLink ? (\n\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\tto={data.nextStepLink.to}\n\t\t\t\t\t\t\t\t\taria-label=\"Next Step\"\n\t\t\t\t\t\t\t\t\tdata-keyboard-action=\"g+n\"\n\t\t\t\t\t\t\t\t\tprefetch=\"intent\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span className=\"hidden xl:inline\">Next </span>\n\t\t\t\t\t\t\t\t\t<span aria-hidden>→</span>\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span />\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</article>\n\t\t\t\t\t<ElementScrollRestoration\n\t\t\t\t\t\telementQuery={`#${data.articleId}`}\n\t\t\t\t\t\tkey={`scroll-${data.articleId}`}\n\t\t\t\t\t/>\n\t\t\t\t\t{data.type === 'solution' ? (\n\t\t\t\t\t\t<ProgressToggle\n\t\t\t\t\t\t\ttype=\"step\"\n\t\t\t\t\t\t\texerciseNumber={data.exerciseStepApp.exerciseNumber}\n\t\t\t\t\t\t\tstepNumber={data.exerciseStepApp.stepNumber}\n\t\t\t\t\t\t\tclassName=\"h-14 border-t px-6\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<div className=\"@container flex h-16 justify-between border-t border-b-4 lg:border-b-0\">\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<div className=\"h-full\">\n\t\t\t\t\t\t\t\t<TouchedFiles diffFilesPromise={data.diffFiles} />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<EditFileOnGitHub\n\t\t\t\t\t\t\tappName={data.exerciseStepApp.name}\n\t\t\t\t\t\t\trelativePath={`${data.exerciseStepApp.relativePath}/README.mdx`}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<NavChevrons\n\t\t\t\t\t\t\tprev={\n\t\t\t\t\t\t\t\tdata.prevStepLink\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tto: data.prevStepLink.to,\n\t\t\t\t\t\t\t\t\t\t\t'aria-label': 'Previous Step',\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnext={\n\t\t\t\t\t\t\t\tdata.nextStepLink\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tto: data.nextStepLink.to,\n\t\t\t\t\t\t\t\t\t\t\t'aria-label': 'Next Step',\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\trole=\"separator\"\n\t\t\t\t\taria-orientation=\"vertical\"\n\t\t\t\t\ttitle=\"Drag to resize\"\n\t\t\t\t\tclassName=\"bg-border hover:bg-accent hidden w-1 cursor-col-resize lg:block\"\n\t\t\t\t\tonMouseDown={(event) =>\n\t\t\t\t\t\tstartSplitDrag({\n\t\t\t\t\t\t\tcontainer: containerRef.current,\n\t\t\t\t\t\t\tinitialClientX: event.clientX,\n\t\t\t\t\t\t\tsetSplitPercent,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tonDoubleClick={() => {\n\t\t\t\t\t\tsetSplitPercent(setSplitPercentCookie(50))\n\t\t\t\t\t}}\n\t\t\t\t\tonTouchStart={(event) => {\n\t\t\t\t\t\tconst firstTouch = event.touches?.[0]\n\t\t\t\t\t\tif (!firstTouch) return\n\t\t\t\t\t\tstartSplitDrag({\n\t\t\t\t\t\t\tcontainer: containerRef.current,\n\t\t\t\t\t\t\tinitialClientX: firstTouch.clientX,\n\t\t\t\t\t\t\tsetSplitPercent,\n\t\t\t\t\t\t})\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t<div className=\"flex min-h-[50vh] min-w-0 flex-none lg:min-h-0 lg:flex-1\">\n\t\t\t\t\t<Outlet context={{ inBrowserBrowserRef }} />\n\t\t\t\t</div>\n\t\t\t</main>\n\t\t</div>\n\t)\n}\n\nexport function ErrorBoundary() {\n\treturn (\n\t\t<GeneralErrorBoundary\n\t\t\tclassName=\"container flex items-center justify-center\"\n\t\t\tstatusHandlers={{\n\t\t\t\t404: Exercise404ErrorBoundary,\n\t\t\t}}\n\t\t/>\n\t)\n}\n"],"names":["StepContext","React.createContext","useStepContext","context","React.useContext","StepContextProvider","children","inBrowserBrowserRef","stepMdxComponents","DiffLink","PrevDiffLink","NextDiffLink","InlineFile","LinkToApp","StepMdx","data","useLoaderData","jsx","EpicVideoInfoProvider","Mdx","withParam","searchParams","key","value","newSearchParams","app","fullPage","app1","app2","to","getAppName","input","stepIndex","name","stepName","params","app1Name","app2Name","jsxs","pathToDiff","Link","file","type","props","expectedPlaygroundAppName","isPlaygroundReady","disabledReason","info","iconsSvg","SimpleTooltip","LaunchEditor","getPreviewType","preview","appTo","useSearchParams","requestInfo","useRequestInfo","previewAppUrl","getBaseUrl","href","cn","event","Icon","TouchedFiles","diffFilesPromise","showFilesBadge","dismissFilesBadge","useOnboardingIndicator","open","setOpen","React.useState","contentRef","React.useRef","handleOpenChange","isOpen","handleLaunchUpdate","appName","Popover.Root","Popover.Trigger","OnboardingBadge","Popover.Portal","Popover.Content","SetAppToPlayground","React.Suspense","Await","diffFiles","pageTitle","workshopTitle","exerciseNumber","exerciseStepApp","toString","padStart","stepNumber","emoji","problem","solution","title","exerciseTitle","meta","loaderData","matches","rootData","getRootMatchLoaderData","getSeoMetaTags","description","ogTitle","ogDescription","Number","instructor","_layout","_UNSAFE_withComponentProps","apps","useApps","useRef","containerRef","leftPaneRef","splitPercent","setSplitPercent","useState","titleBits","playgroundBasePath","find","playground","fullPath","useRevalidationWS","watchPaths","relativePath","filter","path","Boolean","showPlaygroundIndicator","className","ref","style","getExercisePath","isUpToDate","isOutdated","hideTextOnNarrow","showOnboardingIndicator","id","articleId","instructionsCode","prevStepLink","prefetch","nextStepLink","ElementScrollRestoration","elementQuery","ProgressToggle","EditFileOnGitHub","NavChevrons","prev","next","role","onMouseDown","startSplitDrag","container","current","initialClientX","clientX","onDoubleClick","setSplitPercentCookie","onTouchStart","firstTouch","touches","Outlet","ErrorBoundary","_UNSAFE_withErrorBoundaryProps","GeneralErrorBoundary","statusHandlers","Exercise404ErrorBoundary"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{w as t,b as p}from"./chunk-JZWAC4HX-BuixXc9D.js";import{j as i}from"./jsx-runtime-C5WNSv3b.js";import{r as m}from"./index-Bi6L2ga8.js";import{P as s}from"./preview-BS9wGiJ3.js";import"./mdx-Bvb9XNyL.js";import"./misc-D9k_mI0z.js";import"./index-B2VNpNxJ.js";import"./index-E_6kulJP.js";import"./epic-video-C9q0z-QM.js";import"./loading-BjV0PtX2.js";import"./index-DSpM0ReJ.js";import"./tooltip-H74tOFSr.js";import"./root-loader-DAxaiFMN.js";import"./pe-D585gnmh.js";import"./schemas-siASSt6b.js";import"./online-x7UFobEJ.js";import"./format-CZ5n8p10.js";import"./user-DhjrtSbZ.js";import"./workshop-config-v_LiyFvY.js";import"./preload-helper-BXl3LOEh.js";import"./launch-editor-C8OwTN5L.js";import"./progress-bar-BCbq1CuC.js";import"./popover-Dh41LPal.js";import"./index-ywSltzcE.js";import"./index-DosQrH9F.js";import"./button-B_EAwzD_.js";const z=t(function(){const{appInfo:r}=p(),o=m.useRef(null);return i.jsx(s,{appInfo:r,inBrowserBrowserRef:o})});export{z as default};
2
- //# sourceMappingURL=app-BH96aYJx.js.map
1
+ import{w as t,b as p}from"./chunk-JZWAC4HX-BuixXc9D.js";import{j as i}from"./jsx-runtime-C5WNSv3b.js";import{r as m}from"./index-Bi6L2ga8.js";import{P as s}from"./preview-Dav9KrHN.js";import"./mdx-C3Jl4O5D.js";import"./misc-D9k_mI0z.js";import"./index-B2VNpNxJ.js";import"./index-E_6kulJP.js";import"./epic-video-C9q0z-QM.js";import"./loading-BjV0PtX2.js";import"./index-DSpM0ReJ.js";import"./tooltip-H74tOFSr.js";import"./root-loader-DAxaiFMN.js";import"./pe-D585gnmh.js";import"./schemas-siASSt6b.js";import"./online-x7UFobEJ.js";import"./format-CZ5n8p10.js";import"./user-DhjrtSbZ.js";import"./workshop-config-v_LiyFvY.js";import"./preload-helper-BXl3LOEh.js";import"./launch-editor-C-8ah8qM.js";import"./progress-bar-BCbq1CuC.js";import"./popover-Dh41LPal.js";import"./index-ywSltzcE.js";import"./index-DosQrH9F.js";import"./button-B_EAwzD_.js";const z=t(function(){const{appInfo:r}=p(),o=m.useRef(null);return i.jsx(s,{appInfo:r,inBrowserBrowserRef:o})});export{z as default};
2
+ //# sourceMappingURL=app-BTlBQWy_.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-BH96aYJx.js","sources":["../../../app/routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/app.tsx"],"sourcesContent":["import { type Route } from './+types/app'\nimport { requireExerciseApp } from '@epic-web/workshop-utils/apps.server'\nimport {\n\tcombineServerTimings,\n\tmakeTimings,\n} from '@epic-web/workshop-utils/timing.server'\nimport { useRef } from 'react'\nimport { data, type HeadersFunction, useLoaderData } from 'react-router'\nimport { type InBrowserBrowserRef } from '#app/components/in-browser-browser.ts'\nimport { Preview } from './__shared/preview.tsx'\nimport { getAppRunningState } from './__shared/utils.tsx'\n\nexport async function loader({ request, params }: Route.LoaderArgs) {\n\tconst timings = makeTimings('exercise-step-test')\n\tconst exerciseStepApp = await requireExerciseApp(params, { request, timings })\n\tconst { isRunning, portIsAvailable } =\n\t\tawait getAppRunningState(exerciseStepApp)\n\n\treturn data(\n\t\t{\n\t\t\tappInfo: {\n\t\t\t\tisRunning,\n\t\t\t\tname: exerciseStepApp.name,\n\t\t\t\ttitle: exerciseStepApp.title,\n\t\t\t\tportIsAvailable,\n\t\t\t\ttype: exerciseStepApp.type,\n\t\t\t\tfullPath: exerciseStepApp.fullPath,\n\t\t\t\tdev: exerciseStepApp.dev,\n\t\t\t\ttest: exerciseStepApp.test,\n\t\t\t\tstackBlitzUrl: exerciseStepApp.stackBlitzUrl,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\theaders: {\n\t\t\t\t'Server-Timing': timings.toString(),\n\t\t\t},\n\t\t},\n\t)\n}\n\nexport const headers: HeadersFunction = ({ loaderHeaders, parentHeaders }) => {\n\tconst headers = {\n\t\t'Server-Timing': combineServerTimings(loaderHeaders, parentHeaders),\n\t}\n\treturn headers\n}\n\nexport default function TestsList() {\n\tconst { appInfo } = useLoaderData<typeof loader>()\n\tconst ref = useRef<InBrowserBrowserRef>(null)\n\n\treturn <Preview appInfo={appInfo} inBrowserBrowserRef={ref} />\n}\n"],"names":["app","_UNSAFE_withComponentProps","appInfo","useLoaderData","ref","useRef","jsx","Preview","inBrowserBrowserRef"],"mappings":"i1BA+CA,MAAAA,EAAAC,EAAA,UAAoC,CACnC,KAAM,CAAEC,QAAAA,GAAYC,EAAA,EACdC,EAAMC,EAAAA,OAA4B,IAAI,EAE5C,OAAOC,EAAAA,IAACC,EAAA,CAAQL,QAAAA,EAAkBM,oBAAqBJ,CAAA,CAAK,CAC7D,CAAA"}
1
+ {"version":3,"file":"app-BTlBQWy_.js","sources":["../../../app/routes/_app+/exercise+/$exerciseNumber_.$stepNumber.$type+/app.tsx"],"sourcesContent":["import { type Route } from './+types/app'\nimport { requireExerciseApp } from '@epic-web/workshop-utils/apps.server'\nimport {\n\tcombineServerTimings,\n\tmakeTimings,\n} from '@epic-web/workshop-utils/timing.server'\nimport { useRef } from 'react'\nimport { data, type HeadersFunction, useLoaderData } from 'react-router'\nimport { type InBrowserBrowserRef } from '#app/components/in-browser-browser.ts'\nimport { Preview } from './__shared/preview.tsx'\nimport { getAppRunningState } from './__shared/utils.tsx'\n\nexport async function loader({ request, params }: Route.LoaderArgs) {\n\tconst timings = makeTimings('exercise-step-test')\n\tconst exerciseStepApp = await requireExerciseApp(params, { request, timings })\n\tconst { isRunning, portIsAvailable } =\n\t\tawait getAppRunningState(exerciseStepApp)\n\n\treturn data(\n\t\t{\n\t\t\tappInfo: {\n\t\t\t\tisRunning,\n\t\t\t\tname: exerciseStepApp.name,\n\t\t\t\ttitle: exerciseStepApp.title,\n\t\t\t\tportIsAvailable,\n\t\t\t\ttype: exerciseStepApp.type,\n\t\t\t\tfullPath: exerciseStepApp.fullPath,\n\t\t\t\tdev: exerciseStepApp.dev,\n\t\t\t\ttest: exerciseStepApp.test,\n\t\t\t\tstackBlitzUrl: exerciseStepApp.stackBlitzUrl,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\theaders: {\n\t\t\t\t'Server-Timing': timings.toString(),\n\t\t\t},\n\t\t},\n\t)\n}\n\nexport const headers: HeadersFunction = ({ loaderHeaders, parentHeaders }) => {\n\tconst headers = {\n\t\t'Server-Timing': combineServerTimings(loaderHeaders, parentHeaders),\n\t}\n\treturn headers\n}\n\nexport default function TestsList() {\n\tconst { appInfo } = useLoaderData<typeof loader>()\n\tconst ref = useRef<InBrowserBrowserRef>(null)\n\n\treturn <Preview appInfo={appInfo} inBrowserBrowserRef={ref} />\n}\n"],"names":["app","_UNSAFE_withComponentProps","appInfo","useLoaderData","ref","useRef","jsx","Preview","inBrowserBrowserRef"],"mappings":"i1BA+CA,MAAAA,EAAAC,EAAA,UAAoC,CACnC,KAAM,CAAEC,QAAAA,GAAYC,EAAA,EACdC,EAAMC,EAAAA,OAA4B,IAAI,EAE5C,OAAOC,EAAAA,IAACC,EAAA,CAAQL,QAAAA,EAAkBM,oBAAqBJ,CAAA,CAAK,CAC7D,CAAA"}
@@ -1,2 +1,2 @@
1
- import{h as $}from"./chunk-4LKRSAEJ-TFLSCavK.js";import{w as W,f as D,d as M}from"./chunk-JZWAC4HX-BuixXc9D.js";import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{r as u}from"./index-Bi6L2ga8.js";import{C as F}from"./client-only-C0tXiQt8.js";import{i as U,I as V,B as T}from"./button-B_EAwzD_.js";import{c as I,I as p,f as R,e as q,h as J,i as K,b as A,j as P,k as _}from"./misc-D9k_mI0z.js";import{L as G}from"./launch-editor-C8OwTN5L.js";import"./pe-D585gnmh.js";import"./progress-bar-BCbq1CuC.js";import"./root-loader-DAxaiFMN.js";function X({selectedWorkshops:r,availableWorkshops:m,currentWorkshopId:a}){const[o,s]=M(),i=(t,x)=>{const d=x?[...r,t]:r.filter(g=>g!==t),c=new URLSearchParams(o);d.length>0?c.set("workshops",d.join(",")):c.delete("workshops"),s(c)};return e.jsxs("div",{className:"mb-6",children:[e.jsx("h3",{className:"mb-3 text-lg font-semibold",children:"Workshop Filter"}),e.jsx("div",{className:"flex flex-wrap gap-3",children:m.map(t=>{const x=t.displayName,d=t.shortId,c=t.repoName??t.subtitle,g=t.hasStoredDisplayName,j=g&&t.id!=="global";return e.jsxs("label",{className:"flex items-center gap-2",title:c?`${x} (${c})`:x,children:[e.jsx("input",{type:"checkbox",checked:r.includes(t.id),onChange:b=>i(t.id,b.target.checked),className:"rounded"}),e.jsxs("span",{className:I("text-sm",t.id===a?"text-primary font-bold":null),children:[e.jsxs("span",{className:"inline-flex max-w-[22rem] flex-col leading-tight",children:[e.jsx("span",{className:I(g?"truncate":"font-mono text-xs break-all"),children:x}),j?e.jsxs("span",{className:I("text-muted-foreground font-mono text-xs font-normal",t.id===a?"text-primary/80":null),title:t.id,children:[c?`${c} • `:null,d]}):null]})," ",t.id===a?"(current)":null]})]},t.id)})})]})}function Z({filterQuery:r}){const[m,a]=M(),[o,s]=u.useState(r),i=u.useRef(null);u.useEffect(()=>{s(r)},[r]);const t=d=>{const c=new URLSearchParams(m);d?c.set("q",d):c.delete("q"),a(c)},x=()=>{s(""),t(""),i.current?.focus()};return e.jsxs("div",{className:"mb-6",children:[e.jsx("h3",{className:"mb-3 text-lg font-semibold",children:"Search Cache Entries"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("input",{ref:i,type:"text",placeholder:"Search by key, value, cache name, or workshop...",value:o,onChange:d=>{s(d.target.value),t(d.target.value)},className:"border-border bg-background text-foreground focus:ring-ring flex-1 rounded-md border px-3 py-2 focus:ring-2 focus:outline-none"}),o?e.jsx(V,{onClick:x,title:"Clear search",children:e.jsx(p,{name:"Close",className:"h-4 w-4"})}):null]})]})}function Q({workshopId:r,cacheName:m,filename:a,entryKey:o}){const s=D(),i=D(),[t,x]=u.useState(null),[d,c]=u.useState(null),[g,j]=u.useState(!1),[b,n]=u.useState(!1),[l,w]=u.useState(!1),v=u.useRef(null),S=u.useRef(i.state),f=`${r}/${m}/${a}`,N=s.data?.entry?.value,k=s.state==="loading",y=!!s.data?.entry,E=s.data?.entry===null,L=b&&l&&!k&&s.data===void 0&&s.state==="idle";u.useEffect(()=>{s.state!=="idle"&&w(!0)},[s.state]),u.useEffect(()=>{if(y){const h=JSON.stringify(N,null,2)??"";x(h),c(h),j(!1)}},[y,N]),u.useEffect(()=>{const h=i.state;S.current!=="idle"&&h==="idle"&&i.data?.status==="success"&&(c(v.current??""),j(!1)),S.current=h},[i.state,i.data?.status]);const O=()=>{t!==null&&(v.current=t,i.submit({intent:"update-entry",workshopId:r,cacheName:m,filename:a,newValue:t},{method:"POST"}))},B=h=>{if(x(h),d===null){j(!1);return}j(h!==d)},z=()=>{d!==null&&(x(d),j(!1))},Y=h=>{h.currentTarget.open&&!b?(s.load($("/admin/cache/*",{"*":f})),n(!0)):h.currentTarget.open||(n(!1),w(!1))},H=()=>{s.load($("/admin/cache/*",{"*":f}))};return e.jsxs("details",{className:"mt-2",onToggle:Y,children:[e.jsx("summary",{className:"text-muted-foreground hover:text-foreground cursor-pointer text-sm",children:"Edit entry details"}),e.jsx("div",{className:"border-border bg-muted mt-2 space-y-3 rounded border p-3",children:k?e.jsx("p",{className:"text-muted-foreground text-sm",children:"Loading entry..."}):L?e.jsxs("div",{className:"space-y-2",children:[e.jsx("p",{className:"text-destructive text-sm",children:"Failed to load entry details."}),e.jsx(T,{varient:"mono",onClick:H,children:"Retry"})]}):E?e.jsx("p",{className:"text-destructive text-sm",children:"Entry details were not found."}):y?e.jsxs(e.Fragment,{children:[e.jsxs("div",{children:[e.jsx("label",{className:"mb-1 block text-sm font-medium",children:"Key:"}),e.jsx("code",{className:"bg-background rounded border px-2 py-1 text-sm",children:o})]}),e.jsxs("div",{children:[e.jsx("label",{className:"mb-1 block text-sm font-medium",children:"Value:"}),e.jsx("textarea",{value:t??"",onChange:h=>B(h.target.value),className:"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",placeholder:"Enter JSON value..."})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(T,{varient:"primary",onClick:O,disabled:!g||i.state!=="idle",children:i.state!=="idle"?"Saving...":"Save"}),e.jsx(T,{varient:"mono",onClick:z,disabled:!g,children:"Reset"})]})]}):e.jsx("p",{className:"text-muted-foreground text-sm",children:"Open to load entry details."})})]})}function ee({skippedFiles:r,workshopId:m,cacheName:a}){const o=D();return r.length===0?null:e.jsxs("div",{className:"border-warning bg-warning mt-4 rounded border p-3",children:[e.jsxs("div",{className:"mb-2 flex items-center gap-2",children:[e.jsx(p,{name:"TriangleAlert",className:"text-warning-foreground h-4 w-4"}),e.jsxs("h5",{className:"text-warning-foreground font-medium",children:["Skipped Files (",r.length,")"]})]}),e.jsx("p",{className:"text-warning-foreground/80 mb-3 text-sm",children:"These cache files were skipped because they exceed the 3MB size limit:"}),e.jsx("div",{className:"space-y-2",children:r.map(s=>e.jsxs("div",{className:"border-warning/20 bg-warning/5 flex items-center justify-between rounded border p-2",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsx("div",{className:"text-warning-foreground truncate font-mono text-sm font-medium",children:s.filename}),e.jsxs("div",{className:"text-warning-foreground/70 text-xs",children:[s.error," • Size:"," ",e.jsx("span",{title:`${s.size} bytes`,children:R(s.size)})]})]}),e.jsx("div",{className:"ml-2 flex shrink-0",children:e.jsx(C,{onConfirm:()=>{o.submit({intent:"delete-entry",workshopId:m,cacheName:a,filename:s.filename},{method:"POST"})},title:"Delete large cache file",className:"text-destructive-foreground hover:bg-destructive/20 hover:text-destructive-foreground",children:e.jsx(p,{name:"Remove",className:"h-4 w-4"})})})]},s.filename))})]})}const fe=W(function({loaderData:m}){const a=D(),o=(n,l,w)=>{a.submit({intent:"delete-entry",workshopId:n,cacheName:l,filename:w},{method:"POST"})},s=(n,l)=>{a.submit({intent:"delete-cache",workshopId:n,cacheName:l},{method:"POST"})},i=n=>{a.submit({intent:"delete-workshop-cache",workshopId:n},{method:"POST"})},{currentWorkshopId:t,currentWorkshop:x,filteredCaches:d,filterQuery:c,selectedWorkshops:g,availableWorkshops:j}=m,b=x;return e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"mb-2 text-2xl font-bold",children:"Cache Management"}),e.jsxs("p",{className:"text-muted-foreground",children:["Current Workshop:"," ",e.jsx("span",{className:"text-foreground font-semibold",children:e.jsx("span",{className:I(b?.hasStoredDisplayName?null:"font-mono text-xs"),children:b.displayName??t})})]}),b?.hasStoredDisplayName?e.jsxs("p",{className:"text-muted-foreground mt-1 text-xs",children:["Instance ID:"," ",e.jsx("span",{className:"font-mono",title:t,children:b.shortId})]}):null]}),e.jsx(X,{selectedWorkshops:g,availableWorkshops:j,currentWorkshopId:t}),e.jsx(Z,{filterQuery:c}),a.data?.status==="success"?e.jsx("div",{className:"border-border bg-success text-success-foreground rounded border p-4",children:a.data.message}):null,a.data?.status==="error"?e.jsx("div",{className:"border-border bg-destructive text-destructive-foreground rounded border p-4",children:a.data.error}):null,d.length===0?e.jsx("div",{className:"text-muted-foreground py-8 text-center",children:"No caches found matching your criteria."}):null,e.jsx("div",{className:"space-y-6",children:d.map(n=>e.jsxs("details",{open:n.workshopId===t,children:[e.jsx("summary",{className:"border-border bg-card hover:bg-accent cursor-pointer rounded-lg border p-4",children:e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("h3",{className:"text-card-foreground flex min-w-0 items-center gap-2 text-lg font-semibold",children:[e.jsx(p,{name:"Files",className:"h-5 w-5 shrink-0"}),e.jsx("span",{className:I("min-w-0",n.workshop.hasStoredDisplayName?"truncate":"font-mono text-xs break-all"),title:n.workshopId,children:n.workshop.displayName}),n.workshopId===t?e.jsx("span",{className:"bg-primary text-primary-foreground rounded px-2 py-1 text-xs",children:"Current"}):null]}),n.workshop.hasStoredDisplayName&&n.workshopId!=="global"?e.jsxs("div",{className:"text-muted-foreground mt-1 flex flex-wrap items-center gap-2 text-xs",children:[n.workshop.repoName?e.jsx("span",{className:"truncate",children:n.workshop.repoName}):n.workshop.subtitle?e.jsx("span",{className:"truncate",children:n.workshop.subtitle}):null,e.jsx("span",{className:"font-mono",title:n.workshopId,children:n.workshop.shortId})]}):null]}),e.jsx(C,{onConfirm:()=>i(n.workshopId),title:"Delete all workshop caches",children:e.jsx(p,{name:"Remove",className:"h-4 w-4"})})]})}),e.jsx("div",{className:"mt-4 space-y-4 pl-4",children:n.caches.map(l=>{const w=l.entries.reduce((f,N)=>f+(N.size||0),0),v=(l.skippedFiles||[]).reduce((f,N)=>f+N.size,0),S=w+v;return e.jsxs("details",{className:"bg-muted rounded-md",children:[e.jsx("summary",{className:"hover:bg-accent cursor-pointer p-3",children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("h4",{className:"text-muted-foreground flex items-center gap-2 font-medium",children:[e.jsx(p,{name:"Files",className:"h-4 w-4"}),l.name,e.jsxs("span",{className:"text-sm",children:["(",l.entries.length," entr",l.entries.length===1?"y":"ies",")"]}),S>0?e.jsxs("span",{className:"text-muted-foreground text-sm",children:["•"," ",e.jsx("span",{title:`${S} bytes`,children:R(S)})," ","total",v>0?e.jsxs("span",{className:"text-warning",children:[" ","(",e.jsx("span",{title:`${v} bytes`,children:R(v)})," ","skipped)"]}):null]}):null]}),e.jsx(C,{onConfirm:()=>s(n.workshopId,l.name),title:"Delete cache",children:e.jsx(p,{name:"Remove",className:"h-4 w-4"})})]})}),e.jsxs("div",{className:"p-3 pt-0",children:[l.entries.length===0?e.jsx("p",{className:"text-muted-foreground text-sm",children:"No entries match your search."}):null,l.skippedFiles&&l.skippedFiles.length>0?e.jsx(ee,{skippedFiles:l.skippedFiles,workshopId:n.workshopId,cacheName:l.name}):null,e.jsx("div",{className:"space-y-2",children:l.entries.map(({key:f,metadata:N,filename:k,size:y,filepath:E})=>e.jsxs("div",{className:"border-border bg-background rounded border p-3",children:[e.jsxs("div",{className:"flex items-start justify-between",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"mb-1 flex items-center gap-2",children:[e.jsx("div",{className:"truncate font-mono text-sm font-medium",title:f,children:f}),y?e.jsx("span",{className:"bg-muted text-muted-foreground inline-flex items-center rounded px-1.5 py-0.5 text-xs whitespace-nowrap",title:`${y} bytes`,children:R(y)}):null]}),e.jsx(se,{metadata:N})]}),e.jsxs("div",{className:"ml-4 flex shrink-0 gap-1",children:[e.jsx("a",{href:$("/admin/cache/*",{"*":`${n.workshopId}/${l.name}/${k}`}),target:"_blank",rel:"noopener noreferrer",className:"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",title:"View JSON",children:e.jsx(p,{name:"ExternalLink",className:"h-4 w-4"})}),E?e.jsx(G,{file:E,className:U,children:e.jsx(p,{name:"Files",className:"h-4 w-4",title:"Open in editor"})}):null,e.jsx(C,{onConfirm:()=>o(n.workshopId,l.name,k),title:"Delete entry",children:e.jsx(p,{name:"Remove",className:"h-4 w-4"})})]})]}),e.jsx(Q,{workshopId:n.workshopId,cacheName:l.name,filename:k,entryKey:f})]},f))})]})]},l.name)})})]},n.workshopId))})]})});function se({metadata:r}){const m=J(),[,a]=u.useState(Date.now()),o=K(r);A(()=>{a(Date.now())},1e3);const s=m(r.createdTime),i=o?_(o):{text:"Never",isExpired:!1,isExpiringSoon:!1};return e.jsxs("div",{className:"text-muted-foreground flex flex-col gap-1 text-xs",children:[e.jsxs("div",{children:["Created: ",s.format("MMM D, YYYY HH:mm:ss")," ",e.jsx(F,{children:()=>`(${s.fromNow()})`})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[r.ttl!==void 0&&r.ttl!==null?e.jsxs("span",{children:["TTL:"," ",r.ttl===1/0?"Forever":e.jsx("span",{title:`${r.ttl}ms`,children:P(r.ttl)})]}):null,r.swr!==void 0?e.jsxs("span",{children:["SWR:"," ",e.jsx("span",{title:`${r.swr}ms`,children:P(r.swr)})]}):null,e.jsx("div",{className:`inline-flex w-auto rounded-full px-2 py-[2px] font-medium ${i.isExpired?"bg-destructive text-destructive-foreground":i.isExpiringSoon?"bg-warning text-warning-foreground":"text-foreground"}`,children:o?e.jsxs(e.Fragment,{children:["Expires: ",m(o).format("MMM D, YYYY HH:mm:ss")," (",e.jsx("span",{className:"tabular-nums",children:e.jsx(F,{children:()=>i.text})}),")"]}):"Expires: Never"})]})]})}function C({onConfirm:r,children:m,className:a,...o}){const s=q();return e.jsx(V,{...s.getButtonProps({onClick:s.doubleCheck?r:void 0,...o}),className:I(s.doubleCheck?"bg-destructive text-destructive-foreground":null,a),children:s.doubleCheck?"✓":m})}export{fe as default};
2
- //# sourceMappingURL=cache-5tXauWqp.js.map
1
+ import{h as $}from"./chunk-4LKRSAEJ-TFLSCavK.js";import{w as W,f as D,d as M}from"./chunk-JZWAC4HX-BuixXc9D.js";import{j as e}from"./jsx-runtime-C5WNSv3b.js";import{r as u}from"./index-Bi6L2ga8.js";import{C as F}from"./client-only-C0tXiQt8.js";import{i as U,I as V,B as T}from"./button-B_EAwzD_.js";import{c as I,I as p,f as R,e as q,h as J,i as K,b as A,j as P,k as _}from"./misc-D9k_mI0z.js";import{L as G}from"./launch-editor-C-8ah8qM.js";import"./pe-D585gnmh.js";import"./progress-bar-BCbq1CuC.js";import"./root-loader-DAxaiFMN.js";function X({selectedWorkshops:r,availableWorkshops:m,currentWorkshopId:a}){const[o,s]=M(),i=(t,x)=>{const d=x?[...r,t]:r.filter(g=>g!==t),c=new URLSearchParams(o);d.length>0?c.set("workshops",d.join(",")):c.delete("workshops"),s(c)};return e.jsxs("div",{className:"mb-6",children:[e.jsx("h3",{className:"mb-3 text-lg font-semibold",children:"Workshop Filter"}),e.jsx("div",{className:"flex flex-wrap gap-3",children:m.map(t=>{const x=t.displayName,d=t.shortId,c=t.repoName??t.subtitle,g=t.hasStoredDisplayName,j=g&&t.id!=="global";return e.jsxs("label",{className:"flex items-center gap-2",title:c?`${x} (${c})`:x,children:[e.jsx("input",{type:"checkbox",checked:r.includes(t.id),onChange:b=>i(t.id,b.target.checked),className:"rounded"}),e.jsxs("span",{className:I("text-sm",t.id===a?"text-primary font-bold":null),children:[e.jsxs("span",{className:"inline-flex max-w-[22rem] flex-col leading-tight",children:[e.jsx("span",{className:I(g?"truncate":"font-mono text-xs break-all"),children:x}),j?e.jsxs("span",{className:I("text-muted-foreground font-mono text-xs font-normal",t.id===a?"text-primary/80":null),title:t.id,children:[c?`${c} • `:null,d]}):null]})," ",t.id===a?"(current)":null]})]},t.id)})})]})}function Z({filterQuery:r}){const[m,a]=M(),[o,s]=u.useState(r),i=u.useRef(null);u.useEffect(()=>{s(r)},[r]);const t=d=>{const c=new URLSearchParams(m);d?c.set("q",d):c.delete("q"),a(c)},x=()=>{s(""),t(""),i.current?.focus()};return e.jsxs("div",{className:"mb-6",children:[e.jsx("h3",{className:"mb-3 text-lg font-semibold",children:"Search Cache Entries"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("input",{ref:i,type:"text",placeholder:"Search by key, value, cache name, or workshop...",value:o,onChange:d=>{s(d.target.value),t(d.target.value)},className:"border-border bg-background text-foreground focus:ring-ring flex-1 rounded-md border px-3 py-2 focus:ring-2 focus:outline-none"}),o?e.jsx(V,{onClick:x,title:"Clear search",children:e.jsx(p,{name:"Close",className:"h-4 w-4"})}):null]})]})}function Q({workshopId:r,cacheName:m,filename:a,entryKey:o}){const s=D(),i=D(),[t,x]=u.useState(null),[d,c]=u.useState(null),[g,j]=u.useState(!1),[b,n]=u.useState(!1),[l,w]=u.useState(!1),v=u.useRef(null),S=u.useRef(i.state),f=`${r}/${m}/${a}`,N=s.data?.entry?.value,k=s.state==="loading",y=!!s.data?.entry,E=s.data?.entry===null,L=b&&l&&!k&&s.data===void 0&&s.state==="idle";u.useEffect(()=>{s.state!=="idle"&&w(!0)},[s.state]),u.useEffect(()=>{if(y){const h=JSON.stringify(N,null,2)??"";x(h),c(h),j(!1)}},[y,N]),u.useEffect(()=>{const h=i.state;S.current!=="idle"&&h==="idle"&&i.data?.status==="success"&&(c(v.current??""),j(!1)),S.current=h},[i.state,i.data?.status]);const O=()=>{t!==null&&(v.current=t,i.submit({intent:"update-entry",workshopId:r,cacheName:m,filename:a,newValue:t},{method:"POST"}))},B=h=>{if(x(h),d===null){j(!1);return}j(h!==d)},z=()=>{d!==null&&(x(d),j(!1))},Y=h=>{h.currentTarget.open&&!b?(s.load($("/admin/cache/*",{"*":f})),n(!0)):h.currentTarget.open||(n(!1),w(!1))},H=()=>{s.load($("/admin/cache/*",{"*":f}))};return e.jsxs("details",{className:"mt-2",onToggle:Y,children:[e.jsx("summary",{className:"text-muted-foreground hover:text-foreground cursor-pointer text-sm",children:"Edit entry details"}),e.jsx("div",{className:"border-border bg-muted mt-2 space-y-3 rounded border p-3",children:k?e.jsx("p",{className:"text-muted-foreground text-sm",children:"Loading entry..."}):L?e.jsxs("div",{className:"space-y-2",children:[e.jsx("p",{className:"text-destructive text-sm",children:"Failed to load entry details."}),e.jsx(T,{varient:"mono",onClick:H,children:"Retry"})]}):E?e.jsx("p",{className:"text-destructive text-sm",children:"Entry details were not found."}):y?e.jsxs(e.Fragment,{children:[e.jsxs("div",{children:[e.jsx("label",{className:"mb-1 block text-sm font-medium",children:"Key:"}),e.jsx("code",{className:"bg-background rounded border px-2 py-1 text-sm",children:o})]}),e.jsxs("div",{children:[e.jsx("label",{className:"mb-1 block text-sm font-medium",children:"Value:"}),e.jsx("textarea",{value:t??"",onChange:h=>B(h.target.value),className:"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",placeholder:"Enter JSON value..."})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(T,{varient:"primary",onClick:O,disabled:!g||i.state!=="idle",children:i.state!=="idle"?"Saving...":"Save"}),e.jsx(T,{varient:"mono",onClick:z,disabled:!g,children:"Reset"})]})]}):e.jsx("p",{className:"text-muted-foreground text-sm",children:"Open to load entry details."})})]})}function ee({skippedFiles:r,workshopId:m,cacheName:a}){const o=D();return r.length===0?null:e.jsxs("div",{className:"border-warning bg-warning mt-4 rounded border p-3",children:[e.jsxs("div",{className:"mb-2 flex items-center gap-2",children:[e.jsx(p,{name:"TriangleAlert",className:"text-warning-foreground h-4 w-4"}),e.jsxs("h5",{className:"text-warning-foreground font-medium",children:["Skipped Files (",r.length,")"]})]}),e.jsx("p",{className:"text-warning-foreground/80 mb-3 text-sm",children:"These cache files were skipped because they exceed the 3MB size limit:"}),e.jsx("div",{className:"space-y-2",children:r.map(s=>e.jsxs("div",{className:"border-warning/20 bg-warning/5 flex items-center justify-between rounded border p-2",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsx("div",{className:"text-warning-foreground truncate font-mono text-sm font-medium",children:s.filename}),e.jsxs("div",{className:"text-warning-foreground/70 text-xs",children:[s.error," • Size:"," ",e.jsx("span",{title:`${s.size} bytes`,children:R(s.size)})]})]}),e.jsx("div",{className:"ml-2 flex shrink-0",children:e.jsx(C,{onConfirm:()=>{o.submit({intent:"delete-entry",workshopId:m,cacheName:a,filename:s.filename},{method:"POST"})},title:"Delete large cache file",className:"text-destructive-foreground hover:bg-destructive/20 hover:text-destructive-foreground",children:e.jsx(p,{name:"Remove",className:"h-4 w-4"})})})]},s.filename))})]})}const fe=W(function({loaderData:m}){const a=D(),o=(n,l,w)=>{a.submit({intent:"delete-entry",workshopId:n,cacheName:l,filename:w},{method:"POST"})},s=(n,l)=>{a.submit({intent:"delete-cache",workshopId:n,cacheName:l},{method:"POST"})},i=n=>{a.submit({intent:"delete-workshop-cache",workshopId:n},{method:"POST"})},{currentWorkshopId:t,currentWorkshop:x,filteredCaches:d,filterQuery:c,selectedWorkshops:g,availableWorkshops:j}=m,b=x;return e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"mb-2 text-2xl font-bold",children:"Cache Management"}),e.jsxs("p",{className:"text-muted-foreground",children:["Current Workshop:"," ",e.jsx("span",{className:"text-foreground font-semibold",children:e.jsx("span",{className:I(b?.hasStoredDisplayName?null:"font-mono text-xs"),children:b.displayName??t})})]}),b?.hasStoredDisplayName?e.jsxs("p",{className:"text-muted-foreground mt-1 text-xs",children:["Instance ID:"," ",e.jsx("span",{className:"font-mono",title:t,children:b.shortId})]}):null]}),e.jsx(X,{selectedWorkshops:g,availableWorkshops:j,currentWorkshopId:t}),e.jsx(Z,{filterQuery:c}),a.data?.status==="success"?e.jsx("div",{className:"border-border bg-success text-success-foreground rounded border p-4",children:a.data.message}):null,a.data?.status==="error"?e.jsx("div",{className:"border-border bg-destructive text-destructive-foreground rounded border p-4",children:a.data.error}):null,d.length===0?e.jsx("div",{className:"text-muted-foreground py-8 text-center",children:"No caches found matching your criteria."}):null,e.jsx("div",{className:"space-y-6",children:d.map(n=>e.jsxs("details",{open:n.workshopId===t,children:[e.jsx("summary",{className:"border-border bg-card hover:bg-accent cursor-pointer rounded-lg border p-4",children:e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("h3",{className:"text-card-foreground flex min-w-0 items-center gap-2 text-lg font-semibold",children:[e.jsx(p,{name:"Files",className:"h-5 w-5 shrink-0"}),e.jsx("span",{className:I("min-w-0",n.workshop.hasStoredDisplayName?"truncate":"font-mono text-xs break-all"),title:n.workshopId,children:n.workshop.displayName}),n.workshopId===t?e.jsx("span",{className:"bg-primary text-primary-foreground rounded px-2 py-1 text-xs",children:"Current"}):null]}),n.workshop.hasStoredDisplayName&&n.workshopId!=="global"?e.jsxs("div",{className:"text-muted-foreground mt-1 flex flex-wrap items-center gap-2 text-xs",children:[n.workshop.repoName?e.jsx("span",{className:"truncate",children:n.workshop.repoName}):n.workshop.subtitle?e.jsx("span",{className:"truncate",children:n.workshop.subtitle}):null,e.jsx("span",{className:"font-mono",title:n.workshopId,children:n.workshop.shortId})]}):null]}),e.jsx(C,{onConfirm:()=>i(n.workshopId),title:"Delete all workshop caches",children:e.jsx(p,{name:"Remove",className:"h-4 w-4"})})]})}),e.jsx("div",{className:"mt-4 space-y-4 pl-4",children:n.caches.map(l=>{const w=l.entries.reduce((f,N)=>f+(N.size||0),0),v=(l.skippedFiles||[]).reduce((f,N)=>f+N.size,0),S=w+v;return e.jsxs("details",{className:"bg-muted rounded-md",children:[e.jsx("summary",{className:"hover:bg-accent cursor-pointer p-3",children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("h4",{className:"text-muted-foreground flex items-center gap-2 font-medium",children:[e.jsx(p,{name:"Files",className:"h-4 w-4"}),l.name,e.jsxs("span",{className:"text-sm",children:["(",l.entries.length," entr",l.entries.length===1?"y":"ies",")"]}),S>0?e.jsxs("span",{className:"text-muted-foreground text-sm",children:["•"," ",e.jsx("span",{title:`${S} bytes`,children:R(S)})," ","total",v>0?e.jsxs("span",{className:"text-warning",children:[" ","(",e.jsx("span",{title:`${v} bytes`,children:R(v)})," ","skipped)"]}):null]}):null]}),e.jsx(C,{onConfirm:()=>s(n.workshopId,l.name),title:"Delete cache",children:e.jsx(p,{name:"Remove",className:"h-4 w-4"})})]})}),e.jsxs("div",{className:"p-3 pt-0",children:[l.entries.length===0?e.jsx("p",{className:"text-muted-foreground text-sm",children:"No entries match your search."}):null,l.skippedFiles&&l.skippedFiles.length>0?e.jsx(ee,{skippedFiles:l.skippedFiles,workshopId:n.workshopId,cacheName:l.name}):null,e.jsx("div",{className:"space-y-2",children:l.entries.map(({key:f,metadata:N,filename:k,size:y,filepath:E})=>e.jsxs("div",{className:"border-border bg-background rounded border p-3",children:[e.jsxs("div",{className:"flex items-start justify-between",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"mb-1 flex items-center gap-2",children:[e.jsx("div",{className:"truncate font-mono text-sm font-medium",title:f,children:f}),y?e.jsx("span",{className:"bg-muted text-muted-foreground inline-flex items-center rounded px-1.5 py-0.5 text-xs whitespace-nowrap",title:`${y} bytes`,children:R(y)}):null]}),e.jsx(se,{metadata:N})]}),e.jsxs("div",{className:"ml-4 flex shrink-0 gap-1",children:[e.jsx("a",{href:$("/admin/cache/*",{"*":`${n.workshopId}/${l.name}/${k}`}),target:"_blank",rel:"noopener noreferrer",className:"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",title:"View JSON",children:e.jsx(p,{name:"ExternalLink",className:"h-4 w-4"})}),E?e.jsx(G,{file:E,className:U,children:e.jsx(p,{name:"Files",className:"h-4 w-4",title:"Open in editor"})}):null,e.jsx(C,{onConfirm:()=>o(n.workshopId,l.name,k),title:"Delete entry",children:e.jsx(p,{name:"Remove",className:"h-4 w-4"})})]})]}),e.jsx(Q,{workshopId:n.workshopId,cacheName:l.name,filename:k,entryKey:f})]},f))})]})]},l.name)})})]},n.workshopId))})]})});function se({metadata:r}){const m=J(),[,a]=u.useState(Date.now()),o=K(r);A(()=>{a(Date.now())},1e3);const s=m(r.createdTime),i=o?_(o):{text:"Never",isExpired:!1,isExpiringSoon:!1};return e.jsxs("div",{className:"text-muted-foreground flex flex-col gap-1 text-xs",children:[e.jsxs("div",{children:["Created: ",s.format("MMM D, YYYY HH:mm:ss")," ",e.jsx(F,{children:()=>`(${s.fromNow()})`})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[r.ttl!==void 0&&r.ttl!==null?e.jsxs("span",{children:["TTL:"," ",r.ttl===1/0?"Forever":e.jsx("span",{title:`${r.ttl}ms`,children:P(r.ttl)})]}):null,r.swr!==void 0?e.jsxs("span",{children:["SWR:"," ",e.jsx("span",{title:`${r.swr}ms`,children:P(r.swr)})]}):null,e.jsx("div",{className:`inline-flex w-auto rounded-full px-2 py-[2px] font-medium ${i.isExpired?"bg-destructive text-destructive-foreground":i.isExpiringSoon?"bg-warning text-warning-foreground":"text-foreground"}`,children:o?e.jsxs(e.Fragment,{children:["Expires: ",m(o).format("MMM D, YYYY HH:mm:ss")," (",e.jsx("span",{className:"tabular-nums",children:e.jsx(F,{children:()=>i.text})}),")"]}):"Expires: Never"})]})]})}function C({onConfirm:r,children:m,className:a,...o}){const s=q();return e.jsx(V,{...s.getButtonProps({onClick:s.doubleCheck?r:void 0,...o}),className:I(s.doubleCheck?"bg-destructive text-destructive-foreground":null,a),children:s.doubleCheck?"✓":m})}export{fe as default};
2
+ //# sourceMappingURL=cache-Cjjb4Th1.js.map