@tanstack/pacer-devtools
Advanced tools
| import { className, createComponent, delegateEvents, effect, insert, memo, setStyleProperty, style, template, use } from "solid-js/web"; | ||
| import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, useTheme } from "@tanstack/devtools-ui"; | ||
| import { createStore, produce } from "solid-js/store"; | ||
| import { For, Show, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js"; | ||
| import { getPacerDevtoolsInstance, pacerEventClient } from "@tanstack/pacer/event-client"; | ||
| import * as goober from "goober"; | ||
| import { useStore } from "@tanstack/solid-store"; | ||
| import clsx from "clsx"; | ||
| import { emitChange } from "@tanstack/pacer"; | ||
| import dayjs from "dayjs"; | ||
| import relativeTime from "dayjs/plugin/relativeTime.js"; | ||
| //#region src/PacerContextProvider.tsx | ||
| const initialPacerDevtoolsStore = { | ||
| asyncBatchers: [], | ||
| asyncDebouncers: [], | ||
| asyncQueuers: [], | ||
| asyncRateLimiters: [], | ||
| asyncThrottlers: [], | ||
| batchers: [], | ||
| debouncers: [], | ||
| queuers: [], | ||
| rateLimiters: [], | ||
| throttlers: [], | ||
| lastUpdatedByKey: {} | ||
| }; | ||
| const PacerDevtoolsContext = createContext([initialPacerDevtoolsStore, () => {}]); | ||
| /** | ||
| * Match TanStack Form devtools: subscribe with {@link pacerEventClient.on} per | ||
| * event suffix so the same `pacer:${suffix}` channel the library emits on is | ||
| * observed. `onAllPluginEvents` only sees `tanstack-devtools-global`, which is | ||
| * not always relayed the same way by the shell. | ||
| * | ||
| * `d-*` suffixes are used when the devtools panel pushes state back (see | ||
| * ActionButtons); those must update the panel store too. | ||
| */ | ||
| const PACER_DEVTOOLS_UTIL_EVENTS = [ | ||
| { | ||
| listKey: "asyncBatchers", | ||
| suffixes: ["AsyncBatcher", "d-AsyncBatcher"] | ||
| }, | ||
| { | ||
| listKey: "asyncDebouncers", | ||
| suffixes: ["AsyncDebouncer", "d-AsyncDebouncer"] | ||
| }, | ||
| { | ||
| listKey: "asyncQueuers", | ||
| suffixes: ["AsyncQueuer", "d-AsyncQueuer"] | ||
| }, | ||
| { | ||
| listKey: "asyncRateLimiters", | ||
| suffixes: ["AsyncRateLimiter", "d-AsyncRateLimiter"] | ||
| }, | ||
| { | ||
| listKey: "asyncThrottlers", | ||
| suffixes: ["AsyncThrottler", "d-AsyncThrottler"] | ||
| }, | ||
| { | ||
| listKey: "batchers", | ||
| suffixes: ["Batcher", "d-Batcher"] | ||
| }, | ||
| { | ||
| listKey: "debouncers", | ||
| suffixes: ["Debouncer", "d-Debouncer"] | ||
| }, | ||
| { | ||
| listKey: "queuers", | ||
| suffixes: ["Queuer", "d-Queuer"] | ||
| }, | ||
| { | ||
| listKey: "rateLimiters", | ||
| suffixes: ["RateLimiter", "d-RateLimiter"] | ||
| }, | ||
| { | ||
| listKey: "throttlers", | ||
| suffixes: ["Throttler", "d-Throttler"] | ||
| } | ||
| ]; | ||
| function PacerContextProvider(props) { | ||
| const [store, setStore] = createStore(initialPacerDevtoolsStore); | ||
| createEffect(() => { | ||
| const cleanups = []; | ||
| for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) for (const suffix of suffixes) cleanups.push(pacerEventClient.on(suffix, (e) => { | ||
| const key = e.payload.key; | ||
| if (!key) return; | ||
| const instance = getPacerDevtoolsInstance(key); | ||
| if (!instance || typeof instance !== "object") return; | ||
| setStore(produce((draft) => { | ||
| const list = draft[listKey]; | ||
| const index = list.findIndex((item) => item.key === key); | ||
| const inst = instance; | ||
| if (index !== -1) list[index] = inst; | ||
| else list.push(inst); | ||
| draft.lastUpdatedByKey[key] = Date.now(); | ||
| })); | ||
| })); | ||
| onCleanup(() => { | ||
| for (const cleanup of cleanups) cleanup(); | ||
| }); | ||
| }); | ||
| return createComponent(PacerDevtoolsContext.Provider, { | ||
| value: [store, setStore], | ||
| get children() { | ||
| return props.children; | ||
| } | ||
| }); | ||
| } | ||
| const usePacerDevtoolsContext = () => { | ||
| return useContext(PacerDevtoolsContext); | ||
| }; | ||
| const usePacerDevtoolsState = () => { | ||
| const [state] = usePacerDevtoolsContext(); | ||
| return state; | ||
| }; | ||
| //#endregion | ||
| //#region src/styles/tokens.ts | ||
| const tokens = { | ||
| colors: { | ||
| inherit: "inherit", | ||
| current: "currentColor", | ||
| transparent: "transparent", | ||
| black: "#000000", | ||
| white: "#ffffff", | ||
| neutral: { | ||
| 50: "#f9fafb", | ||
| 100: "#f2f4f7", | ||
| 200: "#eaecf0", | ||
| 300: "#d0d5dd", | ||
| 400: "#98a2b3", | ||
| 500: "#667085", | ||
| 600: "#475467", | ||
| 700: "#344054", | ||
| 800: "#1d2939", | ||
| 900: "#101828" | ||
| }, | ||
| darkGray: { | ||
| 50: "#525c7a", | ||
| 100: "#49536e", | ||
| 200: "#414962", | ||
| 300: "#394056", | ||
| 400: "#313749", | ||
| 500: "#292e3d", | ||
| 600: "#212530", | ||
| 700: "#191c24", | ||
| 800: "#111318", | ||
| 900: "#0b0d10" | ||
| }, | ||
| gray: { | ||
| 50: "#f9fafb", | ||
| 100: "#f2f4f7", | ||
| 200: "#eaecf0", | ||
| 300: "#d0d5dd", | ||
| 400: "#98a2b3", | ||
| 500: "#667085", | ||
| 600: "#475467", | ||
| 700: "#344054", | ||
| 800: "#1d2939", | ||
| 900: "#101828" | ||
| }, | ||
| blue: { | ||
| 25: "#F5FAFF", | ||
| 50: "#EFF8FF", | ||
| 100: "#D1E9FF", | ||
| 200: "#B2DDFF", | ||
| 300: "#84CAFF", | ||
| 400: "#53B1FD", | ||
| 500: "#2E90FA", | ||
| 600: "#1570EF", | ||
| 700: "#175CD3", | ||
| 800: "#1849A9", | ||
| 900: "#194185" | ||
| }, | ||
| green: { | ||
| 25: "#F6FEF9", | ||
| 50: "#ECFDF3", | ||
| 100: "#D1FADF", | ||
| 200: "#A6F4C5", | ||
| 300: "#6CE9A6", | ||
| 400: "#32D583", | ||
| 500: "#12B76A", | ||
| 600: "#039855", | ||
| 700: "#027A48", | ||
| 800: "#05603A", | ||
| 900: "#054F31" | ||
| }, | ||
| red: { | ||
| 50: "#fef2f2", | ||
| 100: "#fee2e2", | ||
| 200: "#fecaca", | ||
| 300: "#fca5a5", | ||
| 400: "#f87171", | ||
| 500: "#ef4444", | ||
| 600: "#dc2626", | ||
| 700: "#b91c1c", | ||
| 800: "#991b1b", | ||
| 900: "#7f1d1d", | ||
| 950: "#450a0a" | ||
| }, | ||
| yellow: { | ||
| 25: "#FFFCF5", | ||
| 50: "#FFFAEB", | ||
| 100: "#FEF0C7", | ||
| 200: "#FEDF89", | ||
| 300: "#FEC84B", | ||
| 400: "#FDB022", | ||
| 500: "#F79009", | ||
| 600: "#DC6803", | ||
| 700: "#B54708", | ||
| 800: "#93370D", | ||
| 900: "#7A2E0E" | ||
| }, | ||
| purple: { | ||
| 25: "#FAFAFF", | ||
| 50: "#F4F3FF", | ||
| 100: "#EBE9FE", | ||
| 200: "#D9D6FE", | ||
| 300: "#BDB4FE", | ||
| 400: "#9B8AFB", | ||
| 500: "#7A5AF8", | ||
| 600: "#6938EF", | ||
| 700: "#5925DC", | ||
| 800: "#4A1FB8", | ||
| 900: "#3E1C96" | ||
| }, | ||
| teal: { | ||
| 25: "#F6FEFC", | ||
| 50: "#F0FDF9", | ||
| 100: "#CCFBEF", | ||
| 200: "#99F6E0", | ||
| 300: "#5FE9D0", | ||
| 400: "#2ED3B7", | ||
| 500: "#15B79E", | ||
| 600: "#0E9384", | ||
| 700: "#107569", | ||
| 800: "#125D56", | ||
| 900: "#134E48" | ||
| }, | ||
| pink: { | ||
| 25: "#fdf2f8", | ||
| 50: "#fce7f3", | ||
| 100: "#fbcfe8", | ||
| 200: "#f9a8d4", | ||
| 300: "#f472b6", | ||
| 400: "#ec4899", | ||
| 500: "#db2777", | ||
| 600: "#be185d", | ||
| 700: "#9d174d", | ||
| 800: "#831843", | ||
| 900: "#500724" | ||
| }, | ||
| cyan: { | ||
| 25: "#ecfeff", | ||
| 50: "#cffafe", | ||
| 100: "#a5f3fc", | ||
| 200: "#67e8f9", | ||
| 300: "#22d3ee", | ||
| 400: "#06b6d4", | ||
| 500: "#0891b2", | ||
| 600: "#0e7490", | ||
| 700: "#155e75", | ||
| 800: "#164e63", | ||
| 900: "#083344" | ||
| } | ||
| }, | ||
| alpha: { | ||
| 100: "ff", | ||
| 90: "e5", | ||
| 80: "cc", | ||
| 70: "b3", | ||
| 60: "99", | ||
| 50: "80", | ||
| 40: "66", | ||
| 30: "4d", | ||
| 20: "33", | ||
| 10: "1a", | ||
| 0: "00" | ||
| }, | ||
| font: { | ||
| size: { | ||
| "2xs": "calc(var(--tsrd-font-size) * 0.625)", | ||
| xs: "calc(var(--tsrd-font-size) * 0.75)", | ||
| sm: "calc(var(--tsrd-font-size) * 0.875)", | ||
| md: "var(--tsrd-font-size)", | ||
| lg: "calc(var(--tsrd-font-size) * 1.125)", | ||
| xl: "calc(var(--tsrd-font-size) * 1.25)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 1.5)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 1.875)", | ||
| "4xl": "calc(var(--tsrd-font-size) * 2.25)", | ||
| "5xl": "calc(var(--tsrd-font-size) * 3)", | ||
| "6xl": "calc(var(--tsrd-font-size) * 3.75)", | ||
| "7xl": "calc(var(--tsrd-font-size) * 4.5)", | ||
| "8xl": "calc(var(--tsrd-font-size) * 6)", | ||
| "9xl": "calc(var(--tsrd-font-size) * 8)" | ||
| }, | ||
| lineHeight: { | ||
| "3xs": "calc(var(--tsrd-font-size) * 0.75)", | ||
| "2xs": "calc(var(--tsrd-font-size) * 0.875)", | ||
| xs: "calc(var(--tsrd-font-size) * 1)", | ||
| sm: "calc(var(--tsrd-font-size) * 1.25)", | ||
| md: "calc(var(--tsrd-font-size) * 1.5)", | ||
| lg: "calc(var(--tsrd-font-size) * 1.75)", | ||
| xl: "calc(var(--tsrd-font-size) * 2)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 2.25)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 2.5)", | ||
| "4xl": "calc(var(--tsrd-font-size) * 2.75)", | ||
| "5xl": "calc(var(--tsrd-font-size) * 3)", | ||
| "6xl": "calc(var(--tsrd-font-size) * 3.25)", | ||
| "7xl": "calc(var(--tsrd-font-size) * 3.5)", | ||
| "8xl": "calc(var(--tsrd-font-size) * 3.75)", | ||
| "9xl": "calc(var(--tsrd-font-size) * 4)" | ||
| }, | ||
| weight: { | ||
| thin: "100", | ||
| extralight: "200", | ||
| light: "300", | ||
| normal: "400", | ||
| medium: "500", | ||
| semibold: "600", | ||
| bold: "700", | ||
| extrabold: "800", | ||
| black: "900" | ||
| }, | ||
| fontFamily: { | ||
| sans: "ui-sans-serif, Inter, system-ui, sans-serif, sans-serif", | ||
| mono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace` | ||
| } | ||
| }, | ||
| breakpoints: { | ||
| xs: "320px", | ||
| sm: "640px", | ||
| md: "768px", | ||
| lg: "1024px", | ||
| xl: "1280px", | ||
| "2xl": "1536px" | ||
| }, | ||
| border: { radius: { | ||
| none: "0px", | ||
| xs: "calc(var(--tsrd-font-size) * 0.125)", | ||
| sm: "calc(var(--tsrd-font-size) * 0.25)", | ||
| md: "calc(var(--tsrd-font-size) * 0.375)", | ||
| lg: "calc(var(--tsrd-font-size) * 0.5)", | ||
| xl: "calc(var(--tsrd-font-size) * 0.75)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 1)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 1.5)", | ||
| full: "9999px" | ||
| } }, | ||
| size: { | ||
| 0: "0px", | ||
| .25: "calc(var(--tsrd-font-size) * 0.0625)", | ||
| .5: "calc(var(--tsrd-font-size) * 0.125)", | ||
| 1: "calc(var(--tsrd-font-size) * 0.25)", | ||
| 1.5: "calc(var(--tsrd-font-size) * 0.375)", | ||
| 2: "calc(var(--tsrd-font-size) * 0.5)", | ||
| 2.5: "calc(var(--tsrd-font-size) * 0.625)", | ||
| 3: "calc(var(--tsrd-font-size) * 0.75)", | ||
| 3.5: "calc(var(--tsrd-font-size) * 0.875)", | ||
| 4: "calc(var(--tsrd-font-size) * 1)", | ||
| 4.5: "calc(var(--tsrd-font-size) * 1.125)", | ||
| 5: "calc(var(--tsrd-font-size) * 1.25)", | ||
| 5.5: "calc(var(--tsrd-font-size) * 1.375)", | ||
| 6: "calc(var(--tsrd-font-size) * 1.5)", | ||
| 6.5: "calc(var(--tsrd-font-size) * 1.625)", | ||
| 7: "calc(var(--tsrd-font-size) * 1.75)", | ||
| 8: "calc(var(--tsrd-font-size) * 2)", | ||
| 9: "calc(var(--tsrd-font-size) * 2.25)", | ||
| 10: "calc(var(--tsrd-font-size) * 2.5)", | ||
| 11: "calc(var(--tsrd-font-size) * 2.75)", | ||
| 12: "calc(var(--tsrd-font-size) * 3)", | ||
| 14: "calc(var(--tsrd-font-size) * 3.5)", | ||
| 16: "calc(var(--tsrd-font-size) * 4)", | ||
| 20: "calc(var(--tsrd-font-size) * 5)", | ||
| 24: "calc(var(--tsrd-font-size) * 6)", | ||
| 28: "calc(var(--tsrd-font-size) * 7)", | ||
| 32: "calc(var(--tsrd-font-size) * 8)", | ||
| 36: "calc(var(--tsrd-font-size) * 9)", | ||
| 40: "calc(var(--tsrd-font-size) * 10)", | ||
| 44: "calc(var(--tsrd-font-size) * 11)", | ||
| 48: "calc(var(--tsrd-font-size) * 12)", | ||
| 52: "calc(var(--tsrd-font-size) * 13)", | ||
| 56: "calc(var(--tsrd-font-size) * 14)", | ||
| 60: "calc(var(--tsrd-font-size) * 15)", | ||
| 64: "calc(var(--tsrd-font-size) * 16)", | ||
| 72: "calc(var(--tsrd-font-size) * 18)", | ||
| 80: "calc(var(--tsrd-font-size) * 20)", | ||
| 96: "calc(var(--tsrd-font-size) * 24)" | ||
| }, | ||
| shadow: { | ||
| xs: (_ = "rgb(0 0 0 / 0.1)") => `0 1px 2px 0 rgb(0 0 0 / 0.05)`, | ||
| sm: (color = "rgb(0 0 0 / 0.1)") => `0 1px 3px 0 ${color}, 0 1px 2px -1px ${color}`, | ||
| md: (color = "rgb(0 0 0 / 0.1)") => `0 4px 6px -1px ${color}, 0 2px 4px -2px ${color}`, | ||
| lg: (color = "rgb(0 0 0 / 0.1)") => `0 10px 15px -3px ${color}, 0 4px 6px -4px ${color}`, | ||
| xl: (color = "rgb(0 0 0 / 0.1)") => `0 20px 25px -5px ${color}, 0 8px 10px -6px ${color}`, | ||
| "2xl": (color = "rgb(0 0 0 / 0.25)") => `0 25px 50px -12px ${color}`, | ||
| inner: (color = "rgb(0 0 0 / 0.05)") => `inset 0 2px 4px 0 ${color}`, | ||
| none: () => `none` | ||
| }, | ||
| zIndices: { | ||
| hide: -1, | ||
| auto: "auto", | ||
| base: 0, | ||
| docked: 10, | ||
| dropdown: 1e3, | ||
| sticky: 1100, | ||
| banner: 1200, | ||
| overlay: 1300, | ||
| modal: 1400, | ||
| popover: 1500, | ||
| skipLink: 1600, | ||
| toast: 1700, | ||
| tooltip: 1800 | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/styles/use-styles.ts | ||
| const stylesFactory = (theme) => { | ||
| const { colors, font, size, alpha, border } = tokens; | ||
| const { fontFamily, size: fontSize } = font; | ||
| const css = goober.css; | ||
| const t = (light, dark) => theme === "light" ? light : dark; | ||
| return { | ||
| shellRoot: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| box-sizing: border-box; | ||
| width: 100%; | ||
| height: 100%; | ||
| min-height: 0; | ||
| overflow: hidden; | ||
| `, | ||
| mainPanelShell: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| flex: 1 1 0%; | ||
| min-height: 0; | ||
| overflow: hidden !important; | ||
| `, | ||
| mainContainer: css` | ||
| display: flex; | ||
| flex: 1 1 0%; | ||
| min-height: 0; | ||
| overflow: hidden; | ||
| padding: ${size[2]}; | ||
| padding-top: 0; | ||
| margin-top: ${size[2]}; | ||
| `, | ||
| dragHandle: css` | ||
| width: 8px; | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| cursor: col-resize; | ||
| position: relative; | ||
| transition: all 0.2s ease; | ||
| user-select: none; | ||
| pointer-events: all; | ||
| margin: 0 ${size[1]}; | ||
| border-radius: 2px; | ||
| &:hover { | ||
| background: ${t(colors.blue[600], colors.blue[500])}; | ||
| margin: 0 ${size[1]}; | ||
| } | ||
| &.dragging { | ||
| background: ${t(colors.blue[700], colors.blue[600])}; | ||
| margin: 0 ${size[1]}; | ||
| } | ||
| &::after { | ||
| content: ''; | ||
| position: absolute; | ||
| top: 50%; | ||
| left: 50%; | ||
| transform: translate(-50%, -50%); | ||
| width: 2px; | ||
| height: 20px; | ||
| background: ${t(colors.gray[400], colors.darkGray[400])}; | ||
| border-radius: 1px; | ||
| pointer-events: none; | ||
| } | ||
| &:hover::after, | ||
| &.dragging::after { | ||
| background: ${t(colors.blue[500], colors.blue[300])}; | ||
| } | ||
| `, | ||
| leftPanel: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| min-height: 0; | ||
| flex-shrink: 0; | ||
| `, | ||
| rightPanel: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| min-height: 0; | ||
| flex: 1; | ||
| `, | ||
| panelHeader: css` | ||
| font-size: ${fontSize.md}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.blue[700], colors.blue[400])}; | ||
| padding: ${size[2]}; | ||
| border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| flex-shrink: 0; | ||
| `, | ||
| utilList: css` | ||
| flex: 1; | ||
| overflow-y: auto; | ||
| padding: ${size[1]}; | ||
| min-height: 0; | ||
| `, | ||
| utilGroup: css` | ||
| margin-bottom: ${size[2]}; | ||
| `, | ||
| utilGroupHeader: css` | ||
| font-size: ${fontSize.xs}; | ||
| font-weight: ${font.weight.semibold}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| margin-bottom: ${size[1]}; | ||
| padding: ${size[1]} ${size[2]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| `, | ||
| utilRow: css` | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| padding: ${size[2]}; | ||
| margin-bottom: ${size[1]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| cursor: pointer; | ||
| transition: all 0.2s ease; | ||
| border: 1px solid transparent; | ||
| &:hover { | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-color: ${t(colors.gray[400], colors.darkGray[500])}; | ||
| } | ||
| `, | ||
| utilRowSelected: css` | ||
| background: ${t(colors.blue[100], colors.blue[900] + alpha[20])}; | ||
| border-color: ${t(colors.blue[600], colors.blue[500])}; | ||
| box-shadow: 0 0 0 1px | ||
| ${t(colors.blue[600] + alpha[30], colors.blue[500] + alpha[30])}; | ||
| `, | ||
| utilKey: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| flex: 1; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| `, | ||
| utilStatus: css` | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| padding: ${size[1]} ${size[1]}; | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-radius: ${border.radius.sm}; | ||
| margin-left: ${size[1]}; | ||
| `, | ||
| stateDetails: css` | ||
| flex: 1; | ||
| overflow-y: auto; | ||
| padding: ${size[2]}; | ||
| min-height: 0; | ||
| `, | ||
| stateHeader: css` | ||
| margin-bottom: ${size[2]}; | ||
| padding-bottom: ${size[2]}; | ||
| border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| `, | ||
| stateTitle: css` | ||
| font-size: ${fontSize.md}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.blue[700], colors.blue[400])}; | ||
| margin-bottom: ${size[1]}; | ||
| `, | ||
| stateKey: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| word-break: break-all; | ||
| `, | ||
| stateContent: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[2]}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| `, | ||
| detailsGrid: css` | ||
| display: grid; | ||
| grid-template-columns: 1fr; | ||
| gap: ${size[2]}; | ||
| align-items: start; | ||
| `, | ||
| detailSection: css` | ||
| background: ${t(colors.white, colors.darkGray[700])}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[2]}; | ||
| `, | ||
| detailSectionHeader: css` | ||
| font-size: ${fontSize.sm}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.gray[800], colors.gray[200])}; | ||
| margin-bottom: ${size[1]}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.04em; | ||
| `, | ||
| actionsRow: css` | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: ${size[2]}; | ||
| `, | ||
| actionButton: css` | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: ${size[1]}; | ||
| padding: ${size[1]} ${size[2]}; | ||
| border-radius: ${border.radius.md}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[500])}; | ||
| background: ${t(colors.gray[200], colors.darkGray[600])}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| font-size: ${fontSize.xs}; | ||
| cursor: pointer; | ||
| user-select: none; | ||
| transition: | ||
| background 0.15s, | ||
| border-color 0.15s; | ||
| &:hover { | ||
| background: ${t(colors.gray[300], colors.darkGray[500])}; | ||
| border-color: ${t(colors.gray[400], colors.darkGray[400])}; | ||
| } | ||
| &:disabled { | ||
| opacity: 0.5; | ||
| cursor: not-allowed; | ||
| &:hover { | ||
| background: ${t(colors.gray[200], colors.darkGray[600])}; | ||
| border-color: ${t(colors.gray[300], colors.darkGray[500])}; | ||
| } | ||
| } | ||
| `, | ||
| actionDotBlue: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.blue[400]}; | ||
| `, | ||
| actionDotGreen: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.green[400]}; | ||
| `, | ||
| actionDotRed: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.red[400]}; | ||
| `, | ||
| actionDotYellow: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.yellow[400]}; | ||
| `, | ||
| actionDotOrange: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.pink[400]}; | ||
| `, | ||
| actionDotPurple: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.purple[400]}; | ||
| `, | ||
| infoGrid: css` | ||
| display: grid; | ||
| grid-template-columns: auto 1fr; | ||
| gap: ${size[1]}; | ||
| row-gap: ${size[1]}; | ||
| align-items: center; | ||
| `, | ||
| infoLabel: css` | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| font-size: ${fontSize.xs}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| `, | ||
| infoValueMono: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| word-break: break-all; | ||
| `, | ||
| noSelection: css` | ||
| flex: 1; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| color: ${t(colors.gray[500], colors.gray[500])}; | ||
| font-style: italic; | ||
| text-align: center; | ||
| padding: ${size[4]}; | ||
| `, | ||
| sectionContainer: css` | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: ${size[4]}; | ||
| `, | ||
| section: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| box-shadow: ${tokens.shadow.md(t(colors.gray[400] + alpha[80], colors.black + alpha[80]))}; | ||
| padding: ${size[4]}; | ||
| margin-bottom: ${size[4]}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| min-width: 0; | ||
| max-width: 33%; | ||
| max-height: fit-content; | ||
| `, | ||
| sectionHeader: css` | ||
| font-size: ${fontSize.lg}; | ||
| font-weight: ${font.weight.bold}; | ||
| margin-bottom: ${size[2]}; | ||
| color: ${t(colors.blue[600], colors.blue[400])}; | ||
| letter-spacing: 0.01em; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: ${size[2]}; | ||
| `, | ||
| sectionEmpty: css` | ||
| color: ${t(colors.gray[500], colors.gray[500])}; | ||
| font-size: ${fontSize.sm}; | ||
| font-style: italic; | ||
| margin: ${size[2]} 0; | ||
| `, | ||
| instanceList: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: ${size[2]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| `, | ||
| instanceCard: css` | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[3]}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| font-size: ${fontSize.sm}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| font-family: ${fontFamily.mono}; | ||
| overflow-x: auto; | ||
| transition: | ||
| box-shadow 0.3s, | ||
| background 0.3s; | ||
| ` | ||
| }; | ||
| }; | ||
| function useStyles() { | ||
| const { theme } = useTheme(); | ||
| const [styles, setStyles] = createSignal(stylesFactory(theme())); | ||
| createEffect(() => { | ||
| setStyles(stylesFactory(theme())); | ||
| }); | ||
| return styles; | ||
| } | ||
| //#endregion | ||
| //#region src/components/util-groups.ts | ||
| const UTIL_GROUPS = [ | ||
| { | ||
| key: "debouncers", | ||
| label: "Debouncers", | ||
| displayName: "Debouncer" | ||
| }, | ||
| { | ||
| key: "throttlers", | ||
| label: "Throttlers", | ||
| displayName: "Throttler" | ||
| }, | ||
| { | ||
| key: "batchers", | ||
| label: "Batchers", | ||
| displayName: "Batcher" | ||
| }, | ||
| { | ||
| key: "queuers", | ||
| label: "Queuers", | ||
| displayName: "Queuer" | ||
| }, | ||
| { | ||
| key: "rateLimiters", | ||
| label: "Rate Limiters", | ||
| displayName: "Rate Limiter" | ||
| }, | ||
| { | ||
| key: "asyncDebouncers", | ||
| label: "Async Debouncers", | ||
| displayName: "Async Debouncer" | ||
| }, | ||
| { | ||
| key: "asyncThrottlers", | ||
| label: "Async Throttlers", | ||
| displayName: "Async Throttler" | ||
| }, | ||
| { | ||
| key: "asyncBatchers", | ||
| label: "Async Batchers", | ||
| displayName: "Async Batcher" | ||
| }, | ||
| { | ||
| key: "asyncQueuers", | ||
| label: "Async Queuers", | ||
| displayName: "Async Queuer" | ||
| }, | ||
| { | ||
| key: "asyncRateLimiters", | ||
| label: "Async Rate Limiters", | ||
| displayName: "Async Rate Limiter" | ||
| } | ||
| ]; | ||
| //#endregion | ||
| //#region src/components/UtilList.tsx | ||
| var _tmpl$$4 = /* @__PURE__ */ template(`<div>`), _tmpl$2$3 = /* @__PURE__ */ template(`<div><div>`), _tmpl$3$2 = /* @__PURE__ */ template(`<div><div></div><div>`); | ||
| function UtilList(props) { | ||
| const styles = useStyles(); | ||
| return (() => { | ||
| var _el$ = _tmpl$$4(); | ||
| insert(_el$, createComponent(For, { | ||
| each: UTIL_GROUPS, | ||
| children: (group) => memo(() => memo(() => props.utilState()[group.key].length > 0)() && (() => { | ||
| var _el$2 = _tmpl$2$3(), _el$3 = _el$2.firstChild; | ||
| insert(_el$3, () => group.label); | ||
| insert(_el$2, createComponent(For, { | ||
| get each() { | ||
| return props.utilState()[group.key]; | ||
| }, | ||
| children: (instance) => { | ||
| const status = (() => { | ||
| try { | ||
| const statusAccessor = useStore(instance.store, (s) => s.status); | ||
| return () => statusAccessor() ?? "unknown"; | ||
| } catch { | ||
| return () => "unknown"; | ||
| } | ||
| })(); | ||
| return (() => { | ||
| var _el$4 = _tmpl$3$2(), _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling; | ||
| _el$4.$$click = () => props.setSelectedKey(instance.key ?? null); | ||
| insert(_el$5, () => instance.key); | ||
| insert(_el$6, status); | ||
| effect((_p$) => { | ||
| var _v$3 = clsx(styles().utilRow, props.selectedKey() === instance.key && styles().utilRowSelected), _v$4 = styles().utilKey, _v$5 = styles().utilStatus; | ||
| _v$3 !== _p$.e && className(_el$4, _p$.e = _v$3); | ||
| _v$4 !== _p$.t && className(_el$5, _p$.t = _v$4); | ||
| _v$5 !== _p$.a && className(_el$6, _p$.a = _v$5); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0 | ||
| }); | ||
| return _el$4; | ||
| })(); | ||
| } | ||
| }), null); | ||
| effect((_p$) => { | ||
| var _v$ = styles().utilGroup, _v$2 = styles().utilGroupHeader; | ||
| _v$ !== _p$.e && className(_el$2, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$3, _p$.t = _v$2); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0 | ||
| }); | ||
| return _el$2; | ||
| })()) | ||
| })); | ||
| effect(() => className(_el$, styles().utilList)); | ||
| return _el$; | ||
| })(); | ||
| } | ||
| delegateEvents(["click"]); | ||
| //#endregion | ||
| //#region src/utils/read-pacer-store-state.ts | ||
| /** TanStack Store (`@tanstack/store`) used by pacer utils. */ | ||
| function isPacerUtilTanStackStore(x) { | ||
| return typeof x === "object" && x !== null && "subscribe" in x && typeof x.subscribe === "function" && "get" in x && typeof x.get === "function"; | ||
| } | ||
| /** | ||
| * Core pacer utils expose TanStack `Store` (`get()` / `state`). React hooks may | ||
| * also expose a reactive `state` field. Use this for devtools UI that needs a | ||
| * plain snapshot (e.g. JsonTree). | ||
| */ | ||
| function getPacerUtilStoreState(instance) { | ||
| const store = instance.store; | ||
| if (store && typeof store.get === "function") return store.get(); | ||
| if (store && store.state !== void 0) return store.state; | ||
| return instance.state; | ||
| } | ||
| //#endregion | ||
| //#region src/components/ActionButtons.tsx | ||
| var _tmpl$$3 = /* @__PURE__ */ template(`<div>No actions available for this util`), _tmpl$2$2 = /* @__PURE__ */ template(`<div>`), _tmpl$3$1 = /* @__PURE__ */ template(`<button><span>`), _tmpl$4 = /* @__PURE__ */ template(`<button><span></span>Flush`), _tmpl$5 = /* @__PURE__ */ template(`<button><span></span>Cancel`), _tmpl$6 = /* @__PURE__ */ template(`<button><span></span>Reset`), _tmpl$7 = /* @__PURE__ */ template(`<button><span></span>Clear`); | ||
| function ActionButtons(props) { | ||
| const styles = useStyles(); | ||
| const utilInstance = props.instance; | ||
| const store = utilInstance?.store; | ||
| const stateAccessor = isPacerUtilTanStackStore(store) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => getPacerUtilStoreState(utilInstance); | ||
| const getState = () => stateAccessor(); | ||
| const hasPending = () => { | ||
| return "isPending" in getState(); | ||
| }; | ||
| const isPending = () => { | ||
| const u = getState(); | ||
| return "isPending" in u ? !!u.isPending : false; | ||
| }; | ||
| const isEmptyFlag = () => { | ||
| const u = getState(); | ||
| return "isEmpty" in u ? !!u.isEmpty : false; | ||
| }; | ||
| const hasFlush = typeof utilInstance.flush === "function"; | ||
| const hasCancel = typeof utilInstance.cancel === "function"; | ||
| const hasReset = typeof utilInstance.reset === "function"; | ||
| const hasClear = typeof utilInstance.clear === "function"; | ||
| const hasStart = typeof utilInstance.start === "function"; | ||
| const hasStop = typeof utilInstance.stop === "function"; | ||
| const hasStartStop = hasStart && hasStop; | ||
| const isRunning = () => { | ||
| const u = getState(); | ||
| return "isRunning" in u ? !!u.isRunning : true; | ||
| }; | ||
| if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) return (() => { | ||
| var _el$ = _tmpl$$3(); | ||
| effect(() => className(_el$, styles().sectionEmpty)); | ||
| return _el$; | ||
| })(); | ||
| const emitName = `d-${props.utilName}`; | ||
| const utilNameLower = props.utilName.toLowerCase(); | ||
| return (() => { | ||
| var _el$2 = _tmpl$2$2(); | ||
| insert(_el$2, (() => { | ||
| var _c$ = memo(() => !!hasPending()); | ||
| return () => _c$() && (() => { | ||
| var _el$3 = _tmpl$3$1(), _el$4 = _el$3.firstChild; | ||
| _el$3.$$mousedown = () => { | ||
| const next = !isPending(); | ||
| utilInstance.store.setState((prev) => ({ | ||
| ...prev, | ||
| isPending: next | ||
| })); | ||
| emitChange(emitName, utilInstance); | ||
| }; | ||
| insert(_el$3, () => isPending() ? "Restore Pending" : "Trigger Pending", null); | ||
| effect((_p$) => { | ||
| var _v$ = styles().actionButton, _v$2 = styles().actionDotBlue; | ||
| _v$ !== _p$.e && className(_el$3, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$4, _p$.t = _v$2); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0 | ||
| }); | ||
| return _el$3; | ||
| })(); | ||
| })(), null); | ||
| insert(_el$2, hasFlush && (() => { | ||
| var _el$5 = _tmpl$4(), _el$6 = _el$5.firstChild; | ||
| _el$5.$$mousedown = () => { | ||
| utilInstance.flush(); | ||
| emitChange(emitName, utilInstance); | ||
| }; | ||
| effect((_p$) => { | ||
| var _v$3 = styles().actionButton, _v$4 = utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), _v$5 = styles().actionDotGreen; | ||
| _v$3 !== _p$.e && className(_el$5, _p$.e = _v$3); | ||
| _v$4 !== _p$.t && (_el$5.disabled = _p$.t = _v$4); | ||
| _v$5 !== _p$.a && className(_el$6, _p$.a = _v$5); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0 | ||
| }); | ||
| return _el$5; | ||
| })(), null); | ||
| insert(_el$2, hasCancel && (() => { | ||
| var _el$7 = _tmpl$5(), _el$8 = _el$7.firstChild; | ||
| _el$7.$$mousedown = () => { | ||
| utilInstance.cancel(); | ||
| emitChange(emitName, utilInstance); | ||
| }; | ||
| effect((_p$) => { | ||
| var _v$6 = styles().actionButton, _v$7 = utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), _v$8 = styles().actionDotRed; | ||
| _v$6 !== _p$.e && className(_el$7, _p$.e = _v$6); | ||
| _v$7 !== _p$.t && (_el$7.disabled = _p$.t = _v$7); | ||
| _v$8 !== _p$.a && className(_el$8, _p$.a = _v$8); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0 | ||
| }); | ||
| return _el$7; | ||
| })(), null); | ||
| insert(_el$2, hasReset && (() => { | ||
| var _el$9 = _tmpl$6(), _el$0 = _el$9.firstChild; | ||
| _el$9.$$mousedown = () => { | ||
| utilInstance.reset(); | ||
| emitChange(emitName, utilInstance); | ||
| }; | ||
| effect((_p$) => { | ||
| var _v$9 = styles().actionButton, _v$0 = styles().actionDotYellow; | ||
| _v$9 !== _p$.e && className(_el$9, _p$.e = _v$9); | ||
| _v$0 !== _p$.t && className(_el$0, _p$.t = _v$0); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0 | ||
| }); | ||
| return _el$9; | ||
| })(), null); | ||
| insert(_el$2, hasClear && (() => { | ||
| var _el$1 = _tmpl$7(), _el$10 = _el$1.firstChild; | ||
| _el$1.$$mousedown = () => { | ||
| utilInstance.clear(); | ||
| emitChange(emitName, utilInstance); | ||
| }; | ||
| effect((_p$) => { | ||
| var _v$1 = styles().actionButton, _v$10 = utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), _v$11 = styles().actionDotOrange; | ||
| _v$1 !== _p$.e && className(_el$1, _p$.e = _v$1); | ||
| _v$10 !== _p$.t && (_el$1.disabled = _p$.t = _v$10); | ||
| _v$11 !== _p$.a && className(_el$10, _p$.a = _v$11); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0 | ||
| }); | ||
| return _el$1; | ||
| })(), null); | ||
| insert(_el$2, hasStartStop && (() => { | ||
| var _el$11 = _tmpl$3$1(), _el$12 = _el$11.firstChild; | ||
| _el$11.$$mousedown = () => { | ||
| if (isRunning()) utilInstance.stop(); | ||
| else utilInstance.start(); | ||
| emitChange(emitName, utilInstance); | ||
| }; | ||
| insert(_el$11, () => isRunning() ? "Stop" : "Start", null); | ||
| effect((_p$) => { | ||
| var _v$12 = styles().actionButton, _v$13 = styles().actionDotPurple; | ||
| _v$12 !== _p$.e && className(_el$11, _p$.e = _v$12); | ||
| _v$13 !== _p$.t && className(_el$12, _p$.t = _v$13); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0 | ||
| }); | ||
| return _el$11; | ||
| })(), null); | ||
| effect(() => className(_el$2, styles().actionsRow)); | ||
| return _el$2; | ||
| })(); | ||
| } | ||
| delegateEvents(["mousedown"]); | ||
| //#endregion | ||
| //#region src/components/StateHeader.tsx | ||
| var _tmpl$$2 = /* @__PURE__ */ template(`<div><div></div><div style=display:flex;align-items:center;gap:16px><div><div>Key</div><div></div><div>Last Updated</div><div> (<!>)</div></div><div style=margin-left:auto;font-weight:bold>% reduction`); | ||
| dayjs.extend(relativeTime); | ||
| function reductionFromState(entry, state) { | ||
| if (!state) return 0; | ||
| const completedExecutions = entry.type.toLowerCase().includes("async") ? Number(state.settleCount) || 0 : Number(state.executionCount) || 0; | ||
| if (entry.type.toLowerCase().includes("batcher")) { | ||
| const totalItemsProcessed = Number(state.totalItemsProcessed) || 0; | ||
| if (totalItemsProcessed === 0) return 0; | ||
| return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100); | ||
| } | ||
| let requestCount = 0; | ||
| if (state.maybeExecuteCount !== void 0) requestCount = Number(state.maybeExecuteCount) || 0; | ||
| else if (state.addItemCount !== void 0) requestCount = Number(state.addItemCount) || 0; | ||
| else return 0; | ||
| if (requestCount === 0) return 0; | ||
| const reduction = requestCount - completedExecutions; | ||
| return Math.round(reduction / requestCount * 100); | ||
| } | ||
| function StateHeaderInner(props) { | ||
| const key = props.entry.instance.key; | ||
| const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now(); | ||
| const getRelativeTime = () => { | ||
| const at = updatedAt(); | ||
| const diffMs = props.now() - at; | ||
| const diffSeconds = Math.floor(diffMs / 1e3); | ||
| if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`; | ||
| return dayjs(at).fromNow(); | ||
| }; | ||
| const store = props.entry.instance?.store; | ||
| const stateAccessor = isPacerUtilTanStackStore(store) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => getPacerUtilStoreState(props.entry.instance); | ||
| const reductionPercentage = () => reductionFromState(props.entry, stateAccessor()); | ||
| return (() => { | ||
| var _el$ = _tmpl$$2(), _el$2 = _el$.firstChild, _el$4 = _el$2.nextSibling.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.firstChild, _el$1 = _el$9.nextSibling; | ||
| _el$1.nextSibling; | ||
| var _el$10 = _el$4.nextSibling, _el$11 = _el$10.firstChild; | ||
| insert(_el$2, () => props.entry.type); | ||
| insert(_el$6, key); | ||
| insert(_el$8, () => new Date(updatedAt()).toLocaleTimeString(), _el$9); | ||
| insert(_el$8, getRelativeTime, _el$1); | ||
| insert(_el$10, reductionPercentage, _el$11); | ||
| effect((_p$) => { | ||
| var _v$ = props.styles.stateHeader, _v$2 = props.styles.stateTitle, _v$3 = props.styles.infoGrid, _v$4 = props.styles.infoLabel, _v$5 = props.styles.infoValueMono, _v$6 = props.styles.infoLabel, _v$7 = props.styles.infoValueMono, _v$8 = props.styles.infoValueMono; | ||
| _v$ !== _p$.e && className(_el$, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$2, _p$.t = _v$2); | ||
| _v$3 !== _p$.a && className(_el$4, _p$.a = _v$3); | ||
| _v$4 !== _p$.o && className(_el$5, _p$.o = _v$4); | ||
| _v$5 !== _p$.i && className(_el$6, _p$.i = _v$5); | ||
| _v$6 !== _p$.n && className(_el$7, _p$.n = _v$6); | ||
| _v$7 !== _p$.s && className(_el$8, _p$.s = _v$7); | ||
| _v$8 !== _p$.h && className(_el$10, _p$.h = _v$8); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0, | ||
| o: void 0, | ||
| i: void 0, | ||
| n: void 0, | ||
| s: void 0, | ||
| h: void 0 | ||
| }); | ||
| return _el$; | ||
| })(); | ||
| } | ||
| function StateHeader(props) { | ||
| const styles = useStyles(); | ||
| const [now, setNow] = createSignal(Date.now()); | ||
| onMount(() => { | ||
| const interval = setInterval(() => { | ||
| setNow(Date.now()); | ||
| }, 1e3); | ||
| onCleanup(() => { | ||
| clearInterval(interval); | ||
| }); | ||
| }); | ||
| return createComponent(Show, { | ||
| get when() { | ||
| return props.selectedInstance(); | ||
| }, | ||
| children: (entry) => createComponent(StateHeaderInner, { | ||
| get entry() { | ||
| return entry(); | ||
| }, | ||
| get styles() { | ||
| return styles(); | ||
| }, | ||
| now, | ||
| get utilState() { | ||
| return props.utilState; | ||
| } | ||
| }) | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/components/UtilStateJsonTree.tsx | ||
| /** | ||
| * Subscribes to the util's TanStack Store so the tree updates live (same | ||
| * idea as {@link UtilList} status). A plain snapshot would only refresh when the | ||
| * devtools shell re-renders, which does not happen on nested store updates. | ||
| */ | ||
| function UtilStateJsonTree(props) { | ||
| const store = props.instance?.store; | ||
| if (isPacerUtilTanStackStore(store)) { | ||
| const snapshot = useStore(store, (s) => s); | ||
| return createComponent(JsonTree, { get value() { | ||
| return snapshot(); | ||
| } }); | ||
| } | ||
| return createComponent(JsonTree, { get value() { | ||
| return getPacerUtilStoreState(props.instance); | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/components/DetailsPanel.tsx | ||
| var _tmpl$$1 = /* @__PURE__ */ template(`<div>`), _tmpl$2$1 = /* @__PURE__ */ template(`<div>Select a util from the left panel to view its state`), _tmpl$3 = /* @__PURE__ */ template(`<div><div><div>Actions</div></div><div><div>State</div><div></div></div><div><div>Options</div><div>`); | ||
| function DetailsPanel(props) { | ||
| const styles = useStyles(); | ||
| return (() => { | ||
| var _el$ = _tmpl$$1(); | ||
| insert(_el$, () => { | ||
| const entry = props.selectedInstance(); | ||
| if (!entry) return (() => { | ||
| var _el$2 = _tmpl$2$1(); | ||
| effect(() => className(_el$2, styles().noSelection)); | ||
| return _el$2; | ||
| })(); | ||
| return [createComponent(StateHeader, { | ||
| get selectedInstance() { | ||
| return props.selectedInstance; | ||
| }, | ||
| get utilState() { | ||
| return props.utilState; | ||
| } | ||
| }), (() => { | ||
| var _el$3 = _tmpl$3(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$4.nextSibling, _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling, _el$9 = _el$6.nextSibling, _el$0 = _el$9.firstChild, _el$1 = _el$0.nextSibling; | ||
| insert(_el$4, createComponent(ActionButtons, { | ||
| get instance() { | ||
| return entry.instance; | ||
| }, | ||
| get utilName() { | ||
| return entry.type; | ||
| } | ||
| }), null); | ||
| insert(_el$8, createComponent(UtilStateJsonTree, { get instance() { | ||
| return entry.instance; | ||
| } })); | ||
| insert(_el$1, createComponent(JsonTree, { get value() { | ||
| return entry.instance.options; | ||
| } })); | ||
| effect((_p$) => { | ||
| var _v$ = styles().detailsGrid, _v$2 = styles().detailSection, _v$3 = styles().detailSectionHeader, _v$4 = styles().detailSection, _v$5 = styles().detailSectionHeader, _v$6 = styles().stateContent, _v$7 = styles().detailSection, _v$8 = styles().detailSectionHeader, _v$9 = styles().stateContent; | ||
| _v$ !== _p$.e && className(_el$3, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$4, _p$.t = _v$2); | ||
| _v$3 !== _p$.a && className(_el$5, _p$.a = _v$3); | ||
| _v$4 !== _p$.o && className(_el$6, _p$.o = _v$4); | ||
| _v$5 !== _p$.i && className(_el$7, _p$.i = _v$5); | ||
| _v$6 !== _p$.n && className(_el$8, _p$.n = _v$6); | ||
| _v$7 !== _p$.s && className(_el$9, _p$.s = _v$7); | ||
| _v$8 !== _p$.h && className(_el$0, _p$.h = _v$8); | ||
| _v$9 !== _p$.r && className(_el$1, _p$.r = _v$9); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0, | ||
| o: void 0, | ||
| i: void 0, | ||
| n: void 0, | ||
| s: void 0, | ||
| h: void 0, | ||
| r: void 0 | ||
| }); | ||
| return _el$3; | ||
| })()]; | ||
| }); | ||
| effect(() => className(_el$, styles().stateDetails)); | ||
| return _el$; | ||
| })(); | ||
| } | ||
| //#endregion | ||
| //#region src/components/Shell.tsx | ||
| var _tmpl$ = /* @__PURE__ */ template(`<div><div style=min-width:150px;max-width:800px></div><div></div><div style=flex:1><div>Details`), _tmpl$2 = /* @__PURE__ */ template(`<div>`); | ||
| function readResizeObserverBlockSize(entry) { | ||
| const [box] = entry.borderBoxSize; | ||
| if (box !== void 0) return box.blockSize; | ||
| return entry.contentRect.height; | ||
| } | ||
| /** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */ | ||
| function resolvePluginHost(el) { | ||
| const byId = el.closest("[id^=\"plugin-container-\"]"); | ||
| if (byId) return byId; | ||
| return el.parentElement?.parentElement?.parentElement ?? el.parentElement?.parentElement ?? el.parentElement; | ||
| } | ||
| function Shell() { | ||
| const styles = useStyles(); | ||
| const state = usePacerDevtoolsState(); | ||
| const utilState = () => state; | ||
| const [selectedKey, setSelectedKey] = createSignal(null); | ||
| const [leftPanelWidth, setLeftPanelWidth] = createSignal(300); | ||
| const [isDragging, setIsDragging] = createSignal(false); | ||
| const [shellRootEl, setShellRootEl] = createSignal(null); | ||
| const [slotHeightPx, setSlotHeightPx] = createSignal(void 0); | ||
| const selectedInstance = createMemo(() => { | ||
| const key = selectedKey(); | ||
| if (!key) return null; | ||
| for (const group of UTIL_GROUPS) { | ||
| const instance = utilState()[group.key].find((inst) => inst.key === key); | ||
| if (instance) return { | ||
| instance, | ||
| type: group.displayName | ||
| }; | ||
| } | ||
| return null; | ||
| }); | ||
| let dragStartX = 0; | ||
| let dragStartWidth = 0; | ||
| const handleMouseDown = (e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setIsDragging(true); | ||
| document.body.style.cursor = "col-resize"; | ||
| document.body.style.userSelect = "none"; | ||
| dragStartX = e.clientX; | ||
| dragStartWidth = leftPanelWidth(); | ||
| }; | ||
| const handleMouseMove = (e) => { | ||
| if (!isDragging()) return; | ||
| e.preventDefault(); | ||
| const deltaX = e.clientX - dragStartX; | ||
| setLeftPanelWidth(Math.max(150, Math.min(800, dragStartWidth + deltaX))); | ||
| }; | ||
| const handleMouseUp = () => { | ||
| setIsDragging(false); | ||
| document.body.style.cursor = ""; | ||
| document.body.style.userSelect = ""; | ||
| }; | ||
| onMount(() => { | ||
| document.addEventListener("mousemove", handleMouseMove); | ||
| document.addEventListener("mouseup", handleMouseUp); | ||
| }); | ||
| onCleanup(() => { | ||
| document.removeEventListener("mousemove", handleMouseMove); | ||
| document.removeEventListener("mouseup", handleMouseUp); | ||
| }); | ||
| /** | ||
| * TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex | ||
| * ancestors, so percentage height often never constrains our tree. Observing the | ||
| * outer `plugin-container-*` host (not the immediate React mount wrapper) and | ||
| * setting an explicit pixel height makes inner overflow-y regions scroll without | ||
| * capping to the mount div’s collapsed height. | ||
| */ | ||
| createEffect(() => { | ||
| const el = shellRootEl(); | ||
| if (!el || typeof ResizeObserver === "undefined") return; | ||
| const pluginSlot = resolvePluginHost(el); | ||
| if (!pluginSlot) return; | ||
| const ro = new ResizeObserver((entries) => { | ||
| const entry = entries[0]; | ||
| if (!entry) return; | ||
| const target = entry.target; | ||
| const h = target.clientHeight > 0 ? target.clientHeight : readResizeObserverBlockSize(entry); | ||
| if (h > 0) setSlotHeightPx(Math.round(h)); | ||
| }); | ||
| ro.observe(pluginSlot); | ||
| onCleanup(() => ro.disconnect()); | ||
| }); | ||
| return (() => { | ||
| var _el$ = _tmpl$2(); | ||
| use(setShellRootEl, _el$); | ||
| insert(_el$, createComponent(MainPanel, { | ||
| get ["class"]() { | ||
| return styles().mainPanelShell; | ||
| }, | ||
| get children() { | ||
| return [createComponent(Header, { get children() { | ||
| return createComponent(HeaderLogo, { | ||
| flavor: { | ||
| light: "#84cc16", | ||
| dark: "#84cc16" | ||
| }, | ||
| children: "TanStack Pacer" | ||
| }); | ||
| } }), (() => { | ||
| var _el$2 = _tmpl$(), _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild; | ||
| insert(_el$3, createComponent(UtilList, { | ||
| selectedKey, | ||
| setSelectedKey, | ||
| utilState | ||
| })); | ||
| _el$4.$$mousedown = handleMouseDown; | ||
| insert(_el$5, createComponent(DetailsPanel, { | ||
| selectedInstance, | ||
| utilState | ||
| }), null); | ||
| effect((_p$) => { | ||
| var _v$ = styles().mainContainer, _v$2 = styles().leftPanel, _v$3 = `${leftPanelWidth()}px`, _v$4 = `${styles().dragHandle} ${isDragging() ? "dragging" : ""}`, _v$5 = styles().rightPanel, _v$6 = styles().panelHeader; | ||
| _v$ !== _p$.e && className(_el$2, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$3, _p$.t = _v$2); | ||
| _v$3 !== _p$.a && setStyleProperty(_el$3, "width", _p$.a = _v$3); | ||
| _v$4 !== _p$.o && className(_el$4, _p$.o = _v$4); | ||
| _v$5 !== _p$.i && className(_el$5, _p$.i = _v$5); | ||
| _v$6 !== _p$.n && className(_el$6, _p$.n = _v$6); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0, | ||
| o: void 0, | ||
| i: void 0, | ||
| n: void 0 | ||
| }); | ||
| return _el$2; | ||
| })()]; | ||
| } | ||
| })); | ||
| effect((_p$) => { | ||
| var _v$7 = styles().shellRoot, _v$8 = { ...slotHeightPx() !== void 0 ? { | ||
| height: `${slotHeightPx()}px`, | ||
| "max-height": `${slotHeightPx()}px` | ||
| } : {} }; | ||
| _v$7 !== _p$.e && className(_el$, _p$.e = _v$7); | ||
| _p$.t = style(_el$, _v$8, _p$.t); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0 | ||
| }); | ||
| return _el$; | ||
| })(); | ||
| } | ||
| delegateEvents(["mousedown"]); | ||
| //#endregion | ||
| //#region src/components/index.tsx | ||
| function PacerDevtools(props) { | ||
| return createComponent(ThemeContextProvider, { | ||
| get theme() { | ||
| return props.theme; | ||
| }, | ||
| get children() { | ||
| return createComponent(PacerContextProvider, { get children() { | ||
| return createComponent(Shell, {}); | ||
| } }); | ||
| } | ||
| }); | ||
| } | ||
| //#endregion | ||
| export { PacerDevtools as default }; |
| import { createComponent, escape, ssr, ssrAttribute, ssrStyle, ssrStyleProperty } from "solid-js/web"; | ||
| import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, useTheme } from "@tanstack/devtools-ui"; | ||
| import { createStore, produce } from "solid-js/store"; | ||
| import { For, Show, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js"; | ||
| import { getPacerDevtoolsInstance, pacerEventClient } from "@tanstack/pacer/event-client"; | ||
| import * as goober from "goober"; | ||
| import { useStore } from "@tanstack/solid-store"; | ||
| import clsx from "clsx"; | ||
| import dayjs from "dayjs"; | ||
| import relativeTime from "dayjs/plugin/relativeTime.js"; | ||
| //#region src/PacerContextProvider.tsx | ||
| const initialPacerDevtoolsStore = { | ||
| asyncBatchers: [], | ||
| asyncDebouncers: [], | ||
| asyncQueuers: [], | ||
| asyncRateLimiters: [], | ||
| asyncThrottlers: [], | ||
| batchers: [], | ||
| debouncers: [], | ||
| queuers: [], | ||
| rateLimiters: [], | ||
| throttlers: [], | ||
| lastUpdatedByKey: {} | ||
| }; | ||
| const PacerDevtoolsContext = createContext([initialPacerDevtoolsStore, () => {}]); | ||
| /** | ||
| * Match TanStack Form devtools: subscribe with {@link pacerEventClient.on} per | ||
| * event suffix so the same `pacer:${suffix}` channel the library emits on is | ||
| * observed. `onAllPluginEvents` only sees `tanstack-devtools-global`, which is | ||
| * not always relayed the same way by the shell. | ||
| * | ||
| * `d-*` suffixes are used when the devtools panel pushes state back (see | ||
| * ActionButtons); those must update the panel store too. | ||
| */ | ||
| const PACER_DEVTOOLS_UTIL_EVENTS = [ | ||
| { | ||
| listKey: "asyncBatchers", | ||
| suffixes: ["AsyncBatcher", "d-AsyncBatcher"] | ||
| }, | ||
| { | ||
| listKey: "asyncDebouncers", | ||
| suffixes: ["AsyncDebouncer", "d-AsyncDebouncer"] | ||
| }, | ||
| { | ||
| listKey: "asyncQueuers", | ||
| suffixes: ["AsyncQueuer", "d-AsyncQueuer"] | ||
| }, | ||
| { | ||
| listKey: "asyncRateLimiters", | ||
| suffixes: ["AsyncRateLimiter", "d-AsyncRateLimiter"] | ||
| }, | ||
| { | ||
| listKey: "asyncThrottlers", | ||
| suffixes: ["AsyncThrottler", "d-AsyncThrottler"] | ||
| }, | ||
| { | ||
| listKey: "batchers", | ||
| suffixes: ["Batcher", "d-Batcher"] | ||
| }, | ||
| { | ||
| listKey: "debouncers", | ||
| suffixes: ["Debouncer", "d-Debouncer"] | ||
| }, | ||
| { | ||
| listKey: "queuers", | ||
| suffixes: ["Queuer", "d-Queuer"] | ||
| }, | ||
| { | ||
| listKey: "rateLimiters", | ||
| suffixes: ["RateLimiter", "d-RateLimiter"] | ||
| }, | ||
| { | ||
| listKey: "throttlers", | ||
| suffixes: ["Throttler", "d-Throttler"] | ||
| } | ||
| ]; | ||
| function PacerContextProvider(props) { | ||
| const [store, setStore] = createStore(initialPacerDevtoolsStore); | ||
| createEffect(() => { | ||
| const cleanups = []; | ||
| for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) for (const suffix of suffixes) cleanups.push(pacerEventClient.on(suffix, (e) => { | ||
| const key = e.payload.key; | ||
| if (!key) return; | ||
| const instance = getPacerDevtoolsInstance(key); | ||
| if (!instance || typeof instance !== "object") return; | ||
| setStore(produce((draft) => { | ||
| const list = draft[listKey]; | ||
| const index = list.findIndex((item) => item.key === key); | ||
| const inst = instance; | ||
| if (index !== -1) list[index] = inst; | ||
| else list.push(inst); | ||
| draft.lastUpdatedByKey[key] = Date.now(); | ||
| })); | ||
| })); | ||
| onCleanup(() => { | ||
| for (const cleanup of cleanups) cleanup(); | ||
| }); | ||
| }); | ||
| return createComponent(PacerDevtoolsContext.Provider, { | ||
| value: [store, setStore], | ||
| get children() { | ||
| return props.children; | ||
| } | ||
| }); | ||
| } | ||
| const usePacerDevtoolsContext = () => { | ||
| return useContext(PacerDevtoolsContext); | ||
| }; | ||
| const usePacerDevtoolsState = () => { | ||
| const [state] = usePacerDevtoolsContext(); | ||
| return state; | ||
| }; | ||
| //#endregion | ||
| //#region src/styles/tokens.ts | ||
| const tokens = { | ||
| colors: { | ||
| inherit: "inherit", | ||
| current: "currentColor", | ||
| transparent: "transparent", | ||
| black: "#000000", | ||
| white: "#ffffff", | ||
| neutral: { | ||
| 50: "#f9fafb", | ||
| 100: "#f2f4f7", | ||
| 200: "#eaecf0", | ||
| 300: "#d0d5dd", | ||
| 400: "#98a2b3", | ||
| 500: "#667085", | ||
| 600: "#475467", | ||
| 700: "#344054", | ||
| 800: "#1d2939", | ||
| 900: "#101828" | ||
| }, | ||
| darkGray: { | ||
| 50: "#525c7a", | ||
| 100: "#49536e", | ||
| 200: "#414962", | ||
| 300: "#394056", | ||
| 400: "#313749", | ||
| 500: "#292e3d", | ||
| 600: "#212530", | ||
| 700: "#191c24", | ||
| 800: "#111318", | ||
| 900: "#0b0d10" | ||
| }, | ||
| gray: { | ||
| 50: "#f9fafb", | ||
| 100: "#f2f4f7", | ||
| 200: "#eaecf0", | ||
| 300: "#d0d5dd", | ||
| 400: "#98a2b3", | ||
| 500: "#667085", | ||
| 600: "#475467", | ||
| 700: "#344054", | ||
| 800: "#1d2939", | ||
| 900: "#101828" | ||
| }, | ||
| blue: { | ||
| 25: "#F5FAFF", | ||
| 50: "#EFF8FF", | ||
| 100: "#D1E9FF", | ||
| 200: "#B2DDFF", | ||
| 300: "#84CAFF", | ||
| 400: "#53B1FD", | ||
| 500: "#2E90FA", | ||
| 600: "#1570EF", | ||
| 700: "#175CD3", | ||
| 800: "#1849A9", | ||
| 900: "#194185" | ||
| }, | ||
| green: { | ||
| 25: "#F6FEF9", | ||
| 50: "#ECFDF3", | ||
| 100: "#D1FADF", | ||
| 200: "#A6F4C5", | ||
| 300: "#6CE9A6", | ||
| 400: "#32D583", | ||
| 500: "#12B76A", | ||
| 600: "#039855", | ||
| 700: "#027A48", | ||
| 800: "#05603A", | ||
| 900: "#054F31" | ||
| }, | ||
| red: { | ||
| 50: "#fef2f2", | ||
| 100: "#fee2e2", | ||
| 200: "#fecaca", | ||
| 300: "#fca5a5", | ||
| 400: "#f87171", | ||
| 500: "#ef4444", | ||
| 600: "#dc2626", | ||
| 700: "#b91c1c", | ||
| 800: "#991b1b", | ||
| 900: "#7f1d1d", | ||
| 950: "#450a0a" | ||
| }, | ||
| yellow: { | ||
| 25: "#FFFCF5", | ||
| 50: "#FFFAEB", | ||
| 100: "#FEF0C7", | ||
| 200: "#FEDF89", | ||
| 300: "#FEC84B", | ||
| 400: "#FDB022", | ||
| 500: "#F79009", | ||
| 600: "#DC6803", | ||
| 700: "#B54708", | ||
| 800: "#93370D", | ||
| 900: "#7A2E0E" | ||
| }, | ||
| purple: { | ||
| 25: "#FAFAFF", | ||
| 50: "#F4F3FF", | ||
| 100: "#EBE9FE", | ||
| 200: "#D9D6FE", | ||
| 300: "#BDB4FE", | ||
| 400: "#9B8AFB", | ||
| 500: "#7A5AF8", | ||
| 600: "#6938EF", | ||
| 700: "#5925DC", | ||
| 800: "#4A1FB8", | ||
| 900: "#3E1C96" | ||
| }, | ||
| teal: { | ||
| 25: "#F6FEFC", | ||
| 50: "#F0FDF9", | ||
| 100: "#CCFBEF", | ||
| 200: "#99F6E0", | ||
| 300: "#5FE9D0", | ||
| 400: "#2ED3B7", | ||
| 500: "#15B79E", | ||
| 600: "#0E9384", | ||
| 700: "#107569", | ||
| 800: "#125D56", | ||
| 900: "#134E48" | ||
| }, | ||
| pink: { | ||
| 25: "#fdf2f8", | ||
| 50: "#fce7f3", | ||
| 100: "#fbcfe8", | ||
| 200: "#f9a8d4", | ||
| 300: "#f472b6", | ||
| 400: "#ec4899", | ||
| 500: "#db2777", | ||
| 600: "#be185d", | ||
| 700: "#9d174d", | ||
| 800: "#831843", | ||
| 900: "#500724" | ||
| }, | ||
| cyan: { | ||
| 25: "#ecfeff", | ||
| 50: "#cffafe", | ||
| 100: "#a5f3fc", | ||
| 200: "#67e8f9", | ||
| 300: "#22d3ee", | ||
| 400: "#06b6d4", | ||
| 500: "#0891b2", | ||
| 600: "#0e7490", | ||
| 700: "#155e75", | ||
| 800: "#164e63", | ||
| 900: "#083344" | ||
| } | ||
| }, | ||
| alpha: { | ||
| 100: "ff", | ||
| 90: "e5", | ||
| 80: "cc", | ||
| 70: "b3", | ||
| 60: "99", | ||
| 50: "80", | ||
| 40: "66", | ||
| 30: "4d", | ||
| 20: "33", | ||
| 10: "1a", | ||
| 0: "00" | ||
| }, | ||
| font: { | ||
| size: { | ||
| "2xs": "calc(var(--tsrd-font-size) * 0.625)", | ||
| xs: "calc(var(--tsrd-font-size) * 0.75)", | ||
| sm: "calc(var(--tsrd-font-size) * 0.875)", | ||
| md: "var(--tsrd-font-size)", | ||
| lg: "calc(var(--tsrd-font-size) * 1.125)", | ||
| xl: "calc(var(--tsrd-font-size) * 1.25)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 1.5)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 1.875)", | ||
| "4xl": "calc(var(--tsrd-font-size) * 2.25)", | ||
| "5xl": "calc(var(--tsrd-font-size) * 3)", | ||
| "6xl": "calc(var(--tsrd-font-size) * 3.75)", | ||
| "7xl": "calc(var(--tsrd-font-size) * 4.5)", | ||
| "8xl": "calc(var(--tsrd-font-size) * 6)", | ||
| "9xl": "calc(var(--tsrd-font-size) * 8)" | ||
| }, | ||
| lineHeight: { | ||
| "3xs": "calc(var(--tsrd-font-size) * 0.75)", | ||
| "2xs": "calc(var(--tsrd-font-size) * 0.875)", | ||
| xs: "calc(var(--tsrd-font-size) * 1)", | ||
| sm: "calc(var(--tsrd-font-size) * 1.25)", | ||
| md: "calc(var(--tsrd-font-size) * 1.5)", | ||
| lg: "calc(var(--tsrd-font-size) * 1.75)", | ||
| xl: "calc(var(--tsrd-font-size) * 2)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 2.25)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 2.5)", | ||
| "4xl": "calc(var(--tsrd-font-size) * 2.75)", | ||
| "5xl": "calc(var(--tsrd-font-size) * 3)", | ||
| "6xl": "calc(var(--tsrd-font-size) * 3.25)", | ||
| "7xl": "calc(var(--tsrd-font-size) * 3.5)", | ||
| "8xl": "calc(var(--tsrd-font-size) * 3.75)", | ||
| "9xl": "calc(var(--tsrd-font-size) * 4)" | ||
| }, | ||
| weight: { | ||
| thin: "100", | ||
| extralight: "200", | ||
| light: "300", | ||
| normal: "400", | ||
| medium: "500", | ||
| semibold: "600", | ||
| bold: "700", | ||
| extrabold: "800", | ||
| black: "900" | ||
| }, | ||
| fontFamily: { | ||
| sans: "ui-sans-serif, Inter, system-ui, sans-serif, sans-serif", | ||
| mono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace` | ||
| } | ||
| }, | ||
| breakpoints: { | ||
| xs: "320px", | ||
| sm: "640px", | ||
| md: "768px", | ||
| lg: "1024px", | ||
| xl: "1280px", | ||
| "2xl": "1536px" | ||
| }, | ||
| border: { radius: { | ||
| none: "0px", | ||
| xs: "calc(var(--tsrd-font-size) * 0.125)", | ||
| sm: "calc(var(--tsrd-font-size) * 0.25)", | ||
| md: "calc(var(--tsrd-font-size) * 0.375)", | ||
| lg: "calc(var(--tsrd-font-size) * 0.5)", | ||
| xl: "calc(var(--tsrd-font-size) * 0.75)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 1)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 1.5)", | ||
| full: "9999px" | ||
| } }, | ||
| size: { | ||
| 0: "0px", | ||
| .25: "calc(var(--tsrd-font-size) * 0.0625)", | ||
| .5: "calc(var(--tsrd-font-size) * 0.125)", | ||
| 1: "calc(var(--tsrd-font-size) * 0.25)", | ||
| 1.5: "calc(var(--tsrd-font-size) * 0.375)", | ||
| 2: "calc(var(--tsrd-font-size) * 0.5)", | ||
| 2.5: "calc(var(--tsrd-font-size) * 0.625)", | ||
| 3: "calc(var(--tsrd-font-size) * 0.75)", | ||
| 3.5: "calc(var(--tsrd-font-size) * 0.875)", | ||
| 4: "calc(var(--tsrd-font-size) * 1)", | ||
| 4.5: "calc(var(--tsrd-font-size) * 1.125)", | ||
| 5: "calc(var(--tsrd-font-size) * 1.25)", | ||
| 5.5: "calc(var(--tsrd-font-size) * 1.375)", | ||
| 6: "calc(var(--tsrd-font-size) * 1.5)", | ||
| 6.5: "calc(var(--tsrd-font-size) * 1.625)", | ||
| 7: "calc(var(--tsrd-font-size) * 1.75)", | ||
| 8: "calc(var(--tsrd-font-size) * 2)", | ||
| 9: "calc(var(--tsrd-font-size) * 2.25)", | ||
| 10: "calc(var(--tsrd-font-size) * 2.5)", | ||
| 11: "calc(var(--tsrd-font-size) * 2.75)", | ||
| 12: "calc(var(--tsrd-font-size) * 3)", | ||
| 14: "calc(var(--tsrd-font-size) * 3.5)", | ||
| 16: "calc(var(--tsrd-font-size) * 4)", | ||
| 20: "calc(var(--tsrd-font-size) * 5)", | ||
| 24: "calc(var(--tsrd-font-size) * 6)", | ||
| 28: "calc(var(--tsrd-font-size) * 7)", | ||
| 32: "calc(var(--tsrd-font-size) * 8)", | ||
| 36: "calc(var(--tsrd-font-size) * 9)", | ||
| 40: "calc(var(--tsrd-font-size) * 10)", | ||
| 44: "calc(var(--tsrd-font-size) * 11)", | ||
| 48: "calc(var(--tsrd-font-size) * 12)", | ||
| 52: "calc(var(--tsrd-font-size) * 13)", | ||
| 56: "calc(var(--tsrd-font-size) * 14)", | ||
| 60: "calc(var(--tsrd-font-size) * 15)", | ||
| 64: "calc(var(--tsrd-font-size) * 16)", | ||
| 72: "calc(var(--tsrd-font-size) * 18)", | ||
| 80: "calc(var(--tsrd-font-size) * 20)", | ||
| 96: "calc(var(--tsrd-font-size) * 24)" | ||
| }, | ||
| shadow: { | ||
| xs: (_ = "rgb(0 0 0 / 0.1)") => `0 1px 2px 0 rgb(0 0 0 / 0.05)`, | ||
| sm: (color = "rgb(0 0 0 / 0.1)") => `0 1px 3px 0 ${color}, 0 1px 2px -1px ${color}`, | ||
| md: (color = "rgb(0 0 0 / 0.1)") => `0 4px 6px -1px ${color}, 0 2px 4px -2px ${color}`, | ||
| lg: (color = "rgb(0 0 0 / 0.1)") => `0 10px 15px -3px ${color}, 0 4px 6px -4px ${color}`, | ||
| xl: (color = "rgb(0 0 0 / 0.1)") => `0 20px 25px -5px ${color}, 0 8px 10px -6px ${color}`, | ||
| "2xl": (color = "rgb(0 0 0 / 0.25)") => `0 25px 50px -12px ${color}`, | ||
| inner: (color = "rgb(0 0 0 / 0.05)") => `inset 0 2px 4px 0 ${color}`, | ||
| none: () => `none` | ||
| }, | ||
| zIndices: { | ||
| hide: -1, | ||
| auto: "auto", | ||
| base: 0, | ||
| docked: 10, | ||
| dropdown: 1e3, | ||
| sticky: 1100, | ||
| banner: 1200, | ||
| overlay: 1300, | ||
| modal: 1400, | ||
| popover: 1500, | ||
| skipLink: 1600, | ||
| toast: 1700, | ||
| tooltip: 1800 | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/styles/use-styles.ts | ||
| const stylesFactory = (theme) => { | ||
| const { colors, font, size, alpha, border } = tokens; | ||
| const { fontFamily, size: fontSize } = font; | ||
| const css = goober.css; | ||
| const t = (light, dark) => theme === "light" ? light : dark; | ||
| return { | ||
| shellRoot: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| box-sizing: border-box; | ||
| width: 100%; | ||
| height: 100%; | ||
| min-height: 0; | ||
| overflow: hidden; | ||
| `, | ||
| mainPanelShell: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| flex: 1 1 0%; | ||
| min-height: 0; | ||
| overflow: hidden !important; | ||
| `, | ||
| mainContainer: css` | ||
| display: flex; | ||
| flex: 1 1 0%; | ||
| min-height: 0; | ||
| overflow: hidden; | ||
| padding: ${size[2]}; | ||
| padding-top: 0; | ||
| margin-top: ${size[2]}; | ||
| `, | ||
| dragHandle: css` | ||
| width: 8px; | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| cursor: col-resize; | ||
| position: relative; | ||
| transition: all 0.2s ease; | ||
| user-select: none; | ||
| pointer-events: all; | ||
| margin: 0 ${size[1]}; | ||
| border-radius: 2px; | ||
| &:hover { | ||
| background: ${t(colors.blue[600], colors.blue[500])}; | ||
| margin: 0 ${size[1]}; | ||
| } | ||
| &.dragging { | ||
| background: ${t(colors.blue[700], colors.blue[600])}; | ||
| margin: 0 ${size[1]}; | ||
| } | ||
| &::after { | ||
| content: ''; | ||
| position: absolute; | ||
| top: 50%; | ||
| left: 50%; | ||
| transform: translate(-50%, -50%); | ||
| width: 2px; | ||
| height: 20px; | ||
| background: ${t(colors.gray[400], colors.darkGray[400])}; | ||
| border-radius: 1px; | ||
| pointer-events: none; | ||
| } | ||
| &:hover::after, | ||
| &.dragging::after { | ||
| background: ${t(colors.blue[500], colors.blue[300])}; | ||
| } | ||
| `, | ||
| leftPanel: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| min-height: 0; | ||
| flex-shrink: 0; | ||
| `, | ||
| rightPanel: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| min-height: 0; | ||
| flex: 1; | ||
| `, | ||
| panelHeader: css` | ||
| font-size: ${fontSize.md}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.blue[700], colors.blue[400])}; | ||
| padding: ${size[2]}; | ||
| border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| flex-shrink: 0; | ||
| `, | ||
| utilList: css` | ||
| flex: 1; | ||
| overflow-y: auto; | ||
| padding: ${size[1]}; | ||
| min-height: 0; | ||
| `, | ||
| utilGroup: css` | ||
| margin-bottom: ${size[2]}; | ||
| `, | ||
| utilGroupHeader: css` | ||
| font-size: ${fontSize.xs}; | ||
| font-weight: ${font.weight.semibold}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| margin-bottom: ${size[1]}; | ||
| padding: ${size[1]} ${size[2]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| `, | ||
| utilRow: css` | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| padding: ${size[2]}; | ||
| margin-bottom: ${size[1]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| cursor: pointer; | ||
| transition: all 0.2s ease; | ||
| border: 1px solid transparent; | ||
| &:hover { | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-color: ${t(colors.gray[400], colors.darkGray[500])}; | ||
| } | ||
| `, | ||
| utilRowSelected: css` | ||
| background: ${t(colors.blue[100], colors.blue[900] + alpha[20])}; | ||
| border-color: ${t(colors.blue[600], colors.blue[500])}; | ||
| box-shadow: 0 0 0 1px | ||
| ${t(colors.blue[600] + alpha[30], colors.blue[500] + alpha[30])}; | ||
| `, | ||
| utilKey: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| flex: 1; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| `, | ||
| utilStatus: css` | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| padding: ${size[1]} ${size[1]}; | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-radius: ${border.radius.sm}; | ||
| margin-left: ${size[1]}; | ||
| `, | ||
| stateDetails: css` | ||
| flex: 1; | ||
| overflow-y: auto; | ||
| padding: ${size[2]}; | ||
| min-height: 0; | ||
| `, | ||
| stateHeader: css` | ||
| margin-bottom: ${size[2]}; | ||
| padding-bottom: ${size[2]}; | ||
| border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| `, | ||
| stateTitle: css` | ||
| font-size: ${fontSize.md}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.blue[700], colors.blue[400])}; | ||
| margin-bottom: ${size[1]}; | ||
| `, | ||
| stateKey: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| word-break: break-all; | ||
| `, | ||
| stateContent: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[2]}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| `, | ||
| detailsGrid: css` | ||
| display: grid; | ||
| grid-template-columns: 1fr; | ||
| gap: ${size[2]}; | ||
| align-items: start; | ||
| `, | ||
| detailSection: css` | ||
| background: ${t(colors.white, colors.darkGray[700])}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[2]}; | ||
| `, | ||
| detailSectionHeader: css` | ||
| font-size: ${fontSize.sm}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.gray[800], colors.gray[200])}; | ||
| margin-bottom: ${size[1]}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.04em; | ||
| `, | ||
| actionsRow: css` | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: ${size[2]}; | ||
| `, | ||
| actionButton: css` | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: ${size[1]}; | ||
| padding: ${size[1]} ${size[2]}; | ||
| border-radius: ${border.radius.md}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[500])}; | ||
| background: ${t(colors.gray[200], colors.darkGray[600])}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| font-size: ${fontSize.xs}; | ||
| cursor: pointer; | ||
| user-select: none; | ||
| transition: | ||
| background 0.15s, | ||
| border-color 0.15s; | ||
| &:hover { | ||
| background: ${t(colors.gray[300], colors.darkGray[500])}; | ||
| border-color: ${t(colors.gray[400], colors.darkGray[400])}; | ||
| } | ||
| &:disabled { | ||
| opacity: 0.5; | ||
| cursor: not-allowed; | ||
| &:hover { | ||
| background: ${t(colors.gray[200], colors.darkGray[600])}; | ||
| border-color: ${t(colors.gray[300], colors.darkGray[500])}; | ||
| } | ||
| } | ||
| `, | ||
| actionDotBlue: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.blue[400]}; | ||
| `, | ||
| actionDotGreen: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.green[400]}; | ||
| `, | ||
| actionDotRed: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.red[400]}; | ||
| `, | ||
| actionDotYellow: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.yellow[400]}; | ||
| `, | ||
| actionDotOrange: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.pink[400]}; | ||
| `, | ||
| actionDotPurple: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.purple[400]}; | ||
| `, | ||
| infoGrid: css` | ||
| display: grid; | ||
| grid-template-columns: auto 1fr; | ||
| gap: ${size[1]}; | ||
| row-gap: ${size[1]}; | ||
| align-items: center; | ||
| `, | ||
| infoLabel: css` | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| font-size: ${fontSize.xs}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| `, | ||
| infoValueMono: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| word-break: break-all; | ||
| `, | ||
| noSelection: css` | ||
| flex: 1; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| color: ${t(colors.gray[500], colors.gray[500])}; | ||
| font-style: italic; | ||
| text-align: center; | ||
| padding: ${size[4]}; | ||
| `, | ||
| sectionContainer: css` | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: ${size[4]}; | ||
| `, | ||
| section: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| box-shadow: ${tokens.shadow.md(t(colors.gray[400] + alpha[80], colors.black + alpha[80]))}; | ||
| padding: ${size[4]}; | ||
| margin-bottom: ${size[4]}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| min-width: 0; | ||
| max-width: 33%; | ||
| max-height: fit-content; | ||
| `, | ||
| sectionHeader: css` | ||
| font-size: ${fontSize.lg}; | ||
| font-weight: ${font.weight.bold}; | ||
| margin-bottom: ${size[2]}; | ||
| color: ${t(colors.blue[600], colors.blue[400])}; | ||
| letter-spacing: 0.01em; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: ${size[2]}; | ||
| `, | ||
| sectionEmpty: css` | ||
| color: ${t(colors.gray[500], colors.gray[500])}; | ||
| font-size: ${fontSize.sm}; | ||
| font-style: italic; | ||
| margin: ${size[2]} 0; | ||
| `, | ||
| instanceList: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: ${size[2]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| `, | ||
| instanceCard: css` | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[3]}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| font-size: ${fontSize.sm}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| font-family: ${fontFamily.mono}; | ||
| overflow-x: auto; | ||
| transition: | ||
| box-shadow 0.3s, | ||
| background 0.3s; | ||
| ` | ||
| }; | ||
| }; | ||
| function useStyles() { | ||
| const { theme } = useTheme(); | ||
| const [styles, setStyles] = createSignal(stylesFactory(theme())); | ||
| createEffect(() => { | ||
| setStyles(stylesFactory(theme())); | ||
| }); | ||
| return styles; | ||
| } | ||
| //#endregion | ||
| //#region src/components/util-groups.ts | ||
| const UTIL_GROUPS = [ | ||
| { | ||
| key: "debouncers", | ||
| label: "Debouncers", | ||
| displayName: "Debouncer" | ||
| }, | ||
| { | ||
| key: "throttlers", | ||
| label: "Throttlers", | ||
| displayName: "Throttler" | ||
| }, | ||
| { | ||
| key: "batchers", | ||
| label: "Batchers", | ||
| displayName: "Batcher" | ||
| }, | ||
| { | ||
| key: "queuers", | ||
| label: "Queuers", | ||
| displayName: "Queuer" | ||
| }, | ||
| { | ||
| key: "rateLimiters", | ||
| label: "Rate Limiters", | ||
| displayName: "Rate Limiter" | ||
| }, | ||
| { | ||
| key: "asyncDebouncers", | ||
| label: "Async Debouncers", | ||
| displayName: "Async Debouncer" | ||
| }, | ||
| { | ||
| key: "asyncThrottlers", | ||
| label: "Async Throttlers", | ||
| displayName: "Async Throttler" | ||
| }, | ||
| { | ||
| key: "asyncBatchers", | ||
| label: "Async Batchers", | ||
| displayName: "Async Batcher" | ||
| }, | ||
| { | ||
| key: "asyncQueuers", | ||
| label: "Async Queuers", | ||
| displayName: "Async Queuer" | ||
| }, | ||
| { | ||
| key: "asyncRateLimiters", | ||
| label: "Async Rate Limiters", | ||
| displayName: "Async Rate Limiter" | ||
| } | ||
| ]; | ||
| //#endregion | ||
| //#region src/components/UtilList.tsx | ||
| var _tmpl$$4 = [ | ||
| "<div", | ||
| ">", | ||
| "</div>" | ||
| ], _tmpl$2$3 = [ | ||
| "<div", | ||
| "><div", | ||
| ">", | ||
| "</div>", | ||
| "</div>" | ||
| ], _tmpl$3$2 = [ | ||
| "<div", | ||
| "><div", | ||
| ">", | ||
| "</div><div", | ||
| ">", | ||
| "</div></div>" | ||
| ]; | ||
| function UtilList(props) { | ||
| const styles = useStyles(); | ||
| return ssr(_tmpl$$4, ssrAttribute("class", escape(styles().utilList, true), false), escape(createComponent(For, { | ||
| each: UTIL_GROUPS, | ||
| children: (group) => props.utilState()[group.key].length > 0 && ssr(_tmpl$2$3, ssrAttribute("class", escape(styles().utilGroup, true), false), ssrAttribute("class", escape(styles().utilGroupHeader, true), false), escape(group.label), escape(createComponent(For, { | ||
| get each() { | ||
| return props.utilState()[group.key]; | ||
| }, | ||
| children: (instance) => { | ||
| const status = (() => { | ||
| try { | ||
| const statusAccessor = useStore(instance.store, (s) => s.status); | ||
| return () => statusAccessor() ?? "unknown"; | ||
| } catch { | ||
| return () => "unknown"; | ||
| } | ||
| })(); | ||
| return ssr(_tmpl$3$2, ssrAttribute("class", escape(clsx(styles().utilRow, props.selectedKey() === instance.key && styles().utilRowSelected), true), false), ssrAttribute("class", escape(styles().utilKey, true), false), escape(instance.key), ssrAttribute("class", escape(styles().utilStatus, true), false), escape(status())); | ||
| } | ||
| }))) | ||
| }))); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/read-pacer-store-state.ts | ||
| /** TanStack Store (`@tanstack/store`) used by pacer utils. */ | ||
| function isPacerUtilTanStackStore(x) { | ||
| return typeof x === "object" && x !== null && "subscribe" in x && typeof x.subscribe === "function" && "get" in x && typeof x.get === "function"; | ||
| } | ||
| /** | ||
| * Core pacer utils expose TanStack `Store` (`get()` / `state`). React hooks may | ||
| * also expose a reactive `state` field. Use this for devtools UI that needs a | ||
| * plain snapshot (e.g. JsonTree). | ||
| */ | ||
| function getPacerUtilStoreState(instance) { | ||
| const store = instance.store; | ||
| if (store && typeof store.get === "function") return store.get(); | ||
| if (store && store.state !== void 0) return store.state; | ||
| return instance.state; | ||
| } | ||
| //#endregion | ||
| //#region src/components/ActionButtons.tsx | ||
| var _tmpl$$3 = ["<div", ">No actions available for this util</div>"], _tmpl$2$2 = [ | ||
| "<div", | ||
| ">", | ||
| "", | ||
| "", | ||
| "", | ||
| "", | ||
| "", | ||
| "</div>" | ||
| ], _tmpl$3$1 = [ | ||
| "<button", | ||
| "><span", | ||
| "></span>", | ||
| "</button>" | ||
| ], _tmpl$4 = [ | ||
| "<button", | ||
| "", | ||
| "><span", | ||
| "></span>Flush</button>" | ||
| ], _tmpl$5 = [ | ||
| "<button", | ||
| "", | ||
| "><span", | ||
| "></span>Cancel</button>" | ||
| ], _tmpl$6 = [ | ||
| "<button", | ||
| "><span", | ||
| "></span>Reset</button>" | ||
| ], _tmpl$7 = [ | ||
| "<button", | ||
| "", | ||
| "><span", | ||
| "></span>Clear</button>" | ||
| ]; | ||
| function ActionButtons(props) { | ||
| const styles = useStyles(); | ||
| const utilInstance = props.instance; | ||
| const store = utilInstance?.store; | ||
| const stateAccessor = isPacerUtilTanStackStore(store) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => getPacerUtilStoreState(utilInstance); | ||
| const getState = () => stateAccessor(); | ||
| const hasPending = () => { | ||
| return "isPending" in getState(); | ||
| }; | ||
| const isPending = () => { | ||
| const u = getState(); | ||
| return "isPending" in u ? !!u.isPending : false; | ||
| }; | ||
| const isEmptyFlag = () => { | ||
| const u = getState(); | ||
| return "isEmpty" in u ? !!u.isEmpty : false; | ||
| }; | ||
| const hasFlush = typeof utilInstance.flush === "function"; | ||
| const hasCancel = typeof utilInstance.cancel === "function"; | ||
| const hasReset = typeof utilInstance.reset === "function"; | ||
| const hasClear = typeof utilInstance.clear === "function"; | ||
| const hasStart = typeof utilInstance.start === "function"; | ||
| const hasStop = typeof utilInstance.stop === "function"; | ||
| const hasStartStop = hasStart && hasStop; | ||
| const isRunning = () => { | ||
| const u = getState(); | ||
| return "isRunning" in u ? !!u.isRunning : true; | ||
| }; | ||
| if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) return ssr(_tmpl$$3, ssrAttribute("class", escape(styles().sectionEmpty, true), false)); | ||
| `${props.utilName}`; | ||
| const utilNameLower = props.utilName.toLowerCase(); | ||
| return ssr(_tmpl$2$2, ssrAttribute("class", escape(styles().actionsRow, true), false), hasPending() && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotBlue, true), false), isPending() ? "Restore Pending" : "Trigger Pending"), hasFlush && ssr(_tmpl$4, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotGreen, true), false)), hasCancel && ssr(_tmpl$5, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("debouncer") && !isPending() || utilNameLower.includes("throttler") && !isPending() || utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotRed, true), false)), hasReset && ssr(_tmpl$6, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotYellow, true), false)), hasClear && ssr(_tmpl$7, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", utilNameLower.includes("batcher") && isEmptyFlag() || utilNameLower.includes("queuer") && isEmptyFlag(), true), ssrAttribute("class", escape(styles().actionDotOrange, true), false)), hasStartStop && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotPurple, true), false), isRunning() ? "Stop" : "Start")); | ||
| } | ||
| //#endregion | ||
| //#region src/components/StateHeader.tsx | ||
| var _tmpl$$2 = [ | ||
| "<div", | ||
| "><div", | ||
| ">", | ||
| "</div><div style=\"", | ||
| "\"><div", | ||
| "><div", | ||
| ">Key</div><div", | ||
| ">", | ||
| "</div><div", | ||
| ">Last Updated</div><div", | ||
| ">", | ||
| " (", | ||
| ")</div></div><div", | ||
| " style=\"", | ||
| "\">", | ||
| "% reduction</div></div></div>" | ||
| ]; | ||
| dayjs.extend(relativeTime); | ||
| function reductionFromState(entry, state) { | ||
| if (!state) return 0; | ||
| const completedExecutions = entry.type.toLowerCase().includes("async") ? Number(state.settleCount) || 0 : Number(state.executionCount) || 0; | ||
| if (entry.type.toLowerCase().includes("batcher")) { | ||
| const totalItemsProcessed = Number(state.totalItemsProcessed) || 0; | ||
| if (totalItemsProcessed === 0) return 0; | ||
| return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100); | ||
| } | ||
| let requestCount = 0; | ||
| if (state.maybeExecuteCount !== void 0) requestCount = Number(state.maybeExecuteCount) || 0; | ||
| else if (state.addItemCount !== void 0) requestCount = Number(state.addItemCount) || 0; | ||
| else return 0; | ||
| if (requestCount === 0) return 0; | ||
| const reduction = requestCount - completedExecutions; | ||
| return Math.round(reduction / requestCount * 100); | ||
| } | ||
| function StateHeaderInner(props) { | ||
| const key = props.entry.instance.key; | ||
| const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now(); | ||
| const getRelativeTime = () => { | ||
| const at = updatedAt(); | ||
| const diffMs = props.now() - at; | ||
| const diffSeconds = Math.floor(diffMs / 1e3); | ||
| if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`; | ||
| return dayjs(at).fromNow(); | ||
| }; | ||
| const store = props.entry.instance?.store; | ||
| const stateAccessor = isPacerUtilTanStackStore(store) ? useStore(store, (s) => s !== null && s !== void 0 && typeof s === "object" ? s : {}) : () => getPacerUtilStoreState(props.entry.instance); | ||
| const reductionPercentage = () => reductionFromState(props.entry, stateAccessor()); | ||
| return ssr(_tmpl$$2, ssrAttribute("class", escape(props.styles.stateHeader, true), false), ssrAttribute("class", escape(props.styles.stateTitle, true), false), escape(props.entry.type), ssrStyleProperty("display:", "flex") + ssrStyleProperty(";align-items:", "center") + ssrStyleProperty(";gap:", "16px"), ssrAttribute("class", escape(props.styles.infoGrid, true), false), ssrAttribute("class", escape(props.styles.infoLabel, true), false), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), escape(key), ssrAttribute("class", escape(props.styles.infoLabel, true), false), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), escape(new Date(updatedAt()).toLocaleTimeString()), escape(getRelativeTime()), ssrAttribute("class", escape(props.styles.infoValueMono, true), false), ssrStyleProperty("margin-left:", "auto") + ssrStyleProperty(";font-weight:", "bold"), escape(reductionPercentage())); | ||
| } | ||
| function StateHeader(props) { | ||
| const styles = useStyles(); | ||
| const [now, setNow] = createSignal(Date.now()); | ||
| onMount(() => { | ||
| const interval = setInterval(() => { | ||
| setNow(Date.now()); | ||
| }, 1e3); | ||
| onCleanup(() => { | ||
| clearInterval(interval); | ||
| }); | ||
| }); | ||
| return createComponent(Show, { | ||
| get when() { | ||
| return props.selectedInstance(); | ||
| }, | ||
| children: (entry) => createComponent(StateHeaderInner, { | ||
| get entry() { | ||
| return entry(); | ||
| }, | ||
| get styles() { | ||
| return styles(); | ||
| }, | ||
| now, | ||
| get utilState() { | ||
| return props.utilState; | ||
| } | ||
| }) | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/components/UtilStateJsonTree.tsx | ||
| /** | ||
| * Subscribes to the util's TanStack Store so the tree updates live (same | ||
| * idea as {@link UtilList} status). A plain snapshot would only refresh when the | ||
| * devtools shell re-renders, which does not happen on nested store updates. | ||
| */ | ||
| function UtilStateJsonTree(props) { | ||
| const store = props.instance?.store; | ||
| if (isPacerUtilTanStackStore(store)) { | ||
| const snapshot = useStore(store, (s) => s); | ||
| return createComponent(JsonTree, { get value() { | ||
| return snapshot(); | ||
| } }); | ||
| } | ||
| return createComponent(JsonTree, { get value() { | ||
| return getPacerUtilStoreState(props.instance); | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/components/DetailsPanel.tsx | ||
| var _tmpl$$1 = [ | ||
| "<div", | ||
| ">", | ||
| "</div>" | ||
| ], _tmpl$2$1 = ["<div", ">Select a util from the left panel to view its state</div>"], _tmpl$3 = [ | ||
| "<div", | ||
| "><div", | ||
| "><div", | ||
| ">Actions</div>", | ||
| "</div><div", | ||
| "><div", | ||
| ">State</div><div", | ||
| ">", | ||
| "</div></div><div", | ||
| "><div", | ||
| ">Options</div><div", | ||
| ">", | ||
| "</div></div></div>" | ||
| ]; | ||
| function DetailsPanel(props) { | ||
| const styles = useStyles(); | ||
| return ssr(_tmpl$$1, ssrAttribute("class", escape(styles().stateDetails, true), false), (() => { | ||
| const entry = props.selectedInstance(); | ||
| if (!entry) return ssr(_tmpl$2$1, ssrAttribute("class", escape(styles().noSelection, true), false)); | ||
| return escape([createComponent(StateHeader, { | ||
| get selectedInstance() { | ||
| return props.selectedInstance; | ||
| }, | ||
| get utilState() { | ||
| return props.utilState; | ||
| } | ||
| }), ssr(_tmpl$3, ssrAttribute("class", escape(styles().detailsGrid, true), false), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), escape(createComponent(ActionButtons, { | ||
| get instance() { | ||
| return entry.instance; | ||
| }, | ||
| get utilName() { | ||
| return entry.type; | ||
| } | ||
| })), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(UtilStateJsonTree, { get instance() { | ||
| return entry.instance; | ||
| } })), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(JsonTree, { get value() { | ||
| return entry.instance.options; | ||
| } })))]); | ||
| })()); | ||
| } | ||
| //#endregion | ||
| //#region src/components/Shell.tsx | ||
| var _tmpl$ = [ | ||
| "<div", | ||
| "><div", | ||
| " style=\"", | ||
| "\">", | ||
| "</div><div class=\"", | ||
| "\"></div><div", | ||
| " style=\"", | ||
| "\"><div", | ||
| ">Details</div>", | ||
| "</div></div>" | ||
| ], _tmpl$2 = [ | ||
| "<div", | ||
| " style=\"", | ||
| "\">", | ||
| "</div>" | ||
| ]; | ||
| function readResizeObserverBlockSize(entry) { | ||
| const [box] = entry.borderBoxSize; | ||
| if (box !== void 0) return box.blockSize; | ||
| return entry.contentRect.height; | ||
| } | ||
| /** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */ | ||
| function resolvePluginHost(el) { | ||
| const byId = el.closest("[id^=\"plugin-container-\"]"); | ||
| if (byId) return byId; | ||
| return el.parentElement?.parentElement?.parentElement ?? el.parentElement?.parentElement ?? el.parentElement; | ||
| } | ||
| function Shell() { | ||
| const styles = useStyles(); | ||
| const state = usePacerDevtoolsState(); | ||
| const utilState = () => state; | ||
| const [selectedKey, setSelectedKey] = createSignal(null); | ||
| const [leftPanelWidth, setLeftPanelWidth] = createSignal(300); | ||
| const [isDragging, setIsDragging] = createSignal(false); | ||
| const [shellRootEl, setShellRootEl] = createSignal(null); | ||
| const [slotHeightPx, setSlotHeightPx] = createSignal(void 0); | ||
| const selectedInstance = createMemo(() => { | ||
| const key = selectedKey(); | ||
| if (!key) return null; | ||
| for (const group of UTIL_GROUPS) { | ||
| const instance = utilState()[group.key].find((inst) => inst.key === key); | ||
| if (instance) return { | ||
| instance, | ||
| type: group.displayName | ||
| }; | ||
| } | ||
| return null; | ||
| }); | ||
| let dragStartX = 0; | ||
| let dragStartWidth = 0; | ||
| const handleMouseMove = (e) => { | ||
| if (!isDragging()) return; | ||
| e.preventDefault(); | ||
| const deltaX = e.clientX - dragStartX; | ||
| setLeftPanelWidth(Math.max(150, Math.min(800, dragStartWidth + deltaX))); | ||
| }; | ||
| const handleMouseUp = () => { | ||
| setIsDragging(false); | ||
| document.body.style.cursor = ""; | ||
| document.body.style.userSelect = ""; | ||
| }; | ||
| onMount(() => { | ||
| document.addEventListener("mousemove", handleMouseMove); | ||
| document.addEventListener("mouseup", handleMouseUp); | ||
| }); | ||
| onCleanup(() => { | ||
| document.removeEventListener("mousemove", handleMouseMove); | ||
| document.removeEventListener("mouseup", handleMouseUp); | ||
| }); | ||
| /** | ||
| * TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex | ||
| * ancestors, so percentage height often never constrains our tree. Observing the | ||
| * outer `plugin-container-*` host (not the immediate React mount wrapper) and | ||
| * setting an explicit pixel height makes inner overflow-y regions scroll without | ||
| * capping to the mount div’s collapsed height. | ||
| */ | ||
| createEffect(() => { | ||
| const el = shellRootEl(); | ||
| if (!el || typeof ResizeObserver === "undefined") return; | ||
| const pluginSlot = resolvePluginHost(el); | ||
| if (!pluginSlot) return; | ||
| const ro = new ResizeObserver((entries) => { | ||
| const entry = entries[0]; | ||
| if (!entry) return; | ||
| const target = entry.target; | ||
| const h = target.clientHeight > 0 ? target.clientHeight : readResizeObserverBlockSize(entry); | ||
| if (h > 0) setSlotHeightPx(Math.round(h)); | ||
| }); | ||
| ro.observe(pluginSlot); | ||
| onCleanup(() => ro.disconnect()); | ||
| }); | ||
| return ssr(_tmpl$2, ssrAttribute("class", escape(styles().shellRoot, true), false), ssrStyle({ ...slotHeightPx() !== void 0 ? { | ||
| height: `${slotHeightPx()}px`, | ||
| "max-height": `${slotHeightPx()}px` | ||
| } : {} }), escape(createComponent(MainPanel, { | ||
| get ["class"]() { | ||
| return styles().mainPanelShell; | ||
| }, | ||
| get children() { | ||
| return [createComponent(Header, { get children() { | ||
| return createComponent(HeaderLogo, { | ||
| flavor: { | ||
| light: "#84cc16", | ||
| dark: "#84cc16" | ||
| }, | ||
| children: "TanStack Pacer" | ||
| }); | ||
| } }), ssr(_tmpl$, ssrAttribute("class", escape(styles().mainContainer, true), false), ssrAttribute("class", escape(styles().leftPanel, true), false), ssrStyleProperty("width:", `${escape(leftPanelWidth(), true)}px`) + ssrStyleProperty(";min-width:", "150px") + ssrStyleProperty(";max-width:", "800px"), escape(createComponent(UtilList, { | ||
| selectedKey, | ||
| setSelectedKey, | ||
| utilState | ||
| })), `${escape(styles().dragHandle, true)} ${isDragging() ? "dragging" : ""}`, ssrAttribute("class", escape(styles().rightPanel, true), false), ssrStyleProperty("flex:", 1), ssrAttribute("class", escape(styles().panelHeader, true), false), escape(createComponent(DetailsPanel, { | ||
| selectedInstance, | ||
| utilState | ||
| })))]; | ||
| } | ||
| }))); | ||
| } | ||
| //#endregion | ||
| //#region src/components/index.tsx | ||
| function PacerDevtools(props) { | ||
| return createComponent(ThemeContextProvider, { | ||
| get theme() { | ||
| return props.theme; | ||
| }, | ||
| get children() { | ||
| return createComponent(PacerContextProvider, { get children() { | ||
| return createComponent(Shell, {}); | ||
| } }); | ||
| } | ||
| }); | ||
| } | ||
| //#endregion | ||
| export { PacerDevtools as default }; |
| import { JsonTree } from '@tanstack/devtools-ui' | ||
| import { useStore } from '@tanstack/solid-store' | ||
| import { | ||
| getPacerUtilStoreState, | ||
| isPacerUtilTanStackStore, | ||
| } from '../utils/read-pacer-store-state' | ||
| import type { JSX } from 'solid-js' | ||
| type UtilStateJsonTreeProps = { | ||
| instance: any | ||
| } | ||
| /** | ||
| * Subscribes to the util's TanStack Store so the tree updates live (same | ||
| * idea as {@link UtilList} status). A plain snapshot would only refresh when the | ||
| * devtools shell re-renders, which does not happen on nested store updates. | ||
| */ | ||
| export function UtilStateJsonTree(props: UtilStateJsonTreeProps): JSX.Element { | ||
| const store = props.instance?.store | ||
| if (isPacerUtilTanStackStore(store)) { | ||
| const snapshot = useStore(store as never, (s: unknown) => s) | ||
| return <JsonTree value={snapshot()} /> | ||
| } | ||
| return <JsonTree value={getPacerUtilStoreState(props.instance)} /> | ||
| } |
| /** TanStack Store (`@tanstack/store`) used by pacer utils. */ | ||
| export function isPacerUtilTanStackStore(x: unknown): x is { | ||
| subscribe: (cb: (s: unknown) => void) => { unsubscribe: () => void } | ||
| get: () => unknown | ||
| } { | ||
| return ( | ||
| typeof x === 'object' && | ||
| x !== null && | ||
| 'subscribe' in x && | ||
| typeof (x as { subscribe: unknown }).subscribe === 'function' && | ||
| 'get' in x && | ||
| typeof (x as { get: unknown }).get === 'function' | ||
| ) | ||
| } | ||
| /** | ||
| * Core pacer utils expose TanStack `Store` (`get()` / `state`). React hooks may | ||
| * also expose a reactive `state` field. Use this for devtools UI that needs a | ||
| * plain snapshot (e.g. JsonTree). | ||
| */ | ||
| export function getPacerUtilStoreState(instance: { | ||
| store?: { get?: () => unknown; state?: unknown } | ||
| state?: unknown | ||
| }): unknown { | ||
| const store = instance.store | ||
| if (store && typeof store.get === 'function') { | ||
| return store.get() | ||
| } | ||
| if (store && store.state !== undefined) { | ||
| return store.state | ||
| } | ||
| return instance.state | ||
| } |
+4
-8
@@ -0,14 +1,10 @@ | ||
| import { ClassType } from "@tanstack/devtools-utils/solid"; | ||
| //#region src/core.d.ts | ||
| interface PacerDevtoolsInit {} | ||
| declare const PacerDevtoolsCore$1: ClassType; | ||
| //#endregion | ||
| //#region src/index.d.ts | ||
| declare const PacerDevtoolsCore: new () => { | ||
| "__#private@#isMounted": boolean; | ||
| "__#private@#isMounting": boolean; | ||
| "__#private@#abortMount": boolean; | ||
| "__#private@#dispose"?: () => void; | ||
| mount<T extends HTMLElement>(el: T, props: TanStackDevtoolsPluginProps): Promise<void>; | ||
| unmount(): void; | ||
| }; | ||
| declare const PacerDevtoolsCore: typeof PacerDevtoolsCore$1; | ||
| //#endregion | ||
| export { PacerDevtoolsCore, type PacerDevtoolsInit }; |
+3
-1
| "use client"; | ||
| import { constructCoreClass } from "@tanstack/devtools-utils/solid"; | ||
| //#region src/core.tsx | ||
| const [PacerDevtoolsCore$1, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("./components-DDmnxkL2.js")); | ||
| const coreClasses = constructCoreClass(() => import("./components-CGEHIK6F.js")); | ||
| const PacerDevtoolsCore$1 = coreClasses[0]; | ||
| const PacerDevtoolsCoreNoOp = coreClasses[1]; | ||
| //#endregion | ||
@@ -6,0 +8,0 @@ //#region src/index.ts |
@@ -0,19 +1,7 @@ | ||
| import { ClassType } from "@tanstack/devtools-utils/solid"; | ||
| //#region src/core.d.ts | ||
| interface PacerDevtoolsInit {} | ||
| declare const PacerDevtoolsCore: new () => { | ||
| "__#private@#isMounted": boolean; | ||
| "__#private@#isMounting": boolean; | ||
| "__#private@#abortMount": boolean; | ||
| "__#private@#dispose"?: () => void; | ||
| mount<T extends HTMLElement>(el: T, props: TanStackDevtoolsPluginProps): Promise<void>; | ||
| unmount(): void; | ||
| }, PacerDevtoolsCoreNoOp: new () => { | ||
| mount<T extends HTMLElement>(_el: T, _props: TanStackDevtoolsPluginProps): Promise<void>; | ||
| unmount(): void; | ||
| "__#private@#isMounted": boolean; | ||
| "__#private@#isMounting": boolean; | ||
| "__#private@#abortMount": boolean; | ||
| "__#private@#dispose"?: () => void; | ||
| }; | ||
| declare const PacerDevtoolsCore: ClassType; | ||
| //#endregion | ||
| export { PacerDevtoolsCore, type PacerDevtoolsInit }; |
| "use client"; | ||
| import { constructCoreClass } from "@tanstack/devtools-utils/solid"; | ||
| //#region src/core.tsx | ||
| const [PacerDevtoolsCore, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("../components-DDmnxkL2.js")); | ||
| const coreClasses = constructCoreClass(() => import("../components-CGEHIK6F.js")); | ||
| const PacerDevtoolsCore = coreClasses[0]; | ||
| coreClasses[1]; | ||
| //#endregion | ||
| export { PacerDevtoolsCore }; |
| "use client"; | ||
| import { constructCoreClass } from "@tanstack/devtools-utils/solid"; | ||
| //#region src/core.tsx | ||
| const [PacerDevtoolsCore, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("../components-ByKT1JiI.js")); | ||
| const coreClasses = constructCoreClass(() => import("../components-vap4_VJT.js")); | ||
| const PacerDevtoolsCore = coreClasses[0]; | ||
| coreClasses[1]; | ||
| //#endregion | ||
| export { PacerDevtoolsCore }; |
+3
-1
| "use client"; | ||
| import { constructCoreClass } from "@tanstack/devtools-utils/solid"; | ||
| //#region src/core.tsx | ||
| const [PacerDevtoolsCore$1, PacerDevtoolsCoreNoOp] = constructCoreClass(() => import("./components-ByKT1JiI.js")); | ||
| const coreClasses = constructCoreClass(() => import("./components-vap4_VJT.js")); | ||
| const PacerDevtoolsCore$1 = coreClasses[0]; | ||
| const PacerDevtoolsCoreNoOp = coreClasses[1]; | ||
| //#endregion | ||
@@ -6,0 +8,0 @@ //#region src/index.ts |
+6
-6
| { | ||
| "name": "@tanstack/pacer-devtools", | ||
| "version": "1.1.5", | ||
| "version": "1.2.0", | ||
| "description": "Devtools for Pacer.", | ||
@@ -76,6 +76,6 @@ "author": "Tanner Linsley", | ||
| "@tanstack/devtools-utils": "^0.4.0", | ||
| "@tanstack/solid-store": "^0.8.1", | ||
| "@tanstack/solid-store": "^0.9.2", | ||
| "clsx": "^2.1.1", | ||
| "csstype": "^3.0.10", | ||
| "dayjs": "^1.11.19", | ||
| "csstype": "^3.2.3", | ||
| "dayjs": "^1.11.20", | ||
| "goober": "^2.1.18", | ||
@@ -86,4 +86,4 @@ "solid-js": "^1.9.11" | ||
| "rolldown-plugin-solid": "^0.2.1", | ||
| "tsdown": "^0.21.1", | ||
| "vite-plugin-solid": "^2.11.10" | ||
| "tsdown": "^0.21.4", | ||
| "vite-plugin-solid": "^2.11.11" | ||
| }, | ||
@@ -90,0 +90,0 @@ "scripts": { |
@@ -1,3 +0,8 @@ | ||
| import { pacerEventClient } from '@tanstack/pacer' | ||
| import { emitChange } from '@tanstack/pacer' | ||
| import { useStore } from '@tanstack/solid-store' | ||
| import { useStyles } from '../styles/use-styles' | ||
| import { | ||
| getPacerUtilStoreState, | ||
| isPacerUtilTanStackStore, | ||
| } from '../utils/read-pacer-store-state' | ||
| import type { PacerEventName } from '@tanstack/pacer' | ||
@@ -13,8 +18,27 @@ | ||
| const utilInstance = props.instance | ||
| const utilState = utilInstance?.store?.state | ||
| const hasPending = utilState && 'isPending' in utilState | ||
| const hasEmpty = utilState && 'isEmpty' in utilState | ||
| const isPending = hasPending ? !!utilState.isPending : false | ||
| const isEmpty = hasEmpty ? utilState.isEmpty : undefined | ||
| const store = utilInstance?.store | ||
| const stateAccessor = isPacerUtilTanStackStore(store) | ||
| ? useStore(store as never, (s: unknown) => | ||
| s !== null && s !== undefined && typeof s === 'object' | ||
| ? (s as Record<string, unknown>) | ||
| : {}, | ||
| ) | ||
| : () => getPacerUtilStoreState(utilInstance) as Record<string, unknown> | ||
| const getState = () => stateAccessor() | ||
| const hasPending = () => { | ||
| const u = getState() | ||
| return 'isPending' in u | ||
| } | ||
| const isPending = () => { | ||
| const u = getState() | ||
| return 'isPending' in u ? !!u.isPending : false | ||
| } | ||
| const isEmptyFlag = () => { | ||
| const u = getState() | ||
| return 'isEmpty' in u ? !!u.isEmpty : false | ||
| } | ||
| const hasFlush = typeof utilInstance.flush === 'function' | ||
@@ -28,5 +52,8 @@ const hasCancel = typeof utilInstance.cancel === 'function' | ||
| const isRunning = utilState?.isRunning ?? true | ||
| const isRunning = () => { | ||
| const u = getState() | ||
| return 'isRunning' in u ? !!u.isRunning : true | ||
| } | ||
| if (!hasPending && !hasFlush && !hasCancel && !hasReset && !hasClear) { | ||
| if (!hasPending() && !hasFlush && !hasCancel && !hasReset && !hasClear) { | ||
| return ( | ||
@@ -40,10 +67,11 @@ <div class={styles().sectionEmpty}> | ||
| const emitName = `d-${props.utilName}` as PacerEventName | ||
| const utilNameLower = props.utilName.toLowerCase() | ||
| return ( | ||
| <div class={styles().actionsRow}> | ||
| {hasPending && ( | ||
| {hasPending() && ( | ||
| <button | ||
| class={styles().actionButton} | ||
| onMouseDown={() => { | ||
| const next = !isPending | ||
| const next = !isPending() | ||
| utilInstance.store.setState((prev: any) => ({ | ||
@@ -53,7 +81,7 @@ ...prev, | ||
| })) | ||
| pacerEventClient.emit(emitName, utilInstance) | ||
| emitChange(emitName, utilInstance) | ||
| }} | ||
| > | ||
| <span class={styles().actionDotBlue} /> | ||
| {isPending ? 'Restore Pending' : 'Trigger Pending'} | ||
| {isPending() ? 'Restore Pending' : 'Trigger Pending'} | ||
| </button> | ||
@@ -66,11 +94,9 @@ )} | ||
| utilInstance.flush() | ||
| pacerEventClient.emit(emitName, utilInstance) | ||
| emitChange(emitName, utilInstance) | ||
| }} | ||
| disabled={ | ||
| (props.utilName.toLowerCase().includes('debouncer') && | ||
| !isPending) || | ||
| (props.utilName.toLowerCase().includes('throttler') && | ||
| !isPending) || | ||
| (props.utilName.toLowerCase().includes('batcher') && isEmpty) || | ||
| (props.utilName.toLowerCase().includes('queuer') && isEmpty) | ||
| (utilNameLower.includes('debouncer') && !isPending()) || | ||
| (utilNameLower.includes('throttler') && !isPending()) || | ||
| (utilNameLower.includes('batcher') && isEmptyFlag()) || | ||
| (utilNameLower.includes('queuer') && isEmptyFlag()) | ||
| } | ||
@@ -87,11 +113,9 @@ > | ||
| utilInstance.cancel() | ||
| pacerEventClient.emit(emitName, utilInstance) | ||
| emitChange(emitName, utilInstance) | ||
| }} | ||
| disabled={ | ||
| (props.utilName.toLowerCase().includes('debouncer') && | ||
| !isPending) || | ||
| (props.utilName.toLowerCase().includes('throttler') && | ||
| !isPending) || | ||
| (props.utilName.toLowerCase().includes('batcher') && isEmpty) || | ||
| (props.utilName.toLowerCase().includes('queuer') && isEmpty) | ||
| (utilNameLower.includes('debouncer') && !isPending()) || | ||
| (utilNameLower.includes('throttler') && !isPending()) || | ||
| (utilNameLower.includes('batcher') && isEmptyFlag()) || | ||
| (utilNameLower.includes('queuer') && isEmptyFlag()) | ||
| } | ||
@@ -108,3 +132,3 @@ > | ||
| utilInstance.reset() | ||
| pacerEventClient.emit(emitName, utilInstance) | ||
| emitChange(emitName, utilInstance) | ||
| }} | ||
@@ -121,7 +145,7 @@ > | ||
| utilInstance.clear() | ||
| pacerEventClient.emit(emitName, utilInstance) | ||
| emitChange(emitName, utilInstance) | ||
| }} | ||
| disabled={ | ||
| (props.utilName.toLowerCase().includes('batcher') && isEmpty) || | ||
| (props.utilName.toLowerCase().includes('queuer') && isEmpty) | ||
| (utilNameLower.includes('batcher') && isEmptyFlag()) || | ||
| (utilNameLower.includes('queuer') && isEmptyFlag()) | ||
| } | ||
@@ -137,3 +161,3 @@ > | ||
| onMouseDown={() => { | ||
| if (isRunning) { | ||
| if (isRunning()) { | ||
| utilInstance.stop() | ||
@@ -143,7 +167,7 @@ } else { | ||
| } | ||
| pacerEventClient.emit(emitName, utilInstance) | ||
| emitChange(emitName, utilInstance) | ||
| }} | ||
| > | ||
| <span class={styles().actionDotPurple} /> | ||
| {isRunning ? 'Stop' : 'Start'} | ||
| {isRunning() ? 'Stop' : 'Start'} | ||
| </button> | ||
@@ -150,0 +174,0 @@ )} |
@@ -5,2 +5,3 @@ import { JsonTree } from '@tanstack/devtools-ui' | ||
| import { StateHeader } from './StateHeader' | ||
| import { UtilStateJsonTree } from './UtilStateJsonTree' | ||
@@ -46,3 +47,3 @@ type DetailsPanelProps = { | ||
| <div class={styles().stateContent}> | ||
| <JsonTree value={entry.instance.store?.state as unknown} /> | ||
| <UtilStateJsonTree instance={entry.instance} /> | ||
| </div> | ||
@@ -49,0 +50,0 @@ </div> |
+103
-34
@@ -1,2 +0,8 @@ | ||
| import { createMemo, createSignal, onCleanup, onMount } from 'solid-js' | ||
| import { | ||
| createEffect, | ||
| createMemo, | ||
| createSignal, | ||
| onCleanup, | ||
| onMount, | ||
| } from 'solid-js' | ||
| import { Header, HeaderLogo, MainPanel } from '@tanstack/devtools-ui' | ||
@@ -10,2 +16,19 @@ import { useStyles } from '../styles/use-styles' | ||
| function readResizeObserverBlockSize(entry: ResizeObserverEntry): number { | ||
| const [box] = entry.borderBoxSize | ||
| if (box !== undefined) return box.blockSize | ||
| return entry.contentRect.height | ||
| } | ||
| /** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */ | ||
| function resolvePluginHost(el: HTMLElement): HTMLElement | null { | ||
| const byId = el.closest<HTMLElement>('[id^="plugin-container-"]') | ||
| if (byId) return byId | ||
| return ( | ||
| el.parentElement?.parentElement?.parentElement ?? | ||
| el.parentElement?.parentElement ?? | ||
| el.parentElement | ||
| ) | ||
| } | ||
| export function Shell() { | ||
@@ -18,2 +41,8 @@ const styles = useStyles() | ||
| const [isDragging, setIsDragging] = createSignal(false) | ||
| const [shellRootEl, setShellRootEl] = createSignal<HTMLDivElement | null>( | ||
| null, | ||
| ) | ||
| const [slotHeightPx, setSlotHeightPx] = createSignal<number | undefined>( | ||
| undefined, | ||
| ) | ||
@@ -70,41 +99,81 @@ const selectedInstance = createMemo(() => { | ||
| /** | ||
| * TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex | ||
| * ancestors, so percentage height often never constrains our tree. Observing the | ||
| * outer `plugin-container-*` host (not the immediate React mount wrapper) and | ||
| * setting an explicit pixel height makes inner overflow-y regions scroll without | ||
| * capping to the mount div’s collapsed height. | ||
| */ | ||
| createEffect(() => { | ||
| const el = shellRootEl() | ||
| if (!el || typeof ResizeObserver === 'undefined') return | ||
| const pluginSlot = resolvePluginHost(el) | ||
| if (!pluginSlot) return | ||
| const ro = new ResizeObserver((entries) => { | ||
| const entry = entries[0] | ||
| if (!entry) return | ||
| const target = entry.target as HTMLElement | ||
| const h = | ||
| target.clientHeight > 0 | ||
| ? target.clientHeight | ||
| : readResizeObserverBlockSize(entry) | ||
| if (h > 0) setSlotHeightPx(Math.round(h)) | ||
| }) | ||
| ro.observe(pluginSlot) | ||
| onCleanup(() => ro.disconnect()) | ||
| }) | ||
| return ( | ||
| <MainPanel> | ||
| <Header> | ||
| <HeaderLogo flavor={{ light: '#84cc16', dark: '#84cc16' }}> | ||
| TanStack Pacer | ||
| </HeaderLogo> | ||
| </Header> | ||
| <div | ||
| ref={setShellRootEl} | ||
| class={styles().shellRoot} | ||
| style={{ | ||
| ...(slotHeightPx() !== undefined | ||
| ? { | ||
| height: `${slotHeightPx()}px`, | ||
| 'max-height': `${slotHeightPx()}px`, | ||
| } | ||
| : {}), | ||
| }} | ||
| > | ||
| <MainPanel class={styles().mainPanelShell}> | ||
| <Header> | ||
| <HeaderLogo flavor={{ light: '#84cc16', dark: '#84cc16' }}> | ||
| TanStack Pacer | ||
| </HeaderLogo> | ||
| </Header> | ||
| <div class={styles().mainContainer}> | ||
| <div | ||
| class={styles().leftPanel} | ||
| style={{ | ||
| width: `${leftPanelWidth()}px`, | ||
| 'min-width': '150px', | ||
| 'max-width': '800px', | ||
| }} | ||
| > | ||
| <UtilList | ||
| selectedKey={selectedKey} | ||
| setSelectedKey={setSelectedKey} | ||
| utilState={utilState} | ||
| <div class={styles().mainContainer}> | ||
| <div | ||
| class={styles().leftPanel} | ||
| style={{ | ||
| width: `${leftPanelWidth()}px`, | ||
| 'min-width': '150px', | ||
| 'max-width': '800px', | ||
| }} | ||
| > | ||
| <UtilList | ||
| selectedKey={selectedKey} | ||
| setSelectedKey={setSelectedKey} | ||
| utilState={utilState} | ||
| /> | ||
| </div> | ||
| <div | ||
| class={`${styles().dragHandle} ${isDragging() ? 'dragging' : ''}`} | ||
| onMouseDown={handleMouseDown} | ||
| /> | ||
| </div> | ||
| <div | ||
| class={`${styles().dragHandle} ${isDragging() ? 'dragging' : ''}`} | ||
| onMouseDown={handleMouseDown} | ||
| /> | ||
| <div class={styles().rightPanel} style={{ flex: 1 }}> | ||
| <div class={styles().panelHeader}>Details</div> | ||
| <DetailsPanel | ||
| selectedInstance={selectedInstance} | ||
| utilState={utilState} | ||
| /> | ||
| <div class={styles().rightPanel} style={{ flex: 1 }}> | ||
| <div class={styles().panelHeader}>Details</div> | ||
| <DetailsPanel | ||
| selectedInstance={selectedInstance} | ||
| utilState={utilState} | ||
| /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </MainPanel> | ||
| </MainPanel> | ||
| </div> | ||
| ) | ||
| } |
| import dayjs from 'dayjs' | ||
| import relativeTime from 'dayjs/plugin/relativeTime.js' | ||
| import { createSignal, onCleanup, onMount } from 'solid-js' | ||
| import { Show, createSignal, onCleanup, onMount } from 'solid-js' | ||
| import { useStore } from '@tanstack/solid-store' | ||
| import { useStyles } from '../styles/use-styles' | ||
| import { | ||
| getPacerUtilStoreState, | ||
| isPacerUtilTanStackStore, | ||
| } from '../utils/read-pacer-store-state' | ||
@@ -13,24 +18,49 @@ dayjs.extend(relativeTime) | ||
| export function StateHeader(props: StateHeaderProps) { | ||
| const styles = useStyles() | ||
| const [now, setNow] = createSignal(Date.now()) | ||
| function reductionFromState( | ||
| entry: { type: string }, | ||
| state: Record<string, unknown> | null | undefined, | ||
| ): number { | ||
| if (!state) return 0 | ||
| onMount(() => { | ||
| const interval = setInterval(() => { | ||
| setNow(Date.now()) | ||
| }, 1000) | ||
| const isAsync = entry.type.toLowerCase().includes('async') | ||
| const completedExecutions = isAsync | ||
| ? Number(state.settleCount) || 0 | ||
| : Number(state.executionCount) || 0 | ||
| onCleanup(() => { | ||
| clearInterval(interval) | ||
| }) | ||
| }) | ||
| if (entry.type.toLowerCase().includes('batcher')) { | ||
| const totalItemsProcessed = Number(state.totalItemsProcessed) || 0 | ||
| if (totalItemsProcessed === 0) return 0 | ||
| return Math.round( | ||
| ((totalItemsProcessed - completedExecutions) / totalItemsProcessed) * 100, | ||
| ) | ||
| } | ||
| const entry = props.selectedInstance() | ||
| if (!entry) return null | ||
| let requestCount = 0 | ||
| const key = entry.instance.key as string | ||
| const updatedAt = props.utilState().lastUpdatedByKey[key] ?? Date.now() | ||
| if (state.maybeExecuteCount !== undefined) { | ||
| requestCount = Number(state.maybeExecuteCount) || 0 | ||
| } else if (state.addItemCount !== undefined) { | ||
| requestCount = Number(state.addItemCount) || 0 | ||
| } else { | ||
| return 0 | ||
| } | ||
| if (requestCount === 0) return 0 | ||
| const reduction = requestCount - completedExecutions | ||
| return Math.round((reduction / requestCount) * 100) | ||
| } | ||
| function StateHeaderInner(props: { | ||
| entry: { instance: any; type: string } | ||
| styles: ReturnType<ReturnType<typeof useStyles>> | ||
| now: () => number | ||
| utilState: () => { lastUpdatedByKey: Record<string, number> } | ||
| }) { | ||
| const key = props.entry.instance.key as string | ||
| const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now() | ||
| const getRelativeTime = () => { | ||
| const diffMs = now() - updatedAt | ||
| const at = updatedAt() | ||
| const diffMs = props.now() - at | ||
| const diffSeconds = Math.floor(diffMs / 1000) | ||
@@ -42,63 +72,35 @@ | ||
| return dayjs(updatedAt).fromNow() | ||
| return dayjs(at).fromNow() | ||
| } | ||
| const getReductionPercentage = () => { | ||
| const state = entry.instance.store?.state || entry.instance.state | ||
| if (!state) return 0 | ||
| // Use settleCount for async utilities, executionCount for sync utilities | ||
| const isAsync = entry.type.toLowerCase().includes('async') | ||
| const completedExecutions = isAsync | ||
| ? state.settleCount || 0 | ||
| : state.executionCount || 0 | ||
| // For batchers, calculate reduction based on items processed vs executions | ||
| if (entry.type.toLowerCase().includes('batcher')) { | ||
| const totalItemsProcessed = state.totalItemsProcessed || 0 | ||
| if (totalItemsProcessed === 0) return 0 | ||
| return Math.round( | ||
| ((totalItemsProcessed - completedExecutions) / totalItemsProcessed) * | ||
| 100, | ||
| const store = props.entry.instance?.store | ||
| const stateAccessor = isPacerUtilTanStackStore(store) | ||
| ? useStore(store as never, (s: unknown) => | ||
| s !== null && s !== undefined && typeof s === 'object' | ||
| ? (s as Record<string, unknown>) | ||
| : {}, | ||
| ) | ||
| } | ||
| : () => | ||
| getPacerUtilStoreState(props.entry.instance) as Record<string, unknown> | ||
| // For other utilities, calculate reduction based on request tracking | ||
| let requestCount = 0 | ||
| const reductionPercentage = () => | ||
| reductionFromState(props.entry, stateAccessor()) | ||
| if (state.maybeExecuteCount !== undefined) { | ||
| requestCount = state.maybeExecuteCount | ||
| } else if (state.addItemCount !== undefined) { | ||
| requestCount = state.addItemCount | ||
| } else { | ||
| // For utilities that don't track requests, show 0% | ||
| return 0 | ||
| } | ||
| if (requestCount === 0) return 0 | ||
| const reduction = requestCount - completedExecutions | ||
| return Math.round((reduction / requestCount) * 100) | ||
| } | ||
| const reductionPercentage = getReductionPercentage() | ||
| return ( | ||
| <div class={styles().stateHeader}> | ||
| <div class={styles().stateTitle}>{entry.type}</div> | ||
| <div class={props.styles.stateHeader}> | ||
| <div class={props.styles.stateTitle}>{props.entry.type}</div> | ||
| <div style={{ display: 'flex', 'align-items': 'center', gap: '16px' }}> | ||
| <div class={styles().infoGrid}> | ||
| <div class={styles().infoLabel}>Key</div> | ||
| <div class={styles().infoValueMono}>{key}</div> | ||
| <div class={styles().infoLabel}>Last Updated</div> | ||
| <div class={styles().infoValueMono}> | ||
| {new Date(updatedAt).toLocaleTimeString()} ({getRelativeTime()}) | ||
| <div class={props.styles.infoGrid}> | ||
| <div class={props.styles.infoLabel}>Key</div> | ||
| <div class={props.styles.infoValueMono}>{key}</div> | ||
| <div class={props.styles.infoLabel}>Last Updated</div> | ||
| <div class={props.styles.infoValueMono}> | ||
| {new Date(updatedAt()).toLocaleTimeString()} ({getRelativeTime()}) | ||
| </div> | ||
| </div> | ||
| <div | ||
| class={styles().infoValueMono} | ||
| class={props.styles.infoValueMono} | ||
| style={{ 'margin-left': 'auto', 'font-weight': 'bold' }} | ||
| > | ||
| {reductionPercentage}% reduction | ||
| {reductionPercentage()}% reduction | ||
| </div> | ||
@@ -109,1 +111,29 @@ </div> | ||
| } | ||
| export function StateHeader(props: StateHeaderProps) { | ||
| const styles = useStyles() | ||
| const [now, setNow] = createSignal(Date.now()) | ||
| onMount(() => { | ||
| const interval = setInterval(() => { | ||
| setNow(Date.now()) | ||
| }, 1000) | ||
| onCleanup(() => { | ||
| clearInterval(interval) | ||
| }) | ||
| }) | ||
| return ( | ||
| <Show when={props.selectedInstance()}> | ||
| {(entry) => ( | ||
| <StateHeaderInner | ||
| entry={entry()} | ||
| styles={styles()} | ||
| now={now} | ||
| utilState={props.utilState} | ||
| /> | ||
| )} | ||
| </Show> | ||
| ) | ||
| } |
+4
-4
| import { constructCoreClass } from '@tanstack/devtools-utils/solid' | ||
| import type { ClassType } from '@tanstack/devtools-utils/solid' | ||
| export interface PacerDevtoolsInit {} | ||
| const [PacerDevtoolsCore, PacerDevtoolsCoreNoOp] = constructCoreClass( | ||
| () => import('./components'), | ||
| ) | ||
| const coreClasses = constructCoreClass(() => import('./components')) | ||
| export { PacerDevtoolsCore, PacerDevtoolsCoreNoOp } | ||
| export const PacerDevtoolsCore: ClassType = coreClasses[0] | ||
| export const PacerDevtoolsCoreNoOp: ClassType = coreClasses[1] |
+1
-1
@@ -5,3 +5,3 @@ 'use client' | ||
| export const PacerDevtoolsCore = | ||
| export const PacerDevtoolsCore: typeof Devtools.PacerDevtoolsCore = | ||
| process.env.NODE_ENV !== 'development' | ||
@@ -8,0 +8,0 @@ ? Devtools.PacerDevtoolsCoreNoOp |
+96
-127
@@ -1,4 +0,7 @@ | ||
| import { createStore } from 'solid-js/store' | ||
| import { createStore, produce } from 'solid-js/store' | ||
| import { createContext, createEffect, onCleanup, useContext } from 'solid-js' | ||
| import { pacerEventClient } from '@tanstack/pacer/event-client' | ||
| import { | ||
| getPacerDevtoolsInstance, | ||
| pacerEventClient, | ||
| } from '@tanstack/pacer/event-client' | ||
| import type { | ||
@@ -12,2 +15,3 @@ AsyncBatcher, | ||
| Debouncer, | ||
| PacerEventName, | ||
| Queuer, | ||
@@ -53,15 +57,59 @@ RateLimiter, | ||
| const updateOrAddToArray = <T extends { key: string }>( | ||
| oldArray: Array<T>, | ||
| newItem: T, | ||
| ) => { | ||
| const index = oldArray.findIndex((item) => item.key === newItem.key) | ||
| if (index !== -1) { | ||
| // Update existing item | ||
| return oldArray.map((item, i) => (i === index ? newItem : item)) | ||
| } | ||
| // Add new item | ||
| return [...oldArray, newItem] | ||
| } | ||
| type UtilListKey = Exclude<keyof PacerDevtoolsContextType, 'lastUpdatedByKey'> | ||
| /** | ||
| * Match TanStack Form devtools: subscribe with {@link pacerEventClient.on} per | ||
| * event suffix so the same `pacer:${suffix}` channel the library emits on is | ||
| * observed. `onAllPluginEvents` only sees `tanstack-devtools-global`, which is | ||
| * not always relayed the same way by the shell. | ||
| * | ||
| * `d-*` suffixes are used when the devtools panel pushes state back (see | ||
| * ActionButtons); those must update the panel store too. | ||
| */ | ||
| const PACER_DEVTOOLS_UTIL_EVENTS: Array<{ | ||
| listKey: UtilListKey | ||
| suffixes: ReadonlyArray<PacerEventName> | ||
| }> = [ | ||
| { | ||
| listKey: 'asyncBatchers', | ||
| suffixes: ['AsyncBatcher', 'd-AsyncBatcher'], | ||
| }, | ||
| { | ||
| listKey: 'asyncDebouncers', | ||
| suffixes: ['AsyncDebouncer', 'd-AsyncDebouncer'], | ||
| }, | ||
| { | ||
| listKey: 'asyncQueuers', | ||
| suffixes: ['AsyncQueuer', 'd-AsyncQueuer'], | ||
| }, | ||
| { | ||
| listKey: 'asyncRateLimiters', | ||
| suffixes: ['AsyncRateLimiter', 'd-AsyncRateLimiter'], | ||
| }, | ||
| { | ||
| listKey: 'asyncThrottlers', | ||
| suffixes: ['AsyncThrottler', 'd-AsyncThrottler'], | ||
| }, | ||
| { | ||
| listKey: 'batchers', | ||
| suffixes: ['Batcher', 'd-Batcher'], | ||
| }, | ||
| { | ||
| listKey: 'debouncers', | ||
| suffixes: ['Debouncer', 'd-Debouncer'], | ||
| }, | ||
| { | ||
| listKey: 'queuers', | ||
| suffixes: ['Queuer', 'd-Queuer'], | ||
| }, | ||
| { | ||
| listKey: 'rateLimiters', | ||
| suffixes: ['RateLimiter', 'd-RateLimiter'], | ||
| }, | ||
| { | ||
| listKey: 'throttlers', | ||
| suffixes: ['Throttler', 'd-Throttler'], | ||
| }, | ||
| ] | ||
| export function PacerContextProvider(props: { children: any }) { | ||
@@ -73,117 +121,38 @@ const [store, setStore] = createStore<PacerDevtoolsContextType>( | ||
| createEffect(() => { | ||
| const cleanup = pacerEventClient.onAllPluginEvents((_e) => { | ||
| const e = _e as unknown as { type: string; payload: any } | ||
| switch (e.type) { | ||
| case 'pacer:AsyncBatcher': { | ||
| setStore({ | ||
| asyncBatchers: updateOrAddToArray(store.asyncBatchers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| case 'pacer:AsyncDebouncer': { | ||
| setStore({ | ||
| asyncDebouncers: updateOrAddToArray( | ||
| store.asyncDebouncers, | ||
| e.payload, | ||
| ), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| case 'pacer:AsyncQueuer': { | ||
| setStore({ | ||
| asyncQueuers: updateOrAddToArray(store.asyncQueuers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| case 'pacer:AsyncRateLimiter': { | ||
| setStore({ | ||
| asyncRateLimiters: updateOrAddToArray( | ||
| store.asyncRateLimiters, | ||
| e.payload, | ||
| ), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| case 'pacer:AsyncThrottler': { | ||
| setStore({ | ||
| asyncThrottlers: updateOrAddToArray( | ||
| store.asyncThrottlers, | ||
| e.payload, | ||
| ), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| case 'pacer:Batcher': { | ||
| setStore({ | ||
| batchers: updateOrAddToArray(store.batchers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| case 'pacer:Debouncer': { | ||
| setStore({ | ||
| debouncers: updateOrAddToArray(store.debouncers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| case 'pacer:Queuer': { | ||
| setStore({ | ||
| queuers: updateOrAddToArray(store.queuers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| case 'pacer:RateLimiter': { | ||
| setStore({ | ||
| rateLimiters: updateOrAddToArray(store.rateLimiters, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| case 'pacer:Throttler': { | ||
| setStore({ | ||
| throttlers: updateOrAddToArray(store.throttlers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now(), | ||
| }, | ||
| }) | ||
| break | ||
| } | ||
| const cleanups: Array<() => void> = [] | ||
| for (const { listKey, suffixes } of PACER_DEVTOOLS_UTIL_EVENTS) { | ||
| for (const suffix of suffixes) { | ||
| cleanups.push( | ||
| pacerEventClient.on(suffix, (e) => { | ||
| const payload = e.payload | ||
| const key = payload.key | ||
| if (!key) return | ||
| const instance = getPacerDevtoolsInstance(key) | ||
| if (!instance || typeof instance !== 'object') return | ||
| setStore( | ||
| produce((draft) => { | ||
| const list = draft[listKey] as Array<{ key: string }> | ||
| const index = list.findIndex((item) => item.key === key) | ||
| const inst = instance as { key: string } | ||
| if (index !== -1) { | ||
| list[index] = inst as (typeof list)[number] | ||
| } else { | ||
| list.push(inst as (typeof list)[number]) | ||
| } | ||
| draft.lastUpdatedByKey[key] = Date.now() | ||
| }), | ||
| ) | ||
| }), | ||
| ) | ||
| } | ||
| } | ||
| onCleanup(() => { | ||
| for (const cleanup of cleanups) { | ||
| cleanup() | ||
| } | ||
| }) | ||
| onCleanup(cleanup) | ||
| }) | ||
@@ -190,0 +159,0 @@ return ( |
@@ -13,5 +13,26 @@ import * as goober from 'goober' | ||
| return { | ||
| /** | ||
| * Fills the devtools-utils portal div (height: 100%) and is a flex parent so MainPanel | ||
| * gets a real flex height budget (not just height: 100% without a flex chain). | ||
| */ | ||
| shellRoot: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| box-sizing: border-box; | ||
| width: 100%; | ||
| height: 100%; | ||
| min-height: 0; | ||
| overflow: hidden; | ||
| `, | ||
| /** Overrides devtools-ui MainPanel overflow-y: auto so inner columns own scrolling */ | ||
| mainPanelShell: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| flex: 1 1 0%; | ||
| min-height: 0; | ||
| overflow: hidden !important; | ||
| `, | ||
| mainContainer: css` | ||
| display: flex; | ||
| flex: 1; | ||
| flex: 1 1 0%; | ||
| min-height: 0; | ||
@@ -18,0 +39,0 @@ overflow: hidden; |
| import { createComponent, escape, ssr, ssrAttribute, ssrStyleProperty } from "solid-js/web"; | ||
| import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, useTheme } from "@tanstack/devtools-ui"; | ||
| import { createStore } from "solid-js/store"; | ||
| import { For, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js"; | ||
| import { pacerEventClient } from "@tanstack/pacer/event-client"; | ||
| import * as goober from "goober"; | ||
| import { useStore } from "@tanstack/solid-store"; | ||
| import clsx from "clsx"; | ||
| import dayjs from "dayjs"; | ||
| import relativeTime from "dayjs/plugin/relativeTime.js"; | ||
| //#region src/PacerContextProvider.tsx | ||
| const initialPacerDevtoolsStore = { | ||
| asyncBatchers: [], | ||
| asyncDebouncers: [], | ||
| asyncQueuers: [], | ||
| asyncRateLimiters: [], | ||
| asyncThrottlers: [], | ||
| batchers: [], | ||
| debouncers: [], | ||
| queuers: [], | ||
| rateLimiters: [], | ||
| throttlers: [], | ||
| lastUpdatedByKey: {} | ||
| }; | ||
| const PacerDevtoolsContext = createContext([initialPacerDevtoolsStore, () => {}]); | ||
| const updateOrAddToArray = (oldArray, newItem) => { | ||
| const index = oldArray.findIndex((item) => item.key === newItem.key); | ||
| if (index !== -1) return oldArray.map((item, i) => i === index ? newItem : item); | ||
| return [...oldArray, newItem]; | ||
| }; | ||
| function PacerContextProvider(props) { | ||
| const [store, setStore] = createStore(initialPacerDevtoolsStore); | ||
| createEffect(() => { | ||
| onCleanup(pacerEventClient.onAllPluginEvents((_e) => { | ||
| const e = _e; | ||
| switch (e.type) { | ||
| case "pacer:AsyncBatcher": | ||
| setStore({ | ||
| asyncBatchers: updateOrAddToArray(store.asyncBatchers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:AsyncDebouncer": | ||
| setStore({ | ||
| asyncDebouncers: updateOrAddToArray(store.asyncDebouncers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:AsyncQueuer": | ||
| setStore({ | ||
| asyncQueuers: updateOrAddToArray(store.asyncQueuers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:AsyncRateLimiter": | ||
| setStore({ | ||
| asyncRateLimiters: updateOrAddToArray(store.asyncRateLimiters, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:AsyncThrottler": | ||
| setStore({ | ||
| asyncThrottlers: updateOrAddToArray(store.asyncThrottlers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:Batcher": | ||
| setStore({ | ||
| batchers: updateOrAddToArray(store.batchers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:Debouncer": | ||
| setStore({ | ||
| debouncers: updateOrAddToArray(store.debouncers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:Queuer": | ||
| setStore({ | ||
| queuers: updateOrAddToArray(store.queuers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:RateLimiter": | ||
| setStore({ | ||
| rateLimiters: updateOrAddToArray(store.rateLimiters, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:Throttler": | ||
| setStore({ | ||
| throttlers: updateOrAddToArray(store.throttlers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| } | ||
| })); | ||
| }); | ||
| return createComponent(PacerDevtoolsContext.Provider, { | ||
| value: [store, setStore], | ||
| get children() { | ||
| return props.children; | ||
| } | ||
| }); | ||
| } | ||
| const usePacerDevtoolsContext = () => { | ||
| return useContext(PacerDevtoolsContext); | ||
| }; | ||
| const usePacerDevtoolsState = () => { | ||
| const [state] = usePacerDevtoolsContext(); | ||
| return state; | ||
| }; | ||
| //#endregion | ||
| //#region src/styles/tokens.ts | ||
| const tokens = { | ||
| colors: { | ||
| inherit: "inherit", | ||
| current: "currentColor", | ||
| transparent: "transparent", | ||
| black: "#000000", | ||
| white: "#ffffff", | ||
| neutral: { | ||
| 50: "#f9fafb", | ||
| 100: "#f2f4f7", | ||
| 200: "#eaecf0", | ||
| 300: "#d0d5dd", | ||
| 400: "#98a2b3", | ||
| 500: "#667085", | ||
| 600: "#475467", | ||
| 700: "#344054", | ||
| 800: "#1d2939", | ||
| 900: "#101828" | ||
| }, | ||
| darkGray: { | ||
| 50: "#525c7a", | ||
| 100: "#49536e", | ||
| 200: "#414962", | ||
| 300: "#394056", | ||
| 400: "#313749", | ||
| 500: "#292e3d", | ||
| 600: "#212530", | ||
| 700: "#191c24", | ||
| 800: "#111318", | ||
| 900: "#0b0d10" | ||
| }, | ||
| gray: { | ||
| 50: "#f9fafb", | ||
| 100: "#f2f4f7", | ||
| 200: "#eaecf0", | ||
| 300: "#d0d5dd", | ||
| 400: "#98a2b3", | ||
| 500: "#667085", | ||
| 600: "#475467", | ||
| 700: "#344054", | ||
| 800: "#1d2939", | ||
| 900: "#101828" | ||
| }, | ||
| blue: { | ||
| 25: "#F5FAFF", | ||
| 50: "#EFF8FF", | ||
| 100: "#D1E9FF", | ||
| 200: "#B2DDFF", | ||
| 300: "#84CAFF", | ||
| 400: "#53B1FD", | ||
| 500: "#2E90FA", | ||
| 600: "#1570EF", | ||
| 700: "#175CD3", | ||
| 800: "#1849A9", | ||
| 900: "#194185" | ||
| }, | ||
| green: { | ||
| 25: "#F6FEF9", | ||
| 50: "#ECFDF3", | ||
| 100: "#D1FADF", | ||
| 200: "#A6F4C5", | ||
| 300: "#6CE9A6", | ||
| 400: "#32D583", | ||
| 500: "#12B76A", | ||
| 600: "#039855", | ||
| 700: "#027A48", | ||
| 800: "#05603A", | ||
| 900: "#054F31" | ||
| }, | ||
| red: { | ||
| 50: "#fef2f2", | ||
| 100: "#fee2e2", | ||
| 200: "#fecaca", | ||
| 300: "#fca5a5", | ||
| 400: "#f87171", | ||
| 500: "#ef4444", | ||
| 600: "#dc2626", | ||
| 700: "#b91c1c", | ||
| 800: "#991b1b", | ||
| 900: "#7f1d1d", | ||
| 950: "#450a0a" | ||
| }, | ||
| yellow: { | ||
| 25: "#FFFCF5", | ||
| 50: "#FFFAEB", | ||
| 100: "#FEF0C7", | ||
| 200: "#FEDF89", | ||
| 300: "#FEC84B", | ||
| 400: "#FDB022", | ||
| 500: "#F79009", | ||
| 600: "#DC6803", | ||
| 700: "#B54708", | ||
| 800: "#93370D", | ||
| 900: "#7A2E0E" | ||
| }, | ||
| purple: { | ||
| 25: "#FAFAFF", | ||
| 50: "#F4F3FF", | ||
| 100: "#EBE9FE", | ||
| 200: "#D9D6FE", | ||
| 300: "#BDB4FE", | ||
| 400: "#9B8AFB", | ||
| 500: "#7A5AF8", | ||
| 600: "#6938EF", | ||
| 700: "#5925DC", | ||
| 800: "#4A1FB8", | ||
| 900: "#3E1C96" | ||
| }, | ||
| teal: { | ||
| 25: "#F6FEFC", | ||
| 50: "#F0FDF9", | ||
| 100: "#CCFBEF", | ||
| 200: "#99F6E0", | ||
| 300: "#5FE9D0", | ||
| 400: "#2ED3B7", | ||
| 500: "#15B79E", | ||
| 600: "#0E9384", | ||
| 700: "#107569", | ||
| 800: "#125D56", | ||
| 900: "#134E48" | ||
| }, | ||
| pink: { | ||
| 25: "#fdf2f8", | ||
| 50: "#fce7f3", | ||
| 100: "#fbcfe8", | ||
| 200: "#f9a8d4", | ||
| 300: "#f472b6", | ||
| 400: "#ec4899", | ||
| 500: "#db2777", | ||
| 600: "#be185d", | ||
| 700: "#9d174d", | ||
| 800: "#831843", | ||
| 900: "#500724" | ||
| }, | ||
| cyan: { | ||
| 25: "#ecfeff", | ||
| 50: "#cffafe", | ||
| 100: "#a5f3fc", | ||
| 200: "#67e8f9", | ||
| 300: "#22d3ee", | ||
| 400: "#06b6d4", | ||
| 500: "#0891b2", | ||
| 600: "#0e7490", | ||
| 700: "#155e75", | ||
| 800: "#164e63", | ||
| 900: "#083344" | ||
| } | ||
| }, | ||
| alpha: { | ||
| 100: "ff", | ||
| 90: "e5", | ||
| 80: "cc", | ||
| 70: "b3", | ||
| 60: "99", | ||
| 50: "80", | ||
| 40: "66", | ||
| 30: "4d", | ||
| 20: "33", | ||
| 10: "1a", | ||
| 0: "00" | ||
| }, | ||
| font: { | ||
| size: { | ||
| "2xs": "calc(var(--tsrd-font-size) * 0.625)", | ||
| xs: "calc(var(--tsrd-font-size) * 0.75)", | ||
| sm: "calc(var(--tsrd-font-size) * 0.875)", | ||
| md: "var(--tsrd-font-size)", | ||
| lg: "calc(var(--tsrd-font-size) * 1.125)", | ||
| xl: "calc(var(--tsrd-font-size) * 1.25)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 1.5)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 1.875)", | ||
| "4xl": "calc(var(--tsrd-font-size) * 2.25)", | ||
| "5xl": "calc(var(--tsrd-font-size) * 3)", | ||
| "6xl": "calc(var(--tsrd-font-size) * 3.75)", | ||
| "7xl": "calc(var(--tsrd-font-size) * 4.5)", | ||
| "8xl": "calc(var(--tsrd-font-size) * 6)", | ||
| "9xl": "calc(var(--tsrd-font-size) * 8)" | ||
| }, | ||
| lineHeight: { | ||
| "3xs": "calc(var(--tsrd-font-size) * 0.75)", | ||
| "2xs": "calc(var(--tsrd-font-size) * 0.875)", | ||
| xs: "calc(var(--tsrd-font-size) * 1)", | ||
| sm: "calc(var(--tsrd-font-size) * 1.25)", | ||
| md: "calc(var(--tsrd-font-size) * 1.5)", | ||
| lg: "calc(var(--tsrd-font-size) * 1.75)", | ||
| xl: "calc(var(--tsrd-font-size) * 2)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 2.25)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 2.5)", | ||
| "4xl": "calc(var(--tsrd-font-size) * 2.75)", | ||
| "5xl": "calc(var(--tsrd-font-size) * 3)", | ||
| "6xl": "calc(var(--tsrd-font-size) * 3.25)", | ||
| "7xl": "calc(var(--tsrd-font-size) * 3.5)", | ||
| "8xl": "calc(var(--tsrd-font-size) * 3.75)", | ||
| "9xl": "calc(var(--tsrd-font-size) * 4)" | ||
| }, | ||
| weight: { | ||
| thin: "100", | ||
| extralight: "200", | ||
| light: "300", | ||
| normal: "400", | ||
| medium: "500", | ||
| semibold: "600", | ||
| bold: "700", | ||
| extrabold: "800", | ||
| black: "900" | ||
| }, | ||
| fontFamily: { | ||
| sans: "ui-sans-serif, Inter, system-ui, sans-serif, sans-serif", | ||
| mono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace` | ||
| } | ||
| }, | ||
| breakpoints: { | ||
| xs: "320px", | ||
| sm: "640px", | ||
| md: "768px", | ||
| lg: "1024px", | ||
| xl: "1280px", | ||
| "2xl": "1536px" | ||
| }, | ||
| border: { radius: { | ||
| none: "0px", | ||
| xs: "calc(var(--tsrd-font-size) * 0.125)", | ||
| sm: "calc(var(--tsrd-font-size) * 0.25)", | ||
| md: "calc(var(--tsrd-font-size) * 0.375)", | ||
| lg: "calc(var(--tsrd-font-size) * 0.5)", | ||
| xl: "calc(var(--tsrd-font-size) * 0.75)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 1)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 1.5)", | ||
| full: "9999px" | ||
| } }, | ||
| size: { | ||
| 0: "0px", | ||
| .25: "calc(var(--tsrd-font-size) * 0.0625)", | ||
| .5: "calc(var(--tsrd-font-size) * 0.125)", | ||
| 1: "calc(var(--tsrd-font-size) * 0.25)", | ||
| 1.5: "calc(var(--tsrd-font-size) * 0.375)", | ||
| 2: "calc(var(--tsrd-font-size) * 0.5)", | ||
| 2.5: "calc(var(--tsrd-font-size) * 0.625)", | ||
| 3: "calc(var(--tsrd-font-size) * 0.75)", | ||
| 3.5: "calc(var(--tsrd-font-size) * 0.875)", | ||
| 4: "calc(var(--tsrd-font-size) * 1)", | ||
| 4.5: "calc(var(--tsrd-font-size) * 1.125)", | ||
| 5: "calc(var(--tsrd-font-size) * 1.25)", | ||
| 5.5: "calc(var(--tsrd-font-size) * 1.375)", | ||
| 6: "calc(var(--tsrd-font-size) * 1.5)", | ||
| 6.5: "calc(var(--tsrd-font-size) * 1.625)", | ||
| 7: "calc(var(--tsrd-font-size) * 1.75)", | ||
| 8: "calc(var(--tsrd-font-size) * 2)", | ||
| 9: "calc(var(--tsrd-font-size) * 2.25)", | ||
| 10: "calc(var(--tsrd-font-size) * 2.5)", | ||
| 11: "calc(var(--tsrd-font-size) * 2.75)", | ||
| 12: "calc(var(--tsrd-font-size) * 3)", | ||
| 14: "calc(var(--tsrd-font-size) * 3.5)", | ||
| 16: "calc(var(--tsrd-font-size) * 4)", | ||
| 20: "calc(var(--tsrd-font-size) * 5)", | ||
| 24: "calc(var(--tsrd-font-size) * 6)", | ||
| 28: "calc(var(--tsrd-font-size) * 7)", | ||
| 32: "calc(var(--tsrd-font-size) * 8)", | ||
| 36: "calc(var(--tsrd-font-size) * 9)", | ||
| 40: "calc(var(--tsrd-font-size) * 10)", | ||
| 44: "calc(var(--tsrd-font-size) * 11)", | ||
| 48: "calc(var(--tsrd-font-size) * 12)", | ||
| 52: "calc(var(--tsrd-font-size) * 13)", | ||
| 56: "calc(var(--tsrd-font-size) * 14)", | ||
| 60: "calc(var(--tsrd-font-size) * 15)", | ||
| 64: "calc(var(--tsrd-font-size) * 16)", | ||
| 72: "calc(var(--tsrd-font-size) * 18)", | ||
| 80: "calc(var(--tsrd-font-size) * 20)", | ||
| 96: "calc(var(--tsrd-font-size) * 24)" | ||
| }, | ||
| shadow: { | ||
| xs: (_ = "rgb(0 0 0 / 0.1)") => `0 1px 2px 0 rgb(0 0 0 / 0.05)`, | ||
| sm: (color = "rgb(0 0 0 / 0.1)") => `0 1px 3px 0 ${color}, 0 1px 2px -1px ${color}`, | ||
| md: (color = "rgb(0 0 0 / 0.1)") => `0 4px 6px -1px ${color}, 0 2px 4px -2px ${color}`, | ||
| lg: (color = "rgb(0 0 0 / 0.1)") => `0 10px 15px -3px ${color}, 0 4px 6px -4px ${color}`, | ||
| xl: (color = "rgb(0 0 0 / 0.1)") => `0 20px 25px -5px ${color}, 0 8px 10px -6px ${color}`, | ||
| "2xl": (color = "rgb(0 0 0 / 0.25)") => `0 25px 50px -12px ${color}`, | ||
| inner: (color = "rgb(0 0 0 / 0.05)") => `inset 0 2px 4px 0 ${color}`, | ||
| none: () => `none` | ||
| }, | ||
| zIndices: { | ||
| hide: -1, | ||
| auto: "auto", | ||
| base: 0, | ||
| docked: 10, | ||
| dropdown: 1e3, | ||
| sticky: 1100, | ||
| banner: 1200, | ||
| overlay: 1300, | ||
| modal: 1400, | ||
| popover: 1500, | ||
| skipLink: 1600, | ||
| toast: 1700, | ||
| tooltip: 1800 | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/styles/use-styles.ts | ||
| const stylesFactory = (theme) => { | ||
| const { colors, font, size, alpha, border } = tokens; | ||
| const { fontFamily, size: fontSize } = font; | ||
| const css = goober.css; | ||
| const t = (light, dark) => theme === "light" ? light : dark; | ||
| return { | ||
| mainContainer: css` | ||
| display: flex; | ||
| flex: 1; | ||
| min-height: 0; | ||
| overflow: hidden; | ||
| padding: ${size[2]}; | ||
| padding-top: 0; | ||
| margin-top: ${size[2]}; | ||
| `, | ||
| dragHandle: css` | ||
| width: 8px; | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| cursor: col-resize; | ||
| position: relative; | ||
| transition: all 0.2s ease; | ||
| user-select: none; | ||
| pointer-events: all; | ||
| margin: 0 ${size[1]}; | ||
| border-radius: 2px; | ||
| &:hover { | ||
| background: ${t(colors.blue[600], colors.blue[500])}; | ||
| margin: 0 ${size[1]}; | ||
| } | ||
| &.dragging { | ||
| background: ${t(colors.blue[700], colors.blue[600])}; | ||
| margin: 0 ${size[1]}; | ||
| } | ||
| &::after { | ||
| content: ''; | ||
| position: absolute; | ||
| top: 50%; | ||
| left: 50%; | ||
| transform: translate(-50%, -50%); | ||
| width: 2px; | ||
| height: 20px; | ||
| background: ${t(colors.gray[400], colors.darkGray[400])}; | ||
| border-radius: 1px; | ||
| pointer-events: none; | ||
| } | ||
| &:hover::after, | ||
| &.dragging::after { | ||
| background: ${t(colors.blue[500], colors.blue[300])}; | ||
| } | ||
| `, | ||
| leftPanel: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| min-height: 0; | ||
| flex-shrink: 0; | ||
| `, | ||
| rightPanel: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| min-height: 0; | ||
| flex: 1; | ||
| `, | ||
| panelHeader: css` | ||
| font-size: ${fontSize.md}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.blue[700], colors.blue[400])}; | ||
| padding: ${size[2]}; | ||
| border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| flex-shrink: 0; | ||
| `, | ||
| utilList: css` | ||
| flex: 1; | ||
| overflow-y: auto; | ||
| padding: ${size[1]}; | ||
| min-height: 0; | ||
| `, | ||
| utilGroup: css` | ||
| margin-bottom: ${size[2]}; | ||
| `, | ||
| utilGroupHeader: css` | ||
| font-size: ${fontSize.xs}; | ||
| font-weight: ${font.weight.semibold}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| margin-bottom: ${size[1]}; | ||
| padding: ${size[1]} ${size[2]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| `, | ||
| utilRow: css` | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| padding: ${size[2]}; | ||
| margin-bottom: ${size[1]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| cursor: pointer; | ||
| transition: all 0.2s ease; | ||
| border: 1px solid transparent; | ||
| &:hover { | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-color: ${t(colors.gray[400], colors.darkGray[500])}; | ||
| } | ||
| `, | ||
| utilRowSelected: css` | ||
| background: ${t(colors.blue[100], colors.blue[900] + alpha[20])}; | ||
| border-color: ${t(colors.blue[600], colors.blue[500])}; | ||
| box-shadow: 0 0 0 1px | ||
| ${t(colors.blue[600] + alpha[30], colors.blue[500] + alpha[30])}; | ||
| `, | ||
| utilKey: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| flex: 1; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| `, | ||
| utilStatus: css` | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| padding: ${size[1]} ${size[1]}; | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-radius: ${border.radius.sm}; | ||
| margin-left: ${size[1]}; | ||
| `, | ||
| stateDetails: css` | ||
| flex: 1; | ||
| overflow-y: auto; | ||
| padding: ${size[2]}; | ||
| min-height: 0; | ||
| `, | ||
| stateHeader: css` | ||
| margin-bottom: ${size[2]}; | ||
| padding-bottom: ${size[2]}; | ||
| border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| `, | ||
| stateTitle: css` | ||
| font-size: ${fontSize.md}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.blue[700], colors.blue[400])}; | ||
| margin-bottom: ${size[1]}; | ||
| `, | ||
| stateKey: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| word-break: break-all; | ||
| `, | ||
| stateContent: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[2]}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| `, | ||
| detailsGrid: css` | ||
| display: grid; | ||
| grid-template-columns: 1fr; | ||
| gap: ${size[2]}; | ||
| align-items: start; | ||
| `, | ||
| detailSection: css` | ||
| background: ${t(colors.white, colors.darkGray[700])}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[2]}; | ||
| `, | ||
| detailSectionHeader: css` | ||
| font-size: ${fontSize.sm}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.gray[800], colors.gray[200])}; | ||
| margin-bottom: ${size[1]}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.04em; | ||
| `, | ||
| actionsRow: css` | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: ${size[2]}; | ||
| `, | ||
| actionButton: css` | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: ${size[1]}; | ||
| padding: ${size[1]} ${size[2]}; | ||
| border-radius: ${border.radius.md}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[500])}; | ||
| background: ${t(colors.gray[200], colors.darkGray[600])}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| font-size: ${fontSize.xs}; | ||
| cursor: pointer; | ||
| user-select: none; | ||
| transition: | ||
| background 0.15s, | ||
| border-color 0.15s; | ||
| &:hover { | ||
| background: ${t(colors.gray[300], colors.darkGray[500])}; | ||
| border-color: ${t(colors.gray[400], colors.darkGray[400])}; | ||
| } | ||
| &:disabled { | ||
| opacity: 0.5; | ||
| cursor: not-allowed; | ||
| &:hover { | ||
| background: ${t(colors.gray[200], colors.darkGray[600])}; | ||
| border-color: ${t(colors.gray[300], colors.darkGray[500])}; | ||
| } | ||
| } | ||
| `, | ||
| actionDotBlue: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.blue[400]}; | ||
| `, | ||
| actionDotGreen: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.green[400]}; | ||
| `, | ||
| actionDotRed: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.red[400]}; | ||
| `, | ||
| actionDotYellow: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.yellow[400]}; | ||
| `, | ||
| actionDotOrange: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.pink[400]}; | ||
| `, | ||
| actionDotPurple: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.purple[400]}; | ||
| `, | ||
| infoGrid: css` | ||
| display: grid; | ||
| grid-template-columns: auto 1fr; | ||
| gap: ${size[1]}; | ||
| row-gap: ${size[1]}; | ||
| align-items: center; | ||
| `, | ||
| infoLabel: css` | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| font-size: ${fontSize.xs}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| `, | ||
| infoValueMono: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| word-break: break-all; | ||
| `, | ||
| noSelection: css` | ||
| flex: 1; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| color: ${t(colors.gray[500], colors.gray[500])}; | ||
| font-style: italic; | ||
| text-align: center; | ||
| padding: ${size[4]}; | ||
| `, | ||
| sectionContainer: css` | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: ${size[4]}; | ||
| `, | ||
| section: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| box-shadow: ${tokens.shadow.md(t(colors.gray[400] + alpha[80], colors.black + alpha[80]))}; | ||
| padding: ${size[4]}; | ||
| margin-bottom: ${size[4]}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| min-width: 0; | ||
| max-width: 33%; | ||
| max-height: fit-content; | ||
| `, | ||
| sectionHeader: css` | ||
| font-size: ${fontSize.lg}; | ||
| font-weight: ${font.weight.bold}; | ||
| margin-bottom: ${size[2]}; | ||
| color: ${t(colors.blue[600], colors.blue[400])}; | ||
| letter-spacing: 0.01em; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: ${size[2]}; | ||
| `, | ||
| sectionEmpty: css` | ||
| color: ${t(colors.gray[500], colors.gray[500])}; | ||
| font-size: ${fontSize.sm}; | ||
| font-style: italic; | ||
| margin: ${size[2]} 0; | ||
| `, | ||
| instanceList: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: ${size[2]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| `, | ||
| instanceCard: css` | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[3]}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| font-size: ${fontSize.sm}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| font-family: ${fontFamily.mono}; | ||
| overflow-x: auto; | ||
| transition: | ||
| box-shadow 0.3s, | ||
| background 0.3s; | ||
| ` | ||
| }; | ||
| }; | ||
| function useStyles() { | ||
| const { theme } = useTheme(); | ||
| const [styles, setStyles] = createSignal(stylesFactory(theme())); | ||
| createEffect(() => { | ||
| setStyles(stylesFactory(theme())); | ||
| }); | ||
| return styles; | ||
| } | ||
| //#endregion | ||
| //#region src/components/util-groups.ts | ||
| const UTIL_GROUPS = [ | ||
| { | ||
| key: "debouncers", | ||
| label: "Debouncers", | ||
| displayName: "Debouncer" | ||
| }, | ||
| { | ||
| key: "throttlers", | ||
| label: "Throttlers", | ||
| displayName: "Throttler" | ||
| }, | ||
| { | ||
| key: "batchers", | ||
| label: "Batchers", | ||
| displayName: "Batcher" | ||
| }, | ||
| { | ||
| key: "queuers", | ||
| label: "Queuers", | ||
| displayName: "Queuer" | ||
| }, | ||
| { | ||
| key: "rateLimiters", | ||
| label: "Rate Limiters", | ||
| displayName: "Rate Limiter" | ||
| }, | ||
| { | ||
| key: "asyncDebouncers", | ||
| label: "Async Debouncers", | ||
| displayName: "Async Debouncer" | ||
| }, | ||
| { | ||
| key: "asyncThrottlers", | ||
| label: "Async Throttlers", | ||
| displayName: "Async Throttler" | ||
| }, | ||
| { | ||
| key: "asyncBatchers", | ||
| label: "Async Batchers", | ||
| displayName: "Async Batcher" | ||
| }, | ||
| { | ||
| key: "asyncQueuers", | ||
| label: "Async Queuers", | ||
| displayName: "Async Queuer" | ||
| }, | ||
| { | ||
| key: "asyncRateLimiters", | ||
| label: "Async Rate Limiters", | ||
| displayName: "Async Rate Limiter" | ||
| } | ||
| ]; | ||
| //#endregion | ||
| //#region src/components/UtilList.tsx | ||
| var _tmpl$$4 = [ | ||
| "<div", | ||
| ">", | ||
| "</div>" | ||
| ], _tmpl$2$2 = [ | ||
| "<div", | ||
| "><div", | ||
| ">", | ||
| "</div>", | ||
| "</div>" | ||
| ], _tmpl$3$2 = [ | ||
| "<div", | ||
| "><div", | ||
| ">", | ||
| "</div><div", | ||
| ">", | ||
| "</div></div>" | ||
| ]; | ||
| function UtilList(props) { | ||
| const styles = useStyles(); | ||
| return ssr(_tmpl$$4, ssrAttribute("class", escape(styles().utilList, true), false), escape(createComponent(For, { | ||
| each: UTIL_GROUPS, | ||
| children: (group) => props.utilState()[group.key].length > 0 && ssr(_tmpl$2$2, ssrAttribute("class", escape(styles().utilGroup, true), false), ssrAttribute("class", escape(styles().utilGroupHeader, true), false), escape(group.label), escape(createComponent(For, { | ||
| get each() { | ||
| return props.utilState()[group.key]; | ||
| }, | ||
| children: (instance) => { | ||
| const status = (() => { | ||
| try { | ||
| const statusAccessor = useStore(instance.store, (s) => s.status); | ||
| return () => statusAccessor() ?? "unknown"; | ||
| } catch { | ||
| return () => "unknown"; | ||
| } | ||
| })(); | ||
| return ssr(_tmpl$3$2, ssrAttribute("class", escape(clsx(styles().utilRow, props.selectedKey() === instance.key && styles().utilRowSelected), true), false), ssrAttribute("class", escape(styles().utilKey, true), false), escape(instance.key), ssrAttribute("class", escape(styles().utilStatus, true), false), escape(status())); | ||
| } | ||
| }))) | ||
| }))); | ||
| } | ||
| //#endregion | ||
| //#region src/components/ActionButtons.tsx | ||
| var _tmpl$$3 = ["<div", ">No actions available for this util</div>"], _tmpl$2$1 = [ | ||
| "<div", | ||
| ">", | ||
| "", | ||
| "", | ||
| "", | ||
| "", | ||
| "", | ||
| "</div>" | ||
| ], _tmpl$3$1 = [ | ||
| "<button", | ||
| "><span", | ||
| "></span>", | ||
| "</button>" | ||
| ], _tmpl$4 = [ | ||
| "<button", | ||
| "", | ||
| "><span", | ||
| "></span>Flush</button>" | ||
| ], _tmpl$5 = [ | ||
| "<button", | ||
| "", | ||
| "><span", | ||
| "></span>Cancel</button>" | ||
| ], _tmpl$6 = [ | ||
| "<button", | ||
| "><span", | ||
| "></span>Reset</button>" | ||
| ], _tmpl$7 = [ | ||
| "<button", | ||
| "", | ||
| "><span", | ||
| "></span>Clear</button>" | ||
| ]; | ||
| function ActionButtons(props) { | ||
| const styles = useStyles(); | ||
| const utilInstance = props.instance; | ||
| const utilState = utilInstance?.store?.state; | ||
| const hasPending = utilState && "isPending" in utilState; | ||
| const hasEmpty = utilState && "isEmpty" in utilState; | ||
| const isPending = hasPending ? !!utilState.isPending : false; | ||
| const isEmpty = hasEmpty ? utilState.isEmpty : void 0; | ||
| const hasFlush = typeof utilInstance.flush === "function"; | ||
| const hasCancel = typeof utilInstance.cancel === "function"; | ||
| const hasReset = typeof utilInstance.reset === "function"; | ||
| const hasClear = typeof utilInstance.clear === "function"; | ||
| const hasStart = typeof utilInstance.start === "function"; | ||
| const hasStop = typeof utilInstance.stop === "function"; | ||
| const hasStartStop = hasStart && hasStop; | ||
| const isRunning = utilState?.isRunning ?? true; | ||
| if (!hasPending && !hasFlush && !hasCancel && !hasReset && !hasClear) return ssr(_tmpl$$3, ssrAttribute("class", escape(styles().sectionEmpty, true), false)); | ||
| `${props.utilName}`; | ||
| return ssr(_tmpl$2$1, ssrAttribute("class", escape(styles().actionsRow, true), false), hasPending && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotBlue, true), false), isPending ? "Restore Pending" : "Trigger Pending"), hasFlush && ssr(_tmpl$4, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, true), ssrAttribute("class", escape(styles().actionDotGreen, true), false)), hasCancel && ssr(_tmpl$5, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, true), ssrAttribute("class", escape(styles().actionDotRed, true), false)), hasReset && ssr(_tmpl$6, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotYellow, true), false)), hasClear && ssr(_tmpl$7, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("disabled", props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, true), ssrAttribute("class", escape(styles().actionDotOrange, true), false)), hasStartStop && ssr(_tmpl$3$1, ssrAttribute("class", escape(styles().actionButton, true), false), ssrAttribute("class", escape(styles().actionDotPurple, true), false), isRunning ? "Stop" : "Start")); | ||
| } | ||
| //#endregion | ||
| //#region src/components/StateHeader.tsx | ||
| var _tmpl$$2 = [ | ||
| "<div", | ||
| "><div", | ||
| ">", | ||
| "</div><div style=\"", | ||
| "\"><div", | ||
| "><div", | ||
| ">Key</div><div", | ||
| ">", | ||
| "</div><div", | ||
| ">Last Updated</div><div", | ||
| ">", | ||
| " (", | ||
| ")</div></div><div", | ||
| " style=\"", | ||
| "\">", | ||
| "% reduction</div></div></div>" | ||
| ]; | ||
| dayjs.extend(relativeTime); | ||
| function StateHeader(props) { | ||
| const styles = useStyles(); | ||
| const [now, setNow] = createSignal(Date.now()); | ||
| onMount(() => { | ||
| const interval = setInterval(() => { | ||
| setNow(Date.now()); | ||
| }, 1e3); | ||
| onCleanup(() => { | ||
| clearInterval(interval); | ||
| }); | ||
| }); | ||
| const entry = props.selectedInstance(); | ||
| if (!entry) return null; | ||
| const key = entry.instance.key; | ||
| const updatedAt = props.utilState().lastUpdatedByKey[key] ?? Date.now(); | ||
| const getRelativeTime = () => { | ||
| const diffMs = now() - updatedAt; | ||
| const diffSeconds = Math.floor(diffMs / 1e3); | ||
| if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`; | ||
| return dayjs(updatedAt).fromNow(); | ||
| }; | ||
| const getReductionPercentage = () => { | ||
| const state = entry.instance.store?.state || entry.instance.state; | ||
| if (!state) return 0; | ||
| const completedExecutions = entry.type.toLowerCase().includes("async") ? state.settleCount || 0 : state.executionCount || 0; | ||
| if (entry.type.toLowerCase().includes("batcher")) { | ||
| const totalItemsProcessed = state.totalItemsProcessed || 0; | ||
| if (totalItemsProcessed === 0) return 0; | ||
| return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100); | ||
| } | ||
| let requestCount = 0; | ||
| if (state.maybeExecuteCount !== void 0) requestCount = state.maybeExecuteCount; | ||
| else if (state.addItemCount !== void 0) requestCount = state.addItemCount; | ||
| else return 0; | ||
| if (requestCount === 0) return 0; | ||
| const reduction = requestCount - completedExecutions; | ||
| return Math.round(reduction / requestCount * 100); | ||
| }; | ||
| const reductionPercentage = getReductionPercentage(); | ||
| return ssr(_tmpl$$2, ssrAttribute("class", escape(styles().stateHeader, true), false), ssrAttribute("class", escape(styles().stateTitle, true), false), escape(entry.type), ssrStyleProperty("display:", "flex") + ssrStyleProperty(";align-items:", "center") + ssrStyleProperty(";gap:", "16px"), ssrAttribute("class", escape(styles().infoGrid, true), false), ssrAttribute("class", escape(styles().infoLabel, true), false), ssrAttribute("class", escape(styles().infoValueMono, true), false), escape(key), ssrAttribute("class", escape(styles().infoLabel, true), false), ssrAttribute("class", escape(styles().infoValueMono, true), false), escape(new Date(updatedAt).toLocaleTimeString()), escape(getRelativeTime()), ssrAttribute("class", escape(styles().infoValueMono, true), false), ssrStyleProperty("margin-left:", "auto") + ssrStyleProperty(";font-weight:", "bold"), escape(reductionPercentage)); | ||
| } | ||
| //#endregion | ||
| //#region src/components/DetailsPanel.tsx | ||
| var _tmpl$$1 = [ | ||
| "<div", | ||
| ">", | ||
| "</div>" | ||
| ], _tmpl$2 = ["<div", ">Select a util from the left panel to view its state</div>"], _tmpl$3 = [ | ||
| "<div", | ||
| "><div", | ||
| "><div", | ||
| ">Actions</div>", | ||
| "</div><div", | ||
| "><div", | ||
| ">State</div><div", | ||
| ">", | ||
| "</div></div><div", | ||
| "><div", | ||
| ">Options</div><div", | ||
| ">", | ||
| "</div></div></div>" | ||
| ]; | ||
| function DetailsPanel(props) { | ||
| const styles = useStyles(); | ||
| return ssr(_tmpl$$1, ssrAttribute("class", escape(styles().stateDetails, true), false), (() => { | ||
| const entry = props.selectedInstance(); | ||
| if (!entry) return ssr(_tmpl$2, ssrAttribute("class", escape(styles().noSelection, true), false)); | ||
| return escape([createComponent(StateHeader, { | ||
| get selectedInstance() { | ||
| return props.selectedInstance; | ||
| }, | ||
| get utilState() { | ||
| return props.utilState; | ||
| } | ||
| }), ssr(_tmpl$3, ssrAttribute("class", escape(styles().detailsGrid, true), false), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), escape(createComponent(ActionButtons, { | ||
| get instance() { | ||
| return entry.instance; | ||
| }, | ||
| get utilName() { | ||
| return entry.type; | ||
| } | ||
| })), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(JsonTree, { get value() { | ||
| return entry.instance.store?.state; | ||
| } })), ssrAttribute("class", escape(styles().detailSection, true), false), ssrAttribute("class", escape(styles().detailSectionHeader, true), false), ssrAttribute("class", escape(styles().stateContent, true), false), escape(createComponent(JsonTree, { get value() { | ||
| return entry.instance.options; | ||
| } })))]); | ||
| })()); | ||
| } | ||
| //#endregion | ||
| //#region src/components/Shell.tsx | ||
| var _tmpl$ = [ | ||
| "<div", | ||
| "><div", | ||
| " style=\"", | ||
| "\">", | ||
| "</div><div class=\"", | ||
| "\"></div><div", | ||
| " style=\"", | ||
| "\"><div", | ||
| ">Details</div>", | ||
| "</div></div>" | ||
| ]; | ||
| function Shell() { | ||
| const styles = useStyles(); | ||
| const state = usePacerDevtoolsState(); | ||
| const utilState = () => state; | ||
| const [selectedKey, setSelectedKey] = createSignal(null); | ||
| const [leftPanelWidth, setLeftPanelWidth] = createSignal(300); | ||
| const [isDragging, setIsDragging] = createSignal(false); | ||
| const selectedInstance = createMemo(() => { | ||
| const key = selectedKey(); | ||
| if (!key) return null; | ||
| for (const group of UTIL_GROUPS) { | ||
| const instance = utilState()[group.key].find((inst) => inst.key === key); | ||
| if (instance) return { | ||
| instance, | ||
| type: group.displayName | ||
| }; | ||
| } | ||
| return null; | ||
| }); | ||
| let dragStartX = 0; | ||
| let dragStartWidth = 0; | ||
| const handleMouseMove = (e) => { | ||
| if (!isDragging()) return; | ||
| e.preventDefault(); | ||
| const deltaX = e.clientX - dragStartX; | ||
| setLeftPanelWidth(Math.max(150, Math.min(800, dragStartWidth + deltaX))); | ||
| }; | ||
| const handleMouseUp = () => { | ||
| setIsDragging(false); | ||
| document.body.style.cursor = ""; | ||
| document.body.style.userSelect = ""; | ||
| }; | ||
| onMount(() => { | ||
| document.addEventListener("mousemove", handleMouseMove); | ||
| document.addEventListener("mouseup", handleMouseUp); | ||
| }); | ||
| onCleanup(() => { | ||
| document.removeEventListener("mousemove", handleMouseMove); | ||
| document.removeEventListener("mouseup", handleMouseUp); | ||
| }); | ||
| return createComponent(MainPanel, { get children() { | ||
| return [createComponent(Header, { get children() { | ||
| return createComponent(HeaderLogo, { | ||
| flavor: { | ||
| light: "#84cc16", | ||
| dark: "#84cc16" | ||
| }, | ||
| children: "TanStack Pacer" | ||
| }); | ||
| } }), ssr(_tmpl$, ssrAttribute("class", escape(styles().mainContainer, true), false), ssrAttribute("class", escape(styles().leftPanel, true), false), ssrStyleProperty("width:", `${escape(leftPanelWidth(), true)}px`) + ssrStyleProperty(";min-width:", "150px") + ssrStyleProperty(";max-width:", "800px"), escape(createComponent(UtilList, { | ||
| selectedKey, | ||
| setSelectedKey, | ||
| utilState | ||
| })), `${escape(styles().dragHandle, true)} ${isDragging() ? "dragging" : ""}`, ssrAttribute("class", escape(styles().rightPanel, true), false), ssrStyleProperty("flex:", 1), ssrAttribute("class", escape(styles().panelHeader, true), false), escape(createComponent(DetailsPanel, { | ||
| selectedInstance, | ||
| utilState | ||
| })))]; | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/components/index.tsx | ||
| function PacerDevtools(props) { | ||
| return createComponent(ThemeContextProvider, { | ||
| get theme() { | ||
| return props.theme; | ||
| }, | ||
| get children() { | ||
| return createComponent(PacerContextProvider, { get children() { | ||
| return createComponent(Shell, {}); | ||
| } }); | ||
| } | ||
| }); | ||
| } | ||
| //#endregion | ||
| export { PacerDevtools as default }; |
| import { className, createComponent, delegateEvents, effect, insert, memo, setStyleProperty, template } from "solid-js/web"; | ||
| import { Header, HeaderLogo, JsonTree, MainPanel, ThemeContextProvider, useTheme } from "@tanstack/devtools-ui"; | ||
| import { createStore } from "solid-js/store"; | ||
| import { For, createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js"; | ||
| import { pacerEventClient } from "@tanstack/pacer/event-client"; | ||
| import * as goober from "goober"; | ||
| import { useStore } from "@tanstack/solid-store"; | ||
| import clsx from "clsx"; | ||
| import { pacerEventClient as pacerEventClient$1 } from "@tanstack/pacer"; | ||
| import dayjs from "dayjs"; | ||
| import relativeTime from "dayjs/plugin/relativeTime.js"; | ||
| //#region src/PacerContextProvider.tsx | ||
| const initialPacerDevtoolsStore = { | ||
| asyncBatchers: [], | ||
| asyncDebouncers: [], | ||
| asyncQueuers: [], | ||
| asyncRateLimiters: [], | ||
| asyncThrottlers: [], | ||
| batchers: [], | ||
| debouncers: [], | ||
| queuers: [], | ||
| rateLimiters: [], | ||
| throttlers: [], | ||
| lastUpdatedByKey: {} | ||
| }; | ||
| const PacerDevtoolsContext = createContext([initialPacerDevtoolsStore, () => {}]); | ||
| const updateOrAddToArray = (oldArray, newItem) => { | ||
| const index = oldArray.findIndex((item) => item.key === newItem.key); | ||
| if (index !== -1) return oldArray.map((item, i) => i === index ? newItem : item); | ||
| return [...oldArray, newItem]; | ||
| }; | ||
| function PacerContextProvider(props) { | ||
| const [store, setStore] = createStore(initialPacerDevtoolsStore); | ||
| createEffect(() => { | ||
| onCleanup(pacerEventClient.onAllPluginEvents((_e) => { | ||
| const e = _e; | ||
| switch (e.type) { | ||
| case "pacer:AsyncBatcher": | ||
| setStore({ | ||
| asyncBatchers: updateOrAddToArray(store.asyncBatchers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:AsyncDebouncer": | ||
| setStore({ | ||
| asyncDebouncers: updateOrAddToArray(store.asyncDebouncers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:AsyncQueuer": | ||
| setStore({ | ||
| asyncQueuers: updateOrAddToArray(store.asyncQueuers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:AsyncRateLimiter": | ||
| setStore({ | ||
| asyncRateLimiters: updateOrAddToArray(store.asyncRateLimiters, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:AsyncThrottler": | ||
| setStore({ | ||
| asyncThrottlers: updateOrAddToArray(store.asyncThrottlers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:Batcher": | ||
| setStore({ | ||
| batchers: updateOrAddToArray(store.batchers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:Debouncer": | ||
| setStore({ | ||
| debouncers: updateOrAddToArray(store.debouncers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:Queuer": | ||
| setStore({ | ||
| queuers: updateOrAddToArray(store.queuers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:RateLimiter": | ||
| setStore({ | ||
| rateLimiters: updateOrAddToArray(store.rateLimiters, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| case "pacer:Throttler": | ||
| setStore({ | ||
| throttlers: updateOrAddToArray(store.throttlers, e.payload), | ||
| lastUpdatedByKey: { | ||
| ...store.lastUpdatedByKey, | ||
| [e.payload.key]: Date.now() | ||
| } | ||
| }); | ||
| break; | ||
| } | ||
| })); | ||
| }); | ||
| return createComponent(PacerDevtoolsContext.Provider, { | ||
| value: [store, setStore], | ||
| get children() { | ||
| return props.children; | ||
| } | ||
| }); | ||
| } | ||
| const usePacerDevtoolsContext = () => { | ||
| return useContext(PacerDevtoolsContext); | ||
| }; | ||
| const usePacerDevtoolsState = () => { | ||
| const [state] = usePacerDevtoolsContext(); | ||
| return state; | ||
| }; | ||
| //#endregion | ||
| //#region src/styles/tokens.ts | ||
| const tokens = { | ||
| colors: { | ||
| inherit: "inherit", | ||
| current: "currentColor", | ||
| transparent: "transparent", | ||
| black: "#000000", | ||
| white: "#ffffff", | ||
| neutral: { | ||
| 50: "#f9fafb", | ||
| 100: "#f2f4f7", | ||
| 200: "#eaecf0", | ||
| 300: "#d0d5dd", | ||
| 400: "#98a2b3", | ||
| 500: "#667085", | ||
| 600: "#475467", | ||
| 700: "#344054", | ||
| 800: "#1d2939", | ||
| 900: "#101828" | ||
| }, | ||
| darkGray: { | ||
| 50: "#525c7a", | ||
| 100: "#49536e", | ||
| 200: "#414962", | ||
| 300: "#394056", | ||
| 400: "#313749", | ||
| 500: "#292e3d", | ||
| 600: "#212530", | ||
| 700: "#191c24", | ||
| 800: "#111318", | ||
| 900: "#0b0d10" | ||
| }, | ||
| gray: { | ||
| 50: "#f9fafb", | ||
| 100: "#f2f4f7", | ||
| 200: "#eaecf0", | ||
| 300: "#d0d5dd", | ||
| 400: "#98a2b3", | ||
| 500: "#667085", | ||
| 600: "#475467", | ||
| 700: "#344054", | ||
| 800: "#1d2939", | ||
| 900: "#101828" | ||
| }, | ||
| blue: { | ||
| 25: "#F5FAFF", | ||
| 50: "#EFF8FF", | ||
| 100: "#D1E9FF", | ||
| 200: "#B2DDFF", | ||
| 300: "#84CAFF", | ||
| 400: "#53B1FD", | ||
| 500: "#2E90FA", | ||
| 600: "#1570EF", | ||
| 700: "#175CD3", | ||
| 800: "#1849A9", | ||
| 900: "#194185" | ||
| }, | ||
| green: { | ||
| 25: "#F6FEF9", | ||
| 50: "#ECFDF3", | ||
| 100: "#D1FADF", | ||
| 200: "#A6F4C5", | ||
| 300: "#6CE9A6", | ||
| 400: "#32D583", | ||
| 500: "#12B76A", | ||
| 600: "#039855", | ||
| 700: "#027A48", | ||
| 800: "#05603A", | ||
| 900: "#054F31" | ||
| }, | ||
| red: { | ||
| 50: "#fef2f2", | ||
| 100: "#fee2e2", | ||
| 200: "#fecaca", | ||
| 300: "#fca5a5", | ||
| 400: "#f87171", | ||
| 500: "#ef4444", | ||
| 600: "#dc2626", | ||
| 700: "#b91c1c", | ||
| 800: "#991b1b", | ||
| 900: "#7f1d1d", | ||
| 950: "#450a0a" | ||
| }, | ||
| yellow: { | ||
| 25: "#FFFCF5", | ||
| 50: "#FFFAEB", | ||
| 100: "#FEF0C7", | ||
| 200: "#FEDF89", | ||
| 300: "#FEC84B", | ||
| 400: "#FDB022", | ||
| 500: "#F79009", | ||
| 600: "#DC6803", | ||
| 700: "#B54708", | ||
| 800: "#93370D", | ||
| 900: "#7A2E0E" | ||
| }, | ||
| purple: { | ||
| 25: "#FAFAFF", | ||
| 50: "#F4F3FF", | ||
| 100: "#EBE9FE", | ||
| 200: "#D9D6FE", | ||
| 300: "#BDB4FE", | ||
| 400: "#9B8AFB", | ||
| 500: "#7A5AF8", | ||
| 600: "#6938EF", | ||
| 700: "#5925DC", | ||
| 800: "#4A1FB8", | ||
| 900: "#3E1C96" | ||
| }, | ||
| teal: { | ||
| 25: "#F6FEFC", | ||
| 50: "#F0FDF9", | ||
| 100: "#CCFBEF", | ||
| 200: "#99F6E0", | ||
| 300: "#5FE9D0", | ||
| 400: "#2ED3B7", | ||
| 500: "#15B79E", | ||
| 600: "#0E9384", | ||
| 700: "#107569", | ||
| 800: "#125D56", | ||
| 900: "#134E48" | ||
| }, | ||
| pink: { | ||
| 25: "#fdf2f8", | ||
| 50: "#fce7f3", | ||
| 100: "#fbcfe8", | ||
| 200: "#f9a8d4", | ||
| 300: "#f472b6", | ||
| 400: "#ec4899", | ||
| 500: "#db2777", | ||
| 600: "#be185d", | ||
| 700: "#9d174d", | ||
| 800: "#831843", | ||
| 900: "#500724" | ||
| }, | ||
| cyan: { | ||
| 25: "#ecfeff", | ||
| 50: "#cffafe", | ||
| 100: "#a5f3fc", | ||
| 200: "#67e8f9", | ||
| 300: "#22d3ee", | ||
| 400: "#06b6d4", | ||
| 500: "#0891b2", | ||
| 600: "#0e7490", | ||
| 700: "#155e75", | ||
| 800: "#164e63", | ||
| 900: "#083344" | ||
| } | ||
| }, | ||
| alpha: { | ||
| 100: "ff", | ||
| 90: "e5", | ||
| 80: "cc", | ||
| 70: "b3", | ||
| 60: "99", | ||
| 50: "80", | ||
| 40: "66", | ||
| 30: "4d", | ||
| 20: "33", | ||
| 10: "1a", | ||
| 0: "00" | ||
| }, | ||
| font: { | ||
| size: { | ||
| "2xs": "calc(var(--tsrd-font-size) * 0.625)", | ||
| xs: "calc(var(--tsrd-font-size) * 0.75)", | ||
| sm: "calc(var(--tsrd-font-size) * 0.875)", | ||
| md: "var(--tsrd-font-size)", | ||
| lg: "calc(var(--tsrd-font-size) * 1.125)", | ||
| xl: "calc(var(--tsrd-font-size) * 1.25)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 1.5)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 1.875)", | ||
| "4xl": "calc(var(--tsrd-font-size) * 2.25)", | ||
| "5xl": "calc(var(--tsrd-font-size) * 3)", | ||
| "6xl": "calc(var(--tsrd-font-size) * 3.75)", | ||
| "7xl": "calc(var(--tsrd-font-size) * 4.5)", | ||
| "8xl": "calc(var(--tsrd-font-size) * 6)", | ||
| "9xl": "calc(var(--tsrd-font-size) * 8)" | ||
| }, | ||
| lineHeight: { | ||
| "3xs": "calc(var(--tsrd-font-size) * 0.75)", | ||
| "2xs": "calc(var(--tsrd-font-size) * 0.875)", | ||
| xs: "calc(var(--tsrd-font-size) * 1)", | ||
| sm: "calc(var(--tsrd-font-size) * 1.25)", | ||
| md: "calc(var(--tsrd-font-size) * 1.5)", | ||
| lg: "calc(var(--tsrd-font-size) * 1.75)", | ||
| xl: "calc(var(--tsrd-font-size) * 2)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 2.25)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 2.5)", | ||
| "4xl": "calc(var(--tsrd-font-size) * 2.75)", | ||
| "5xl": "calc(var(--tsrd-font-size) * 3)", | ||
| "6xl": "calc(var(--tsrd-font-size) * 3.25)", | ||
| "7xl": "calc(var(--tsrd-font-size) * 3.5)", | ||
| "8xl": "calc(var(--tsrd-font-size) * 3.75)", | ||
| "9xl": "calc(var(--tsrd-font-size) * 4)" | ||
| }, | ||
| weight: { | ||
| thin: "100", | ||
| extralight: "200", | ||
| light: "300", | ||
| normal: "400", | ||
| medium: "500", | ||
| semibold: "600", | ||
| bold: "700", | ||
| extrabold: "800", | ||
| black: "900" | ||
| }, | ||
| fontFamily: { | ||
| sans: "ui-sans-serif, Inter, system-ui, sans-serif, sans-serif", | ||
| mono: `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace` | ||
| } | ||
| }, | ||
| breakpoints: { | ||
| xs: "320px", | ||
| sm: "640px", | ||
| md: "768px", | ||
| lg: "1024px", | ||
| xl: "1280px", | ||
| "2xl": "1536px" | ||
| }, | ||
| border: { radius: { | ||
| none: "0px", | ||
| xs: "calc(var(--tsrd-font-size) * 0.125)", | ||
| sm: "calc(var(--tsrd-font-size) * 0.25)", | ||
| md: "calc(var(--tsrd-font-size) * 0.375)", | ||
| lg: "calc(var(--tsrd-font-size) * 0.5)", | ||
| xl: "calc(var(--tsrd-font-size) * 0.75)", | ||
| "2xl": "calc(var(--tsrd-font-size) * 1)", | ||
| "3xl": "calc(var(--tsrd-font-size) * 1.5)", | ||
| full: "9999px" | ||
| } }, | ||
| size: { | ||
| 0: "0px", | ||
| .25: "calc(var(--tsrd-font-size) * 0.0625)", | ||
| .5: "calc(var(--tsrd-font-size) * 0.125)", | ||
| 1: "calc(var(--tsrd-font-size) * 0.25)", | ||
| 1.5: "calc(var(--tsrd-font-size) * 0.375)", | ||
| 2: "calc(var(--tsrd-font-size) * 0.5)", | ||
| 2.5: "calc(var(--tsrd-font-size) * 0.625)", | ||
| 3: "calc(var(--tsrd-font-size) * 0.75)", | ||
| 3.5: "calc(var(--tsrd-font-size) * 0.875)", | ||
| 4: "calc(var(--tsrd-font-size) * 1)", | ||
| 4.5: "calc(var(--tsrd-font-size) * 1.125)", | ||
| 5: "calc(var(--tsrd-font-size) * 1.25)", | ||
| 5.5: "calc(var(--tsrd-font-size) * 1.375)", | ||
| 6: "calc(var(--tsrd-font-size) * 1.5)", | ||
| 6.5: "calc(var(--tsrd-font-size) * 1.625)", | ||
| 7: "calc(var(--tsrd-font-size) * 1.75)", | ||
| 8: "calc(var(--tsrd-font-size) * 2)", | ||
| 9: "calc(var(--tsrd-font-size) * 2.25)", | ||
| 10: "calc(var(--tsrd-font-size) * 2.5)", | ||
| 11: "calc(var(--tsrd-font-size) * 2.75)", | ||
| 12: "calc(var(--tsrd-font-size) * 3)", | ||
| 14: "calc(var(--tsrd-font-size) * 3.5)", | ||
| 16: "calc(var(--tsrd-font-size) * 4)", | ||
| 20: "calc(var(--tsrd-font-size) * 5)", | ||
| 24: "calc(var(--tsrd-font-size) * 6)", | ||
| 28: "calc(var(--tsrd-font-size) * 7)", | ||
| 32: "calc(var(--tsrd-font-size) * 8)", | ||
| 36: "calc(var(--tsrd-font-size) * 9)", | ||
| 40: "calc(var(--tsrd-font-size) * 10)", | ||
| 44: "calc(var(--tsrd-font-size) * 11)", | ||
| 48: "calc(var(--tsrd-font-size) * 12)", | ||
| 52: "calc(var(--tsrd-font-size) * 13)", | ||
| 56: "calc(var(--tsrd-font-size) * 14)", | ||
| 60: "calc(var(--tsrd-font-size) * 15)", | ||
| 64: "calc(var(--tsrd-font-size) * 16)", | ||
| 72: "calc(var(--tsrd-font-size) * 18)", | ||
| 80: "calc(var(--tsrd-font-size) * 20)", | ||
| 96: "calc(var(--tsrd-font-size) * 24)" | ||
| }, | ||
| shadow: { | ||
| xs: (_ = "rgb(0 0 0 / 0.1)") => `0 1px 2px 0 rgb(0 0 0 / 0.05)`, | ||
| sm: (color = "rgb(0 0 0 / 0.1)") => `0 1px 3px 0 ${color}, 0 1px 2px -1px ${color}`, | ||
| md: (color = "rgb(0 0 0 / 0.1)") => `0 4px 6px -1px ${color}, 0 2px 4px -2px ${color}`, | ||
| lg: (color = "rgb(0 0 0 / 0.1)") => `0 10px 15px -3px ${color}, 0 4px 6px -4px ${color}`, | ||
| xl: (color = "rgb(0 0 0 / 0.1)") => `0 20px 25px -5px ${color}, 0 8px 10px -6px ${color}`, | ||
| "2xl": (color = "rgb(0 0 0 / 0.25)") => `0 25px 50px -12px ${color}`, | ||
| inner: (color = "rgb(0 0 0 / 0.05)") => `inset 0 2px 4px 0 ${color}`, | ||
| none: () => `none` | ||
| }, | ||
| zIndices: { | ||
| hide: -1, | ||
| auto: "auto", | ||
| base: 0, | ||
| docked: 10, | ||
| dropdown: 1e3, | ||
| sticky: 1100, | ||
| banner: 1200, | ||
| overlay: 1300, | ||
| modal: 1400, | ||
| popover: 1500, | ||
| skipLink: 1600, | ||
| toast: 1700, | ||
| tooltip: 1800 | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/styles/use-styles.ts | ||
| const stylesFactory = (theme) => { | ||
| const { colors, font, size, alpha, border } = tokens; | ||
| const { fontFamily, size: fontSize } = font; | ||
| const css = goober.css; | ||
| const t = (light, dark) => theme === "light" ? light : dark; | ||
| return { | ||
| mainContainer: css` | ||
| display: flex; | ||
| flex: 1; | ||
| min-height: 0; | ||
| overflow: hidden; | ||
| padding: ${size[2]}; | ||
| padding-top: 0; | ||
| margin-top: ${size[2]}; | ||
| `, | ||
| dragHandle: css` | ||
| width: 8px; | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| cursor: col-resize; | ||
| position: relative; | ||
| transition: all 0.2s ease; | ||
| user-select: none; | ||
| pointer-events: all; | ||
| margin: 0 ${size[1]}; | ||
| border-radius: 2px; | ||
| &:hover { | ||
| background: ${t(colors.blue[600], colors.blue[500])}; | ||
| margin: 0 ${size[1]}; | ||
| } | ||
| &.dragging { | ||
| background: ${t(colors.blue[700], colors.blue[600])}; | ||
| margin: 0 ${size[1]}; | ||
| } | ||
| &::after { | ||
| content: ''; | ||
| position: absolute; | ||
| top: 50%; | ||
| left: 50%; | ||
| transform: translate(-50%, -50%); | ||
| width: 2px; | ||
| height: 20px; | ||
| background: ${t(colors.gray[400], colors.darkGray[400])}; | ||
| border-radius: 1px; | ||
| pointer-events: none; | ||
| } | ||
| &:hover::after, | ||
| &.dragging::after { | ||
| background: ${t(colors.blue[500], colors.blue[300])}; | ||
| } | ||
| `, | ||
| leftPanel: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| min-height: 0; | ||
| flex-shrink: 0; | ||
| `, | ||
| rightPanel: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| display: flex; | ||
| flex-direction: column; | ||
| overflow: hidden; | ||
| min-height: 0; | ||
| flex: 1; | ||
| `, | ||
| panelHeader: css` | ||
| font-size: ${fontSize.md}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.blue[700], colors.blue[400])}; | ||
| padding: ${size[2]}; | ||
| border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| flex-shrink: 0; | ||
| `, | ||
| utilList: css` | ||
| flex: 1; | ||
| overflow-y: auto; | ||
| padding: ${size[1]}; | ||
| min-height: 0; | ||
| `, | ||
| utilGroup: css` | ||
| margin-bottom: ${size[2]}; | ||
| `, | ||
| utilGroupHeader: css` | ||
| font-size: ${fontSize.xs}; | ||
| font-weight: ${font.weight.semibold}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| margin-bottom: ${size[1]}; | ||
| padding: ${size[1]} ${size[2]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| `, | ||
| utilRow: css` | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| padding: ${size[2]}; | ||
| margin-bottom: ${size[1]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| cursor: pointer; | ||
| transition: all 0.2s ease; | ||
| border: 1px solid transparent; | ||
| &:hover { | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-color: ${t(colors.gray[400], colors.darkGray[500])}; | ||
| } | ||
| `, | ||
| utilRowSelected: css` | ||
| background: ${t(colors.blue[100], colors.blue[900] + alpha[20])}; | ||
| border-color: ${t(colors.blue[600], colors.blue[500])}; | ||
| box-shadow: 0 0 0 1px | ||
| ${t(colors.blue[600] + alpha[30], colors.blue[500] + alpha[30])}; | ||
| `, | ||
| utilKey: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| flex: 1; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| white-space: nowrap; | ||
| `, | ||
| utilStatus: css` | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| padding: ${size[1]} ${size[1]}; | ||
| background: ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-radius: ${border.radius.sm}; | ||
| margin-left: ${size[1]}; | ||
| `, | ||
| stateDetails: css` | ||
| flex: 1; | ||
| overflow-y: auto; | ||
| padding: ${size[2]}; | ||
| min-height: 0; | ||
| `, | ||
| stateHeader: css` | ||
| margin-bottom: ${size[2]}; | ||
| padding-bottom: ${size[2]}; | ||
| border-bottom: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| `, | ||
| stateTitle: css` | ||
| font-size: ${fontSize.md}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.blue[700], colors.blue[400])}; | ||
| margin-bottom: ${size[1]}; | ||
| `, | ||
| stateKey: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| word-break: break-all; | ||
| `, | ||
| stateContent: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[2]}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| `, | ||
| detailsGrid: css` | ||
| display: grid; | ||
| grid-template-columns: 1fr; | ||
| gap: ${size[2]}; | ||
| align-items: start; | ||
| `, | ||
| detailSection: css` | ||
| background: ${t(colors.white, colors.darkGray[700])}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[2]}; | ||
| `, | ||
| detailSectionHeader: css` | ||
| font-size: ${fontSize.sm}; | ||
| font-weight: ${font.weight.bold}; | ||
| color: ${t(colors.gray[800], colors.gray[200])}; | ||
| margin-bottom: ${size[1]}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.04em; | ||
| `, | ||
| actionsRow: css` | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: ${size[2]}; | ||
| `, | ||
| actionButton: css` | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: ${size[1]}; | ||
| padding: ${size[1]} ${size[2]}; | ||
| border-radius: ${border.radius.md}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[500])}; | ||
| background: ${t(colors.gray[200], colors.darkGray[600])}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| font-size: ${fontSize.xs}; | ||
| cursor: pointer; | ||
| user-select: none; | ||
| transition: | ||
| background 0.15s, | ||
| border-color 0.15s; | ||
| &:hover { | ||
| background: ${t(colors.gray[300], colors.darkGray[500])}; | ||
| border-color: ${t(colors.gray[400], colors.darkGray[400])}; | ||
| } | ||
| &:disabled { | ||
| opacity: 0.5; | ||
| cursor: not-allowed; | ||
| &:hover { | ||
| background: ${t(colors.gray[200], colors.darkGray[600])}; | ||
| border-color: ${t(colors.gray[300], colors.darkGray[500])}; | ||
| } | ||
| } | ||
| `, | ||
| actionDotBlue: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.blue[400]}; | ||
| `, | ||
| actionDotGreen: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.green[400]}; | ||
| `, | ||
| actionDotRed: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.red[400]}; | ||
| `, | ||
| actionDotYellow: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.yellow[400]}; | ||
| `, | ||
| actionDotOrange: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.pink[400]}; | ||
| `, | ||
| actionDotPurple: css` | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 9999px; | ||
| background: ${colors.purple[400]}; | ||
| `, | ||
| infoGrid: css` | ||
| display: grid; | ||
| grid-template-columns: auto 1fr; | ||
| gap: ${size[1]}; | ||
| row-gap: ${size[1]}; | ||
| align-items: center; | ||
| `, | ||
| infoLabel: css` | ||
| color: ${t(colors.gray[600], colors.gray[400])}; | ||
| font-size: ${fontSize.xs}; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| `, | ||
| infoValueMono: css` | ||
| font-family: ${fontFamily.mono}; | ||
| font-size: ${fontSize.xs}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| word-break: break-all; | ||
| `, | ||
| noSelection: css` | ||
| flex: 1; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| color: ${t(colors.gray[500], colors.gray[500])}; | ||
| font-style: italic; | ||
| text-align: center; | ||
| padding: ${size[4]}; | ||
| `, | ||
| sectionContainer: css` | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: ${size[4]}; | ||
| `, | ||
| section: css` | ||
| background: ${t(colors.gray[100], colors.darkGray[800])}; | ||
| border-radius: ${border.radius.lg}; | ||
| box-shadow: ${tokens.shadow.md(t(colors.gray[400] + alpha[80], colors.black + alpha[80]))}; | ||
| padding: ${size[4]}; | ||
| margin-bottom: ${size[4]}; | ||
| border: 1px solid ${t(colors.gray[200], colors.darkGray[700])}; | ||
| min-width: 0; | ||
| max-width: 33%; | ||
| max-height: fit-content; | ||
| `, | ||
| sectionHeader: css` | ||
| font-size: ${fontSize.lg}; | ||
| font-weight: ${font.weight.bold}; | ||
| margin-bottom: ${size[2]}; | ||
| color: ${t(colors.blue[600], colors.blue[400])}; | ||
| letter-spacing: 0.01em; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: ${size[2]}; | ||
| `, | ||
| sectionEmpty: css` | ||
| color: ${t(colors.gray[500], colors.gray[500])}; | ||
| font-size: ${fontSize.sm}; | ||
| font-style: italic; | ||
| margin: ${size[2]} 0; | ||
| `, | ||
| instanceList: css` | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: ${size[2]}; | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| `, | ||
| instanceCard: css` | ||
| background: ${t(colors.gray[200], colors.darkGray[700])}; | ||
| border-radius: ${border.radius.md}; | ||
| padding: ${size[3]}; | ||
| border: 1px solid ${t(colors.gray[300], colors.darkGray[600])}; | ||
| font-size: ${fontSize.sm}; | ||
| color: ${t(colors.gray[900], colors.gray[100])}; | ||
| font-family: ${fontFamily.mono}; | ||
| overflow-x: auto; | ||
| transition: | ||
| box-shadow 0.3s, | ||
| background 0.3s; | ||
| ` | ||
| }; | ||
| }; | ||
| function useStyles() { | ||
| const { theme } = useTheme(); | ||
| const [styles, setStyles] = createSignal(stylesFactory(theme())); | ||
| createEffect(() => { | ||
| setStyles(stylesFactory(theme())); | ||
| }); | ||
| return styles; | ||
| } | ||
| //#endregion | ||
| //#region src/components/util-groups.ts | ||
| const UTIL_GROUPS = [ | ||
| { | ||
| key: "debouncers", | ||
| label: "Debouncers", | ||
| displayName: "Debouncer" | ||
| }, | ||
| { | ||
| key: "throttlers", | ||
| label: "Throttlers", | ||
| displayName: "Throttler" | ||
| }, | ||
| { | ||
| key: "batchers", | ||
| label: "Batchers", | ||
| displayName: "Batcher" | ||
| }, | ||
| { | ||
| key: "queuers", | ||
| label: "Queuers", | ||
| displayName: "Queuer" | ||
| }, | ||
| { | ||
| key: "rateLimiters", | ||
| label: "Rate Limiters", | ||
| displayName: "Rate Limiter" | ||
| }, | ||
| { | ||
| key: "asyncDebouncers", | ||
| label: "Async Debouncers", | ||
| displayName: "Async Debouncer" | ||
| }, | ||
| { | ||
| key: "asyncThrottlers", | ||
| label: "Async Throttlers", | ||
| displayName: "Async Throttler" | ||
| }, | ||
| { | ||
| key: "asyncBatchers", | ||
| label: "Async Batchers", | ||
| displayName: "Async Batcher" | ||
| }, | ||
| { | ||
| key: "asyncQueuers", | ||
| label: "Async Queuers", | ||
| displayName: "Async Queuer" | ||
| }, | ||
| { | ||
| key: "asyncRateLimiters", | ||
| label: "Async Rate Limiters", | ||
| displayName: "Async Rate Limiter" | ||
| } | ||
| ]; | ||
| //#endregion | ||
| //#region src/components/UtilList.tsx | ||
| var _tmpl$$4 = /* @__PURE__ */ template(`<div>`), _tmpl$2$2 = /* @__PURE__ */ template(`<div><div>`), _tmpl$3$2 = /* @__PURE__ */ template(`<div><div></div><div>`); | ||
| function UtilList(props) { | ||
| const styles = useStyles(); | ||
| return (() => { | ||
| var _el$ = _tmpl$$4(); | ||
| insert(_el$, createComponent(For, { | ||
| each: UTIL_GROUPS, | ||
| children: (group) => memo(() => memo(() => props.utilState()[group.key].length > 0)() && (() => { | ||
| var _el$2 = _tmpl$2$2(), _el$3 = _el$2.firstChild; | ||
| insert(_el$3, () => group.label); | ||
| insert(_el$2, createComponent(For, { | ||
| get each() { | ||
| return props.utilState()[group.key]; | ||
| }, | ||
| children: (instance) => { | ||
| const status = (() => { | ||
| try { | ||
| const statusAccessor = useStore(instance.store, (s) => s.status); | ||
| return () => statusAccessor() ?? "unknown"; | ||
| } catch { | ||
| return () => "unknown"; | ||
| } | ||
| })(); | ||
| return (() => { | ||
| var _el$4 = _tmpl$3$2(), _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling; | ||
| _el$4.$$click = () => props.setSelectedKey(instance.key ?? null); | ||
| insert(_el$5, () => instance.key); | ||
| insert(_el$6, status); | ||
| effect((_p$) => { | ||
| var _v$3 = clsx(styles().utilRow, props.selectedKey() === instance.key && styles().utilRowSelected), _v$4 = styles().utilKey, _v$5 = styles().utilStatus; | ||
| _v$3 !== _p$.e && className(_el$4, _p$.e = _v$3); | ||
| _v$4 !== _p$.t && className(_el$5, _p$.t = _v$4); | ||
| _v$5 !== _p$.a && className(_el$6, _p$.a = _v$5); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0 | ||
| }); | ||
| return _el$4; | ||
| })(); | ||
| } | ||
| }), null); | ||
| effect((_p$) => { | ||
| var _v$ = styles().utilGroup, _v$2 = styles().utilGroupHeader; | ||
| _v$ !== _p$.e && className(_el$2, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$3, _p$.t = _v$2); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0 | ||
| }); | ||
| return _el$2; | ||
| })()) | ||
| })); | ||
| effect(() => className(_el$, styles().utilList)); | ||
| return _el$; | ||
| })(); | ||
| } | ||
| delegateEvents(["click"]); | ||
| //#endregion | ||
| //#region src/components/ActionButtons.tsx | ||
| var _tmpl$$3 = /* @__PURE__ */ template(`<div>No actions available for this util`), _tmpl$2$1 = /* @__PURE__ */ template(`<div>`), _tmpl$3$1 = /* @__PURE__ */ template(`<button><span>`), _tmpl$4 = /* @__PURE__ */ template(`<button><span></span>Flush`), _tmpl$5 = /* @__PURE__ */ template(`<button><span></span>Cancel`), _tmpl$6 = /* @__PURE__ */ template(`<button><span></span>Reset`), _tmpl$7 = /* @__PURE__ */ template(`<button><span></span>Clear`); | ||
| function ActionButtons(props) { | ||
| const styles = useStyles(); | ||
| const utilInstance = props.instance; | ||
| const utilState = utilInstance?.store?.state; | ||
| const hasPending = utilState && "isPending" in utilState; | ||
| const hasEmpty = utilState && "isEmpty" in utilState; | ||
| const isPending = hasPending ? !!utilState.isPending : false; | ||
| const isEmpty = hasEmpty ? utilState.isEmpty : void 0; | ||
| const hasFlush = typeof utilInstance.flush === "function"; | ||
| const hasCancel = typeof utilInstance.cancel === "function"; | ||
| const hasReset = typeof utilInstance.reset === "function"; | ||
| const hasClear = typeof utilInstance.clear === "function"; | ||
| const hasStart = typeof utilInstance.start === "function"; | ||
| const hasStop = typeof utilInstance.stop === "function"; | ||
| const hasStartStop = hasStart && hasStop; | ||
| const isRunning = utilState?.isRunning ?? true; | ||
| if (!hasPending && !hasFlush && !hasCancel && !hasReset && !hasClear) return (() => { | ||
| var _el$ = _tmpl$$3(); | ||
| effect(() => className(_el$, styles().sectionEmpty)); | ||
| return _el$; | ||
| })(); | ||
| const emitName = `d-${props.utilName}`; | ||
| return (() => { | ||
| var _el$2 = _tmpl$2$1(); | ||
| insert(_el$2, hasPending && (() => { | ||
| var _el$3 = _tmpl$3$1(), _el$4 = _el$3.firstChild; | ||
| _el$3.$$mousedown = () => { | ||
| const next = !isPending; | ||
| utilInstance.store.setState((prev) => ({ | ||
| ...prev, | ||
| isPending: next | ||
| })); | ||
| pacerEventClient$1.emit(emitName, utilInstance); | ||
| }; | ||
| insert(_el$3, isPending ? "Restore Pending" : "Trigger Pending", null); | ||
| effect((_p$) => { | ||
| var _v$ = styles().actionButton, _v$2 = styles().actionDotBlue; | ||
| _v$ !== _p$.e && className(_el$3, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$4, _p$.t = _v$2); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0 | ||
| }); | ||
| return _el$3; | ||
| })(), null); | ||
| insert(_el$2, hasFlush && (() => { | ||
| var _el$5 = _tmpl$4(), _el$6 = _el$5.firstChild; | ||
| _el$5.$$mousedown = () => { | ||
| utilInstance.flush(); | ||
| pacerEventClient$1.emit(emitName, utilInstance); | ||
| }; | ||
| effect((_p$) => { | ||
| var _v$3 = styles().actionButton, _v$4 = props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, _v$5 = styles().actionDotGreen; | ||
| _v$3 !== _p$.e && className(_el$5, _p$.e = _v$3); | ||
| _v$4 !== _p$.t && (_el$5.disabled = _p$.t = _v$4); | ||
| _v$5 !== _p$.a && className(_el$6, _p$.a = _v$5); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0 | ||
| }); | ||
| return _el$5; | ||
| })(), null); | ||
| insert(_el$2, hasCancel && (() => { | ||
| var _el$7 = _tmpl$5(), _el$8 = _el$7.firstChild; | ||
| _el$7.$$mousedown = () => { | ||
| utilInstance.cancel(); | ||
| pacerEventClient$1.emit(emitName, utilInstance); | ||
| }; | ||
| effect((_p$) => { | ||
| var _v$6 = styles().actionButton, _v$7 = props.utilName.toLowerCase().includes("debouncer") && !isPending || props.utilName.toLowerCase().includes("throttler") && !isPending || props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, _v$8 = styles().actionDotRed; | ||
| _v$6 !== _p$.e && className(_el$7, _p$.e = _v$6); | ||
| _v$7 !== _p$.t && (_el$7.disabled = _p$.t = _v$7); | ||
| _v$8 !== _p$.a && className(_el$8, _p$.a = _v$8); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0 | ||
| }); | ||
| return _el$7; | ||
| })(), null); | ||
| insert(_el$2, hasReset && (() => { | ||
| var _el$9 = _tmpl$6(), _el$0 = _el$9.firstChild; | ||
| _el$9.$$mousedown = () => { | ||
| utilInstance.reset(); | ||
| pacerEventClient$1.emit(emitName, utilInstance); | ||
| }; | ||
| effect((_p$) => { | ||
| var _v$9 = styles().actionButton, _v$0 = styles().actionDotYellow; | ||
| _v$9 !== _p$.e && className(_el$9, _p$.e = _v$9); | ||
| _v$0 !== _p$.t && className(_el$0, _p$.t = _v$0); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0 | ||
| }); | ||
| return _el$9; | ||
| })(), null); | ||
| insert(_el$2, hasClear && (() => { | ||
| var _el$1 = _tmpl$7(), _el$10 = _el$1.firstChild; | ||
| _el$1.$$mousedown = () => { | ||
| utilInstance.clear(); | ||
| pacerEventClient$1.emit(emitName, utilInstance); | ||
| }; | ||
| effect((_p$) => { | ||
| var _v$1 = styles().actionButton, _v$10 = props.utilName.toLowerCase().includes("batcher") && isEmpty || props.utilName.toLowerCase().includes("queuer") && isEmpty, _v$11 = styles().actionDotOrange; | ||
| _v$1 !== _p$.e && className(_el$1, _p$.e = _v$1); | ||
| _v$10 !== _p$.t && (_el$1.disabled = _p$.t = _v$10); | ||
| _v$11 !== _p$.a && className(_el$10, _p$.a = _v$11); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0 | ||
| }); | ||
| return _el$1; | ||
| })(), null); | ||
| insert(_el$2, hasStartStop && (() => { | ||
| var _el$11 = _tmpl$3$1(), _el$12 = _el$11.firstChild; | ||
| _el$11.$$mousedown = () => { | ||
| if (isRunning) utilInstance.stop(); | ||
| else utilInstance.start(); | ||
| pacerEventClient$1.emit(emitName, utilInstance); | ||
| }; | ||
| insert(_el$11, isRunning ? "Stop" : "Start", null); | ||
| effect((_p$) => { | ||
| var _v$12 = styles().actionButton, _v$13 = styles().actionDotPurple; | ||
| _v$12 !== _p$.e && className(_el$11, _p$.e = _v$12); | ||
| _v$13 !== _p$.t && className(_el$12, _p$.t = _v$13); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0 | ||
| }); | ||
| return _el$11; | ||
| })(), null); | ||
| effect(() => className(_el$2, styles().actionsRow)); | ||
| return _el$2; | ||
| })(); | ||
| } | ||
| delegateEvents(["mousedown"]); | ||
| //#endregion | ||
| //#region src/components/StateHeader.tsx | ||
| var _tmpl$$2 = /* @__PURE__ */ template(`<div><div></div><div style=display:flex;align-items:center;gap:16px><div><div>Key</div><div></div><div>Last Updated</div><div> (<!>)</div></div><div style=margin-left:auto;font-weight:bold>% reduction`); | ||
| dayjs.extend(relativeTime); | ||
| function StateHeader(props) { | ||
| const styles = useStyles(); | ||
| const [now, setNow] = createSignal(Date.now()); | ||
| onMount(() => { | ||
| const interval = setInterval(() => { | ||
| setNow(Date.now()); | ||
| }, 1e3); | ||
| onCleanup(() => { | ||
| clearInterval(interval); | ||
| }); | ||
| }); | ||
| const entry = props.selectedInstance(); | ||
| if (!entry) return null; | ||
| const key = entry.instance.key; | ||
| const updatedAt = props.utilState().lastUpdatedByKey[key] ?? Date.now(); | ||
| const getRelativeTime = () => { | ||
| const diffMs = now() - updatedAt; | ||
| const diffSeconds = Math.floor(diffMs / 1e3); | ||
| if (diffSeconds < 60) return `${diffSeconds} second${diffSeconds !== 1 ? "s" : ""} ago`; | ||
| return dayjs(updatedAt).fromNow(); | ||
| }; | ||
| const getReductionPercentage = () => { | ||
| const state = entry.instance.store?.state || entry.instance.state; | ||
| if (!state) return 0; | ||
| const completedExecutions = entry.type.toLowerCase().includes("async") ? state.settleCount || 0 : state.executionCount || 0; | ||
| if (entry.type.toLowerCase().includes("batcher")) { | ||
| const totalItemsProcessed = state.totalItemsProcessed || 0; | ||
| if (totalItemsProcessed === 0) return 0; | ||
| return Math.round((totalItemsProcessed - completedExecutions) / totalItemsProcessed * 100); | ||
| } | ||
| let requestCount = 0; | ||
| if (state.maybeExecuteCount !== void 0) requestCount = state.maybeExecuteCount; | ||
| else if (state.addItemCount !== void 0) requestCount = state.addItemCount; | ||
| else return 0; | ||
| if (requestCount === 0) return 0; | ||
| const reduction = requestCount - completedExecutions; | ||
| return Math.round(reduction / requestCount * 100); | ||
| }; | ||
| const reductionPercentage = getReductionPercentage(); | ||
| return (() => { | ||
| var _el$ = _tmpl$$2(), _el$2 = _el$.firstChild, _el$4 = _el$2.nextSibling.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.firstChild, _el$1 = _el$9.nextSibling; | ||
| _el$1.nextSibling; | ||
| var _el$10 = _el$4.nextSibling, _el$11 = _el$10.firstChild; | ||
| insert(_el$2, () => entry.type); | ||
| insert(_el$6, key); | ||
| insert(_el$8, () => new Date(updatedAt).toLocaleTimeString(), _el$9); | ||
| insert(_el$8, getRelativeTime, _el$1); | ||
| insert(_el$10, reductionPercentage, _el$11); | ||
| effect((_p$) => { | ||
| var _v$ = styles().stateHeader, _v$2 = styles().stateTitle, _v$3 = styles().infoGrid, _v$4 = styles().infoLabel, _v$5 = styles().infoValueMono, _v$6 = styles().infoLabel, _v$7 = styles().infoValueMono, _v$8 = styles().infoValueMono; | ||
| _v$ !== _p$.e && className(_el$, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$2, _p$.t = _v$2); | ||
| _v$3 !== _p$.a && className(_el$4, _p$.a = _v$3); | ||
| _v$4 !== _p$.o && className(_el$5, _p$.o = _v$4); | ||
| _v$5 !== _p$.i && className(_el$6, _p$.i = _v$5); | ||
| _v$6 !== _p$.n && className(_el$7, _p$.n = _v$6); | ||
| _v$7 !== _p$.s && className(_el$8, _p$.s = _v$7); | ||
| _v$8 !== _p$.h && className(_el$10, _p$.h = _v$8); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0, | ||
| o: void 0, | ||
| i: void 0, | ||
| n: void 0, | ||
| s: void 0, | ||
| h: void 0 | ||
| }); | ||
| return _el$; | ||
| })(); | ||
| } | ||
| //#endregion | ||
| //#region src/components/DetailsPanel.tsx | ||
| var _tmpl$$1 = /* @__PURE__ */ template(`<div>`), _tmpl$2 = /* @__PURE__ */ template(`<div>Select a util from the left panel to view its state`), _tmpl$3 = /* @__PURE__ */ template(`<div><div><div>Actions</div></div><div><div>State</div><div></div></div><div><div>Options</div><div>`); | ||
| function DetailsPanel(props) { | ||
| const styles = useStyles(); | ||
| return (() => { | ||
| var _el$ = _tmpl$$1(); | ||
| insert(_el$, () => { | ||
| const entry = props.selectedInstance(); | ||
| if (!entry) return (() => { | ||
| var _el$2 = _tmpl$2(); | ||
| effect(() => className(_el$2, styles().noSelection)); | ||
| return _el$2; | ||
| })(); | ||
| return [createComponent(StateHeader, { | ||
| get selectedInstance() { | ||
| return props.selectedInstance; | ||
| }, | ||
| get utilState() { | ||
| return props.utilState; | ||
| } | ||
| }), (() => { | ||
| var _el$3 = _tmpl$3(), _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$4.nextSibling, _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling, _el$9 = _el$6.nextSibling, _el$0 = _el$9.firstChild, _el$1 = _el$0.nextSibling; | ||
| insert(_el$4, createComponent(ActionButtons, { | ||
| get instance() { | ||
| return entry.instance; | ||
| }, | ||
| get utilName() { | ||
| return entry.type; | ||
| } | ||
| }), null); | ||
| insert(_el$8, createComponent(JsonTree, { get value() { | ||
| return entry.instance.store?.state; | ||
| } })); | ||
| insert(_el$1, createComponent(JsonTree, { get value() { | ||
| return entry.instance.options; | ||
| } })); | ||
| effect((_p$) => { | ||
| var _v$ = styles().detailsGrid, _v$2 = styles().detailSection, _v$3 = styles().detailSectionHeader, _v$4 = styles().detailSection, _v$5 = styles().detailSectionHeader, _v$6 = styles().stateContent, _v$7 = styles().detailSection, _v$8 = styles().detailSectionHeader, _v$9 = styles().stateContent; | ||
| _v$ !== _p$.e && className(_el$3, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$4, _p$.t = _v$2); | ||
| _v$3 !== _p$.a && className(_el$5, _p$.a = _v$3); | ||
| _v$4 !== _p$.o && className(_el$6, _p$.o = _v$4); | ||
| _v$5 !== _p$.i && className(_el$7, _p$.i = _v$5); | ||
| _v$6 !== _p$.n && className(_el$8, _p$.n = _v$6); | ||
| _v$7 !== _p$.s && className(_el$9, _p$.s = _v$7); | ||
| _v$8 !== _p$.h && className(_el$0, _p$.h = _v$8); | ||
| _v$9 !== _p$.r && className(_el$1, _p$.r = _v$9); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0, | ||
| o: void 0, | ||
| i: void 0, | ||
| n: void 0, | ||
| s: void 0, | ||
| h: void 0, | ||
| r: void 0 | ||
| }); | ||
| return _el$3; | ||
| })()]; | ||
| }); | ||
| effect(() => className(_el$, styles().stateDetails)); | ||
| return _el$; | ||
| })(); | ||
| } | ||
| //#endregion | ||
| //#region src/components/Shell.tsx | ||
| var _tmpl$ = /* @__PURE__ */ template(`<div><div style=min-width:150px;max-width:800px></div><div></div><div style=flex:1><div>Details`); | ||
| function Shell() { | ||
| const styles = useStyles(); | ||
| const state = usePacerDevtoolsState(); | ||
| const utilState = () => state; | ||
| const [selectedKey, setSelectedKey] = createSignal(null); | ||
| const [leftPanelWidth, setLeftPanelWidth] = createSignal(300); | ||
| const [isDragging, setIsDragging] = createSignal(false); | ||
| const selectedInstance = createMemo(() => { | ||
| const key = selectedKey(); | ||
| if (!key) return null; | ||
| for (const group of UTIL_GROUPS) { | ||
| const instance = utilState()[group.key].find((inst) => inst.key === key); | ||
| if (instance) return { | ||
| instance, | ||
| type: group.displayName | ||
| }; | ||
| } | ||
| return null; | ||
| }); | ||
| let dragStartX = 0; | ||
| let dragStartWidth = 0; | ||
| const handleMouseDown = (e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setIsDragging(true); | ||
| document.body.style.cursor = "col-resize"; | ||
| document.body.style.userSelect = "none"; | ||
| dragStartX = e.clientX; | ||
| dragStartWidth = leftPanelWidth(); | ||
| }; | ||
| const handleMouseMove = (e) => { | ||
| if (!isDragging()) return; | ||
| e.preventDefault(); | ||
| const deltaX = e.clientX - dragStartX; | ||
| setLeftPanelWidth(Math.max(150, Math.min(800, dragStartWidth + deltaX))); | ||
| }; | ||
| const handleMouseUp = () => { | ||
| setIsDragging(false); | ||
| document.body.style.cursor = ""; | ||
| document.body.style.userSelect = ""; | ||
| }; | ||
| onMount(() => { | ||
| document.addEventListener("mousemove", handleMouseMove); | ||
| document.addEventListener("mouseup", handleMouseUp); | ||
| }); | ||
| onCleanup(() => { | ||
| document.removeEventListener("mousemove", handleMouseMove); | ||
| document.removeEventListener("mouseup", handleMouseUp); | ||
| }); | ||
| return createComponent(MainPanel, { get children() { | ||
| return [createComponent(Header, { get children() { | ||
| return createComponent(HeaderLogo, { | ||
| flavor: { | ||
| light: "#84cc16", | ||
| dark: "#84cc16" | ||
| }, | ||
| children: "TanStack Pacer" | ||
| }); | ||
| } }), (() => { | ||
| var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.nextSibling, _el$5 = _el$4.firstChild; | ||
| insert(_el$2, createComponent(UtilList, { | ||
| selectedKey, | ||
| setSelectedKey, | ||
| utilState | ||
| })); | ||
| _el$3.$$mousedown = handleMouseDown; | ||
| insert(_el$4, createComponent(DetailsPanel, { | ||
| selectedInstance, | ||
| utilState | ||
| }), null); | ||
| effect((_p$) => { | ||
| var _v$ = styles().mainContainer, _v$2 = styles().leftPanel, _v$3 = `${leftPanelWidth()}px`, _v$4 = `${styles().dragHandle} ${isDragging() ? "dragging" : ""}`, _v$5 = styles().rightPanel, _v$6 = styles().panelHeader; | ||
| _v$ !== _p$.e && className(_el$, _p$.e = _v$); | ||
| _v$2 !== _p$.t && className(_el$2, _p$.t = _v$2); | ||
| _v$3 !== _p$.a && setStyleProperty(_el$2, "width", _p$.a = _v$3); | ||
| _v$4 !== _p$.o && className(_el$3, _p$.o = _v$4); | ||
| _v$5 !== _p$.i && className(_el$4, _p$.i = _v$5); | ||
| _v$6 !== _p$.n && className(_el$5, _p$.n = _v$6); | ||
| return _p$; | ||
| }, { | ||
| e: void 0, | ||
| t: void 0, | ||
| a: void 0, | ||
| o: void 0, | ||
| i: void 0, | ||
| n: void 0 | ||
| }); | ||
| return _el$; | ||
| })()]; | ||
| } }); | ||
| } | ||
| delegateEvents(["mousedown"]); | ||
| //#endregion | ||
| //#region src/components/index.tsx | ||
| function PacerDevtools(props) { | ||
| return createComponent(ThemeContextProvider, { | ||
| get theme() { | ||
| return props.theme; | ||
| }, | ||
| get children() { | ||
| return createComponent(PacerContextProvider, { get children() { | ||
| return createComponent(Shell, {}); | ||
| } }); | ||
| } | ||
| }); | ||
| } | ||
| //#endregion | ||
| export { PacerDevtools as default }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
143958
9.3%26
8.33%4260
9.29%+ Added
+ Added
- Removed
- Removed
Updated
Updated
Updated