@geenius/tools 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +62 -3
- package/packages/convex/shared/README.md +1 -1
- package/packages/devtools/dist/index.d.ts +204 -0
- package/packages/devtools/dist/index.js +186 -0
- package/packages/devtools/dist/index.js.map +1 -0
- package/packages/devtools/react/README.md +1 -1
- package/packages/devtools/solidjs/README.md +1 -1
- package/packages/devtools/solidjs/dist/index.js +1830 -0
- package/packages/devtools/solidjs/dist/index.js.map +1 -0
- package/packages/env/dist/index.d.ts +151 -0
- package/packages/env/dist/index.js +93 -0
- package/packages/env/dist/index.js.map +1 -0
- package/packages/errors/dist/index.d.ts +177 -0
- package/packages/errors/dist/index.js +187 -0
- package/packages/errors/dist/index.js.map +1 -0
- package/packages/errors/react/README.md +1 -1
- package/packages/errors/solidjs/README.md +1 -1
- package/packages/logger/dist/index.d.ts +171 -0
- package/packages/logger/dist/index.js +216 -0
- package/packages/logger/dist/index.js.map +1 -0
- package/packages/logger/react/README.md +1 -1
- package/packages/logger/solidjs/README.md +1 -1
- package/packages/perf/dist/index.d.ts +168 -0
- package/packages/perf/dist/index.js +265 -0
- package/packages/perf/dist/index.js.map +1 -0
- package/packages/perf/react/README.md +1 -1
- package/packages/perf/solidjs/README.md +1 -1
- package/packages/shared/dist/index.d.ts +253 -0
- package/packages/shared/dist/index.js +278 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/.changeset/config.json +0 -11
- package/.env.example +0 -2
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -23
- package/.github/workflows/release.yml +0 -29
- package/.node-version +0 -1
- package/.nvmrc +0 -1
- package/.prettierrc +0 -7
- package/.project/ACCOUNT.yaml +0 -4
- package/.project/IDEAS.yaml +0 -7
- package/.project/PROJECT.yaml +0 -11
- package/.project/ROADMAP.yaml +0 -15
- package/CODE_OF_CONDUCT.md +0 -26
- package/CONTRIBUTING.md +0 -69
- package/SECURITY.md +0 -18
- package/SUPPORT.md +0 -14
- package/packages/convex/shared/package.json +0 -42
- package/packages/convex/shared/src/audit/index.ts +0 -5
- package/packages/convex/shared/src/audit/presets.ts +0 -165
- package/packages/convex/shared/src/audit/schema.ts +0 -85
- package/packages/convex/shared/src/audit/write.ts +0 -102
- package/packages/convex/shared/src/extract.ts +0 -75
- package/packages/convex/shared/src/index.ts +0 -41
- package/packages/convex/shared/src/messages.ts +0 -45
- package/packages/convex/shared/src/security.ts +0 -112
- package/packages/convex/shared/src/throw.ts +0 -184
- package/packages/convex/shared/src/types.ts +0 -57
- package/packages/convex/shared/src/utils.ts +0 -58
- package/packages/convex/shared/tsconfig.json +0 -28
- package/packages/convex/shared/tsup.config.ts +0 -12
- package/packages/devtools/package.json +0 -27
- package/packages/devtools/react/package.json +0 -53
- package/packages/devtools/react/src/components/DesignPreview.tsx +0 -59
- package/packages/devtools/react/src/components/DesignSwitcherDropdown.tsx +0 -99
- package/packages/devtools/react/src/components/DevSidebar.tsx +0 -247
- package/packages/devtools/react/src/components/DevToolbar.tsx +0 -242
- package/packages/devtools/react/src/components/GitHubIssueDialog.tsx +0 -402
- package/packages/devtools/react/src/components/InspectorOverlay.tsx +0 -312
- package/packages/devtools/react/src/components/PageLoadWaterfall.tsx +0 -144
- package/packages/devtools/react/src/components/PerformancePanel.tsx +0 -330
- package/packages/devtools/react/src/context/DevModeContext.tsx +0 -226
- package/packages/devtools/react/src/context/PerformanceContext.tsx +0 -143
- package/packages/devtools/react/src/data/designs.ts +0 -13
- package/packages/devtools/react/src/hooks/useGitHubLabels.ts +0 -47
- package/packages/devtools/react/src/hooks/useVirtualList.ts +0 -124
- package/packages/devtools/react/src/index.ts +0 -77
- package/packages/devtools/react/src/panels/ConvexSpy.tsx +0 -130
- package/packages/devtools/react/src/panels/DatabaseSeeder.tsx +0 -116
- package/packages/devtools/react/src/panels/DevModePhase2.tsx +0 -191
- package/packages/devtools/react/src/panels/DevModePhase3.tsx +0 -234
- package/packages/devtools/react/src/panels/FeatureFlagsToggle.tsx +0 -104
- package/packages/devtools/react/src/panels/QuickRouteJump.tsx +0 -152
- package/packages/devtools/react/src/services/github-service.ts +0 -247
- package/packages/devtools/react/tsconfig.json +0 -31
- package/packages/devtools/react/tsup.config.ts +0 -18
- package/packages/devtools/solidjs/package.json +0 -49
- package/packages/devtools/solidjs/src/components/DesignPreview.tsx +0 -51
- package/packages/devtools/solidjs/src/components/DesignSwitcherDropdown.tsx +0 -95
- package/packages/devtools/solidjs/src/components/DevSidebar.tsx +0 -247
- package/packages/devtools/solidjs/src/components/DevToolbar.tsx +0 -242
- package/packages/devtools/solidjs/src/components/GitHubIssueDialog.tsx +0 -400
- package/packages/devtools/solidjs/src/components/InspectorOverlay.tsx +0 -311
- package/packages/devtools/solidjs/src/components/PageLoadWaterfall.tsx +0 -144
- package/packages/devtools/solidjs/src/components/PerformancePanel.tsx +0 -330
- package/packages/devtools/solidjs/src/context/DevModeContext.tsx +0 -216
- package/packages/devtools/solidjs/src/context/PerformanceContext.tsx +0 -135
- package/packages/devtools/solidjs/src/data/designs.ts +0 -13
- package/packages/devtools/solidjs/src/hooks/createGitHubLabels.ts +0 -47
- package/packages/devtools/solidjs/src/index.ts +0 -64
- package/packages/devtools/solidjs/src/services/github-service.ts +0 -247
- package/packages/devtools/solidjs/tsconfig.json +0 -21
- package/packages/devtools/src/index.ts +0 -377
- package/packages/devtools/tsup.config.ts +0 -12
- package/packages/env/package.json +0 -30
- package/packages/env/src/index.ts +0 -264
- package/packages/env/tsup.config.ts +0 -12
- package/packages/errors/package.json +0 -27
- package/packages/errors/react/package.json +0 -72
- package/packages/errors/react/src/analytics.ts +0 -16
- package/packages/errors/react/src/components/ErrorBoundary.tsx +0 -248
- package/packages/errors/react/src/components/ErrorDisplay.tsx +0 -328
- package/packages/errors/react/src/components/ValidationErrors.tsx +0 -102
- package/packages/errors/react/src/config.ts +0 -199
- package/packages/errors/react/src/constants.ts +0 -74
- package/packages/errors/react/src/hooks/useErrorBoundary.ts +0 -92
- package/packages/errors/react/src/hooks/useErrorHandler.ts +0 -87
- package/packages/errors/react/src/index.ts +0 -96
- package/packages/errors/react/src/types.ts +0 -102
- package/packages/errors/react/src/utils/errorMessages.ts +0 -35
- package/packages/errors/react/src/utils/errorPolicy.ts +0 -139
- package/packages/errors/react/src/utils/extractAppError.ts +0 -174
- package/packages/errors/react/src/utils/formatError.ts +0 -112
- package/packages/errors/react/tsconfig.json +0 -25
- package/packages/errors/react/tsup.config.ts +0 -24
- package/packages/errors/solidjs/package.json +0 -46
- package/packages/errors/solidjs/src/components/ErrorDisplay.tsx +0 -179
- package/packages/errors/solidjs/src/config.ts +0 -98
- package/packages/errors/solidjs/src/hooks/createErrorHandler.ts +0 -107
- package/packages/errors/solidjs/src/index.ts +0 -61
- package/packages/errors/solidjs/src/types.ts +0 -34
- package/packages/errors/solidjs/src/utils/errorPolicy.ts +0 -56
- package/packages/errors/solidjs/src/utils/extractAppError.ts +0 -94
- package/packages/errors/solidjs/src/utils/formatError.ts +0 -33
- package/packages/errors/solidjs/tsconfig.json +0 -26
- package/packages/errors/solidjs/tsup.config.ts +0 -21
- package/packages/errors/src/index.ts +0 -320
- package/packages/errors/tsup.config.ts +0 -12
- package/packages/logger/package.json +0 -27
- package/packages/logger/react/package.json +0 -46
- package/packages/logger/react/src/index.ts +0 -4
- package/packages/logger/react/src/useMetrics.ts +0 -42
- package/packages/logger/react/src/usePerformanceLog.ts +0 -61
- package/packages/logger/react/tsconfig.json +0 -31
- package/packages/logger/react/tsup.config.ts +0 -12
- package/packages/logger/solidjs/package.json +0 -45
- package/packages/logger/solidjs/src/createMetrics.ts +0 -37
- package/packages/logger/solidjs/src/createPerformanceLog.ts +0 -58
- package/packages/logger/solidjs/src/index.ts +0 -4
- package/packages/logger/solidjs/tsconfig.json +0 -32
- package/packages/logger/solidjs/tsup.config.ts +0 -12
- package/packages/logger/src/index.ts +0 -363
- package/packages/logger/tsup.config.ts +0 -12
- package/packages/perf/package.json +0 -27
- package/packages/perf/react/package.json +0 -59
- package/packages/perf/react/src/components/PerformanceDashboard.tsx +0 -257
- package/packages/perf/react/src/hooks/useMonitoredQuery.ts +0 -89
- package/packages/perf/react/src/hooks/usePerformanceMetrics.ts +0 -78
- package/packages/perf/react/src/index.ts +0 -33
- package/packages/perf/react/src/services/PerformanceMonitor.ts +0 -313
- package/packages/perf/react/src/types.ts +0 -77
- package/packages/perf/react/tsconfig.json +0 -25
- package/packages/perf/react/tsup.config.ts +0 -19
- package/packages/perf/solidjs/package.json +0 -41
- package/packages/perf/solidjs/src/components/PerformanceDashboard.tsx +0 -207
- package/packages/perf/solidjs/src/hooks/createPerformanceMetrics.ts +0 -73
- package/packages/perf/solidjs/src/index.ts +0 -31
- package/packages/perf/solidjs/src/services/PerformanceMonitor.ts +0 -134
- package/packages/perf/solidjs/src/types.ts +0 -78
- package/packages/perf/solidjs/tsconfig.json +0 -26
- package/packages/perf/solidjs/tsup.config.ts +0 -14
- package/packages/perf/src/index.ts +0 -410
- package/packages/perf/tsup.config.ts +0 -12
- package/pnpm-workspace.yaml +0 -2
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// @geenius-tools/devtools-react — src/hooks/useGitHubLabels.ts
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
getRepositoryLabels,
|
|
6
|
-
isGitHubConfigured,
|
|
7
|
-
type GitHubLabel,
|
|
8
|
-
} from '../services/github-service'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Hook to fetch GitHub repository labels.
|
|
12
|
-
* Fetches once on mount if GitHub is configured.
|
|
13
|
-
* No external dependencies (no react-query).
|
|
14
|
-
*/
|
|
15
|
-
export function useGitHubLabels() {
|
|
16
|
-
const [data, setData] = useState<GitHubLabel[] | undefined>(undefined)
|
|
17
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
18
|
-
const [error, setError] = useState<Error | null>(null)
|
|
19
|
-
const isConfigured = isGitHubConfigured()
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (!isConfigured) return
|
|
23
|
-
|
|
24
|
-
let cancelled = false
|
|
25
|
-
setIsLoading(true)
|
|
26
|
-
|
|
27
|
-
getRepositoryLabels()
|
|
28
|
-
.then((labels) => {
|
|
29
|
-
if (!cancelled) {
|
|
30
|
-
setData(labels)
|
|
31
|
-
setIsLoading(false)
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
.catch((err) => {
|
|
35
|
-
if (!cancelled) {
|
|
36
|
-
setError(err)
|
|
37
|
-
setIsLoading(false)
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
return () => {
|
|
42
|
-
cancelled = true
|
|
43
|
-
}
|
|
44
|
-
}, [isConfigured])
|
|
45
|
-
|
|
46
|
-
return { data, isLoading, error, isConfigured }
|
|
47
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { useRef, useCallback, useMemo, type RefObject } from 'react'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TanStack Virtual wrapper — Unified virtual list hook.
|
|
5
|
-
* Provides a simplified API over @tanstack/react-virtual for consistent usage.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```tsx
|
|
9
|
-
* const { virtualItems, totalSize, containerRef } = useVirtualList({
|
|
10
|
-
* count: items.length,
|
|
11
|
-
* estimateSize: () => 48,
|
|
12
|
-
* })
|
|
13
|
-
*
|
|
14
|
-
* return (
|
|
15
|
-
* <div ref={containerRef} style={{ height: 400, overflow: 'auto' }}>
|
|
16
|
-
* <div style={{ height: totalSize }}>
|
|
17
|
-
* {virtualItems.map(item => (
|
|
18
|
-
* <div key={item.key} style={{ height: item.size, transform: `translateY(${item.start}px)` }}>
|
|
19
|
-
* {items[item.index]}
|
|
20
|
-
* </div>
|
|
21
|
-
* ))}
|
|
22
|
-
* </div>
|
|
23
|
-
* </div>
|
|
24
|
-
* )
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
interface VirtualListOptions {
|
|
29
|
-
count: number
|
|
30
|
-
estimateSize: (index: number) => number
|
|
31
|
-
overscan?: number
|
|
32
|
-
paddingStart?: number
|
|
33
|
-
paddingEnd?: number
|
|
34
|
-
horizontal?: boolean
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface VirtualItem {
|
|
38
|
-
key: string
|
|
39
|
-
index: number
|
|
40
|
-
start: number
|
|
41
|
-
size: number
|
|
42
|
-
end: number
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface VirtualListResult {
|
|
46
|
-
virtualItems: VirtualItem[]
|
|
47
|
-
totalSize: number
|
|
48
|
-
containerRef: RefObject<HTMLDivElement | null>
|
|
49
|
-
scrollToIndex: (index: number) => void
|
|
50
|
-
scrollToOffset: (offset: number) => void
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Simplified virtual list hook.
|
|
55
|
-
*
|
|
56
|
-
* On environments where @tanstack/react-virtual is installed,
|
|
57
|
-
* use that directly for full feature set. This provides a
|
|
58
|
-
* fallback implementation for basic virtualization.
|
|
59
|
-
*/
|
|
60
|
-
export function useVirtualList(options: VirtualListOptions): VirtualListResult {
|
|
61
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
62
|
-
|
|
63
|
-
// Calculate sizes
|
|
64
|
-
const sizes = useMemo(() => {
|
|
65
|
-
return Array.from({ length: options.count }, (_, i) => options.estimateSize(i))
|
|
66
|
-
}, [options.count, options.estimateSize])
|
|
67
|
-
|
|
68
|
-
const offsets = useMemo(() => {
|
|
69
|
-
const result: number[] = []
|
|
70
|
-
let offset = options.paddingStart ?? 0
|
|
71
|
-
for (const size of sizes) {
|
|
72
|
-
result.push(offset)
|
|
73
|
-
offset += size
|
|
74
|
-
}
|
|
75
|
-
return result
|
|
76
|
-
}, [sizes, options.paddingStart])
|
|
77
|
-
|
|
78
|
-
const totalSize = useMemo(() => {
|
|
79
|
-
return (offsets[offsets.length - 1] ?? 0) + (sizes[sizes.length - 1] ?? 0) + (options.paddingEnd ?? 0)
|
|
80
|
-
}, [offsets, sizes, options.paddingEnd])
|
|
81
|
-
|
|
82
|
-
// Simple window calculation (fallback — use TanStack Virtual for scroll-aware virtualization)
|
|
83
|
-
const virtualItems = useMemo((): VirtualItem[] => {
|
|
84
|
-
const overscan = options.overscan ?? 5
|
|
85
|
-
// Without scroll tracking, render all items (or first batch)
|
|
86
|
-
// In real usage, this would be scroll-aware via @tanstack/react-virtual
|
|
87
|
-
const startIdx = 0
|
|
88
|
-
const endIdx = Math.min(options.count, 100) // Render first 100 as fallback
|
|
89
|
-
|
|
90
|
-
return Array.from({ length: endIdx - startIdx }, (_, i) => {
|
|
91
|
-
const index = startIdx + i
|
|
92
|
-
return {
|
|
93
|
-
key: `v_${index}`,
|
|
94
|
-
index,
|
|
95
|
-
start: offsets[index] ?? 0,
|
|
96
|
-
size: sizes[index] ?? 0,
|
|
97
|
-
end: (offsets[index] ?? 0) + (sizes[index] ?? 0),
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
}, [options.count, options.overscan, offsets, sizes])
|
|
101
|
-
|
|
102
|
-
const scrollToIndex = useCallback((index: number) => {
|
|
103
|
-
const offset = offsets[index]
|
|
104
|
-
if (offset !== undefined && containerRef.current) {
|
|
105
|
-
if (options.horizontal) {
|
|
106
|
-
containerRef.current.scrollLeft = offset
|
|
107
|
-
} else {
|
|
108
|
-
containerRef.current.scrollTop = offset
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}, [offsets, options.horizontal])
|
|
112
|
-
|
|
113
|
-
const scrollToOffset = useCallback((offset: number) => {
|
|
114
|
-
if (containerRef.current) {
|
|
115
|
-
if (options.horizontal) {
|
|
116
|
-
containerRef.current.scrollLeft = offset
|
|
117
|
-
} else {
|
|
118
|
-
containerRef.current.scrollTop = offset
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}, [options.horizontal])
|
|
122
|
-
|
|
123
|
-
return { virtualItems, totalSize, containerRef, scrollToIndex, scrollToOffset }
|
|
124
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// @geenius-tools/devtools-react — src/index.ts
|
|
2
|
-
|
|
3
|
-
// Components
|
|
4
|
-
export { DevToolbar } from './components/DevToolbar'
|
|
5
|
-
export type { DevToolbarProps } from './components/DevToolbar'
|
|
6
|
-
export { DevSidebar } from './components/DevSidebar'
|
|
7
|
-
export { PerformancePanel } from './components/PerformancePanel'
|
|
8
|
-
export { InspectorOverlay } from './components/InspectorOverlay'
|
|
9
|
-
export { GitHubIssueDialog } from './components/GitHubIssueDialog'
|
|
10
|
-
export type { GitHubIssueDialogProps } from './components/GitHubIssueDialog'
|
|
11
|
-
export { DesignPreview } from './components/DesignPreview'
|
|
12
|
-
export type { DesignPreviewProps } from './components/DesignPreview'
|
|
13
|
-
export { DesignSwitcherDropdown } from './components/DesignSwitcherDropdown'
|
|
14
|
-
export type { DesignSwitcherDropdownProps } from './components/DesignSwitcherDropdown'
|
|
15
|
-
export { PageLoadWaterfall, logWaterfall } from './components/PageLoadWaterfall'
|
|
16
|
-
export type { SsrTiming } from './components/PageLoadWaterfall'
|
|
17
|
-
|
|
18
|
-
// Data
|
|
19
|
-
export { designs } from './data/designs'
|
|
20
|
-
export type { Design } from './data/designs'
|
|
21
|
-
|
|
22
|
-
// Context
|
|
23
|
-
export {
|
|
24
|
-
DevModeProvider,
|
|
25
|
-
useDevMode,
|
|
26
|
-
useDevModeOptional,
|
|
27
|
-
useIsDevToolsVisible,
|
|
28
|
-
useIsDevAllowed,
|
|
29
|
-
} from './context/DevModeContext'
|
|
30
|
-
export type {
|
|
31
|
-
DevModeProviderProps,
|
|
32
|
-
DevModeContextValue,
|
|
33
|
-
ComponentDetails,
|
|
34
|
-
} from './context/DevModeContext'
|
|
35
|
-
|
|
36
|
-
export {
|
|
37
|
-
PerformanceProvider,
|
|
38
|
-
usePerformanceContext,
|
|
39
|
-
usePerformanceContextOptional,
|
|
40
|
-
} from './context/PerformanceContext'
|
|
41
|
-
|
|
42
|
-
// Services
|
|
43
|
-
export {
|
|
44
|
-
configureGitHub,
|
|
45
|
-
isGitHubConfigured,
|
|
46
|
-
getGitHubConfig,
|
|
47
|
-
createGitHubIssue,
|
|
48
|
-
getRepositoryLabels,
|
|
49
|
-
getGitHubIssues,
|
|
50
|
-
generateContextInfo,
|
|
51
|
-
getIssueType,
|
|
52
|
-
formatRelativeTime,
|
|
53
|
-
} from './services/github-service'
|
|
54
|
-
export type {
|
|
55
|
-
GitHubConfig,
|
|
56
|
-
GitHubIssue,
|
|
57
|
-
GitHubLabel,
|
|
58
|
-
GitHubIssueListItem,
|
|
59
|
-
CreateIssueResponse,
|
|
60
|
-
IssueFilter,
|
|
61
|
-
} from './services/github-service'
|
|
62
|
-
|
|
63
|
-
// Hooks
|
|
64
|
-
export { useGitHubLabels } from './hooks/useGitHubLabels'
|
|
65
|
-
export { useVirtualList } from './hooks/useVirtualList'
|
|
66
|
-
|
|
67
|
-
// Dev Mode Phase 1 Panels
|
|
68
|
-
export { ConvexSpyPanel, useConvexSpy } from './panels/ConvexSpy'
|
|
69
|
-
export { DatabaseSeeder } from './panels/DatabaseSeeder'
|
|
70
|
-
export { FeatureFlagsToggle } from './panels/FeatureFlagsToggle'
|
|
71
|
-
export { QuickRouteJump } from './panels/QuickRouteJump'
|
|
72
|
-
|
|
73
|
-
// Dev Mode Phase 2 Panels
|
|
74
|
-
export { UserImpersonation, ThemeGenerator } from './panels/DevModePhase2'
|
|
75
|
-
|
|
76
|
-
// Dev Mode Phase 3 Panels
|
|
77
|
-
export { AccessibilityAuditor, ResponsiveTester } from './panels/DevModePhase3'
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useRef, type ReactNode } from 'react'
|
|
2
|
-
|
|
3
|
-
// ─── Types ────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
interface ConvexEvent {
|
|
6
|
-
id: string
|
|
7
|
-
type: 'query' | 'mutation' | 'action'
|
|
8
|
-
name: string
|
|
9
|
-
args: unknown
|
|
10
|
-
result?: unknown
|
|
11
|
-
error?: string
|
|
12
|
-
duration: number
|
|
13
|
-
timestamp: number
|
|
14
|
-
status: 'pending' | 'success' | 'error'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface ConvexSpyContextValue {
|
|
18
|
-
events: ConvexEvent[]
|
|
19
|
-
isRecording: boolean
|
|
20
|
-
toggleRecording: () => void
|
|
21
|
-
clear: () => void
|
|
22
|
-
filter: string
|
|
23
|
-
setFilter: (f: string) => void
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ─── Hook ─────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Hook to spy on Convex queries and mutations in real-time.
|
|
30
|
-
* Intercepts the Convex client's internal transport for debugging.
|
|
31
|
-
*/
|
|
32
|
-
export function useConvexSpy(maxEvents = 200): ConvexSpyContextValue {
|
|
33
|
-
const [events, setEvents] = useState<ConvexEvent[]>([])
|
|
34
|
-
const [isRecording, setIsRecording] = useState(true)
|
|
35
|
-
const [filter, setFilter] = useState('')
|
|
36
|
-
const nextId = useRef(0)
|
|
37
|
-
|
|
38
|
-
const addEvent = useCallback((event: Omit<ConvexEvent, 'id'>) => {
|
|
39
|
-
if (!isRecording) return
|
|
40
|
-
setEvents(prev => {
|
|
41
|
-
const next = [{ ...event, id: `ev_${nextId.current++}` }, ...prev]
|
|
42
|
-
return next.slice(0, maxEvents)
|
|
43
|
-
})
|
|
44
|
-
}, [isRecording, maxEvents])
|
|
45
|
-
|
|
46
|
-
// Monkey-patch console to capture Convex debug logs
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (!isRecording) return
|
|
49
|
-
const origDebug = console.debug
|
|
50
|
-
console.debug = (...args: unknown[]) => {
|
|
51
|
-
origDebug.apply(console, args)
|
|
52
|
-
const msg = String(args[0] ?? '')
|
|
53
|
-
if (msg.includes('[Convex]') || msg.includes('convex:')) {
|
|
54
|
-
const isQuery = msg.includes('query') || msg.includes('Query')
|
|
55
|
-
const isMutation = msg.includes('mutation') || msg.includes('Mutation')
|
|
56
|
-
const type = isMutation ? 'mutation' : isQuery ? 'query' : 'action'
|
|
57
|
-
addEvent({
|
|
58
|
-
type,
|
|
59
|
-
name: extractFunctionName(msg),
|
|
60
|
-
args: args.length > 1 ? args[1] : undefined,
|
|
61
|
-
duration: 0,
|
|
62
|
-
timestamp: Date.now(),
|
|
63
|
-
status: msg.includes('error') || msg.includes('Error') ? 'error' : 'success',
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return () => { console.debug = origDebug }
|
|
68
|
-
}, [isRecording, addEvent])
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
events: filter ? events.filter(e => e.name.toLowerCase().includes(filter.toLowerCase())) : events,
|
|
72
|
-
isRecording,
|
|
73
|
-
toggleRecording: () => setIsRecording(p => !p),
|
|
74
|
-
clear: () => setEvents([]),
|
|
75
|
-
filter,
|
|
76
|
-
setFilter,
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function extractFunctionName(msg: string): string {
|
|
81
|
-
const match = msg.match(/(?:query|mutation|action)\s*:\s*(\S+)/i) ?? msg.match(/\[(\w+(?:\.\w+)+)\]/)
|
|
82
|
-
return match?.[1] ?? msg.slice(0, 50)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ─── Component ────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* ConvexSpy Panel — Real-time monitoring of Convex queries/mutations.
|
|
89
|
-
*/
|
|
90
|
-
export function ConvexSpyPanel() {
|
|
91
|
-
const spy = useConvexSpy()
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<div style={{ fontFamily: 'monospace', fontSize: 12, color: '#e2e8f0', background: '#0f172a', borderRadius: 8, overflow: 'hidden' }}>
|
|
95
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 12px', background: '#1e293b', borderBottom: '1px solid #334155' }}>
|
|
96
|
-
<span style={{ fontWeight: 700, fontSize: 13 }}>🔍 Convex Spy</span>
|
|
97
|
-
<div style={{ display: 'flex', gap: 6 }}>
|
|
98
|
-
<input
|
|
99
|
-
placeholder="Filter..."
|
|
100
|
-
value={spy.filter}
|
|
101
|
-
onChange={e => spy.setFilter(e.target.value)}
|
|
102
|
-
style={{ padding: '4px 8px', borderRadius: 4, border: '1px solid #334155', background: '#0f172a', color: '#e2e8f0', fontSize: 11, width: 120 }}
|
|
103
|
-
/>
|
|
104
|
-
<button onClick={spy.toggleRecording} style={btnStyle}>{spy.isRecording ? '⏸' : '▶'}</button>
|
|
105
|
-
<button onClick={spy.clear} style={btnStyle}>🗑</button>
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
<div style={{ maxHeight: 300, overflow: 'auto', padding: 4 }}>
|
|
109
|
-
{spy.events.length === 0 ? (
|
|
110
|
-
<div style={{ padding: 16, textAlign: 'center', color: '#64748b' }}>
|
|
111
|
-
{spy.isRecording ? 'Waiting for Convex events...' : 'Recording paused'}
|
|
112
|
-
</div>
|
|
113
|
-
) : spy.events.map(e => (
|
|
114
|
-
<div key={e.id} style={{ display: 'flex', gap: 8, padding: '4px 8px', borderBottom: '1px solid #1e293b', alignItems: 'center' }}>
|
|
115
|
-
<span style={{ color: typeColor(e.type), fontWeight: 700, width: 60, textTransform: 'uppercase', fontSize: 10 }}>{e.type}</span>
|
|
116
|
-
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{e.name}</span>
|
|
117
|
-
<span style={{ color: e.status === 'error' ? '#ef4444' : '#22c55e', fontSize: 10 }}>{e.status}</span>
|
|
118
|
-
<span style={{ color: '#64748b', fontSize: 10, width: 60, textAlign: 'right' }}>{new Date(e.timestamp).toLocaleTimeString()}</span>
|
|
119
|
-
</div>
|
|
120
|
-
))}
|
|
121
|
-
</div>
|
|
122
|
-
<div style={{ padding: '6px 12px', borderTop: '1px solid #334155', color: '#64748b', fontSize: 10 }}>
|
|
123
|
-
{spy.events.length} events
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const btnStyle: React.CSSProperties = { padding: '4px 8px', borderRadius: 4, border: '1px solid #334155', background: '#0f172a', color: '#e2e8f0', cursor: 'pointer', fontSize: 11 }
|
|
130
|
-
const typeColor = (t: string) => t === 'mutation' ? '#f59e0b' : t === 'query' ? '#06b6d4' : '#a78bfa'
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from 'react'
|
|
2
|
-
|
|
3
|
-
// ─── Types ────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
interface SeedConfig {
|
|
6
|
-
name: string
|
|
7
|
-
description: string
|
|
8
|
-
tables: { name: string; count: number }[]
|
|
9
|
-
generate: () => Promise<void>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface DatabaseSeederProps {
|
|
13
|
-
seeds: SeedConfig[]
|
|
14
|
-
onReset?: () => Promise<void>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// ─── Component ────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Database Seeder Panel — One-click data population and reset.
|
|
21
|
-
* Provide seed configurations and the panel handles execution.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```tsx
|
|
25
|
-
* <DatabaseSeeder seeds={[
|
|
26
|
-
* { name: 'Demo Data', description: '20 tasks, 5 users', tables: [...], generate: async () => { ... } }
|
|
27
|
-
* ]} />
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export function DatabaseSeeder({ seeds, onReset }: DatabaseSeederProps) {
|
|
31
|
-
const [isSeeding, setIsSeeding] = useState(false)
|
|
32
|
-
const [activeSeed, setActiveSeed] = useState<string | null>(null)
|
|
33
|
-
const [log, setLog] = useState<string[]>([])
|
|
34
|
-
|
|
35
|
-
const runSeed = useCallback(async (seed: SeedConfig) => {
|
|
36
|
-
setIsSeeding(true)
|
|
37
|
-
setActiveSeed(seed.name)
|
|
38
|
-
setLog(prev => [...prev, `🌱 Seeding: ${seed.name}...`])
|
|
39
|
-
try {
|
|
40
|
-
await seed.generate()
|
|
41
|
-
setLog(prev => [...prev, `✅ ${seed.name} complete — ${seed.tables.map(t => `${t.count} ${t.name}`).join(', ')}`])
|
|
42
|
-
} catch (err) {
|
|
43
|
-
setLog(prev => [...prev, `❌ Failed: ${err instanceof Error ? err.message : String(err)}`])
|
|
44
|
-
} finally {
|
|
45
|
-
setIsSeeding(false)
|
|
46
|
-
setActiveSeed(null)
|
|
47
|
-
}
|
|
48
|
-
}, [])
|
|
49
|
-
|
|
50
|
-
const handleReset = useCallback(async () => {
|
|
51
|
-
if (!onReset || !confirm('This will delete all data. Are you sure?')) return
|
|
52
|
-
setIsSeeding(true)
|
|
53
|
-
setLog(prev => [...prev, '🗑 Resetting database...'])
|
|
54
|
-
try {
|
|
55
|
-
await onReset()
|
|
56
|
-
setLog(prev => [...prev, '✅ Database reset complete'])
|
|
57
|
-
} catch (err) {
|
|
58
|
-
setLog(prev => [...prev, `❌ Reset failed: ${err instanceof Error ? err.message : String(err)}`])
|
|
59
|
-
} finally {
|
|
60
|
-
setIsSeeding(false)
|
|
61
|
-
}
|
|
62
|
-
}, [onReset])
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<div style={{ fontFamily: 'system-ui', fontSize: 13, color: '#e2e8f0', background: '#0f172a', borderRadius: 8, overflow: 'hidden' }}>
|
|
66
|
-
<div style={{ padding: '8px 12px', background: '#1e293b', borderBottom: '1px solid #334155', fontWeight: 700, fontSize: 13 }}>
|
|
67
|
-
🌱 Database Seeder
|
|
68
|
-
</div>
|
|
69
|
-
<div style={{ padding: 12 }}>
|
|
70
|
-
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 12 }}>
|
|
71
|
-
{seeds.map(seed => (
|
|
72
|
-
<button
|
|
73
|
-
key={seed.name}
|
|
74
|
-
disabled={isSeeding}
|
|
75
|
-
onClick={() => runSeed(seed)}
|
|
76
|
-
style={{
|
|
77
|
-
padding: '8px 16px', borderRadius: 6, border: '1px solid #334155',
|
|
78
|
-
background: activeSeed === seed.name ? '#059669' : '#1e293b',
|
|
79
|
-
color: '#e2e8f0', cursor: isSeeding ? 'wait' : 'pointer', fontWeight: 600, fontSize: 12,
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
{activeSeed === seed.name ? '⏳ Seeding...' : seed.name}
|
|
83
|
-
</button>
|
|
84
|
-
))}
|
|
85
|
-
{onReset && (
|
|
86
|
-
<button
|
|
87
|
-
disabled={isSeeding}
|
|
88
|
-
onClick={handleReset}
|
|
89
|
-
style={{
|
|
90
|
-
padding: '8px 16px', borderRadius: 6, border: '1px solid #ef4444',
|
|
91
|
-
background: 'transparent', color: '#ef4444', cursor: isSeeding ? 'wait' : 'pointer',
|
|
92
|
-
fontWeight: 600, fontSize: 12,
|
|
93
|
-
}}
|
|
94
|
-
>
|
|
95
|
-
Reset All
|
|
96
|
-
</button>
|
|
97
|
-
)}
|
|
98
|
-
</div>
|
|
99
|
-
{seeds.map(seed => (
|
|
100
|
-
<div key={seed.name} style={{ fontSize: 11, color: '#94a3b8', marginBottom: 4 }}>
|
|
101
|
-
<strong>{seed.name}</strong>: {seed.description}
|
|
102
|
-
</div>
|
|
103
|
-
))}
|
|
104
|
-
</div>
|
|
105
|
-
{log.length > 0 && (
|
|
106
|
-
<div style={{ borderTop: '1px solid #334155', padding: 8, maxHeight: 150, overflow: 'auto', fontFamily: 'monospace', fontSize: 11 }}>
|
|
107
|
-
{log.map((entry, i) => (
|
|
108
|
-
<div key={i} style={{ padding: '2px 4px', color: entry.includes('❌') ? '#ef4444' : entry.includes('✅') ? '#22c55e' : '#94a3b8' }}>
|
|
109
|
-
{entry}
|
|
110
|
-
</div>
|
|
111
|
-
))}
|
|
112
|
-
</div>
|
|
113
|
-
)}
|
|
114
|
-
</div>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useMemo } from 'react'
|
|
2
|
-
|
|
3
|
-
// ─── User Impersonation Panel ────────────────────────────────
|
|
4
|
-
|
|
5
|
-
interface UserProfile {
|
|
6
|
-
id: string
|
|
7
|
-
name: string
|
|
8
|
-
email: string
|
|
9
|
-
role: string
|
|
10
|
-
avatar?: string
|
|
11
|
-
metadata?: Record<string, unknown>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface UserImpersonationProps {
|
|
15
|
-
users: UserProfile[]
|
|
16
|
-
currentUserId: string
|
|
17
|
-
onImpersonate: (userId: string) => void | Promise<void>
|
|
18
|
-
onStopImpersonation: () => void | Promise<void>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* User Impersonation Panel — Quick role switching for testing.
|
|
23
|
-
* Shows a list of available users with role badges and one-click switching.
|
|
24
|
-
*/
|
|
25
|
-
export function UserImpersonation({ users, currentUserId, onImpersonate, onStopImpersonation }: UserImpersonationProps) {
|
|
26
|
-
const [isImpersonating, setIsImpersonating] = useState(false)
|
|
27
|
-
const [originalUserId] = useState(currentUserId)
|
|
28
|
-
const [filter, setFilter] = useState('')
|
|
29
|
-
|
|
30
|
-
const filtered = filter
|
|
31
|
-
? users.filter(u => u.name.toLowerCase().includes(filter.toLowerCase()) || u.role.toLowerCase().includes(filter.toLowerCase()))
|
|
32
|
-
: users
|
|
33
|
-
|
|
34
|
-
const handleImpersonate = useCallback(async (userId: string) => {
|
|
35
|
-
await onImpersonate(userId)
|
|
36
|
-
setIsImpersonating(true)
|
|
37
|
-
}, [onImpersonate])
|
|
38
|
-
|
|
39
|
-
const handleStop = useCallback(async () => {
|
|
40
|
-
await onStopImpersonation()
|
|
41
|
-
setIsImpersonating(false)
|
|
42
|
-
}, [onStopImpersonation])
|
|
43
|
-
|
|
44
|
-
const roleColors: Record<string, string> = {
|
|
45
|
-
admin: '#ef4444', owner: '#f59e0b', member: '#3b82f6', viewer: '#22c55e', guest: '#94a3b8',
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<div style={{ fontFamily: 'system-ui', fontSize: 13, color: '#e2e8f0', background: '#0f172a', borderRadius: 8, overflow: 'hidden' }}>
|
|
50
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 12px', background: '#1e293b', borderBottom: '1px solid #334155' }}>
|
|
51
|
-
<span style={{ fontWeight: 700, fontSize: 13 }}>👤 User Impersonation</span>
|
|
52
|
-
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
|
|
53
|
-
{isImpersonating && (
|
|
54
|
-
<button onClick={handleStop} style={{ padding: '4px 10px', borderRadius: 4, border: '1px solid #ef4444', background: 'transparent', color: '#ef4444', cursor: 'pointer', fontSize: 11, fontWeight: 600 }}>
|
|
55
|
-
Stop
|
|
56
|
-
</button>
|
|
57
|
-
)}
|
|
58
|
-
<input
|
|
59
|
-
placeholder="Filter users..."
|
|
60
|
-
value={filter}
|
|
61
|
-
onChange={e => setFilter(e.target.value)}
|
|
62
|
-
style={{ padding: '4px 8px', borderRadius: 4, border: '1px solid #334155', background: '#0f172a', color: '#e2e8f0', fontSize: 11, width: 120 }}
|
|
63
|
-
/>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
{isImpersonating && (
|
|
67
|
-
<div style={{ padding: '6px 12px', background: '#7c2d12', fontSize: 11, fontWeight: 600 }}>
|
|
68
|
-
⚠️ Currently impersonating — actions will execute as this user
|
|
69
|
-
</div>
|
|
70
|
-
)}
|
|
71
|
-
<div style={{ maxHeight: 280, overflow: 'auto', padding: 4 }}>
|
|
72
|
-
{filtered.map(user => (
|
|
73
|
-
<div
|
|
74
|
-
key={user.id}
|
|
75
|
-
onClick={() => handleImpersonate(user.id)}
|
|
76
|
-
style={{
|
|
77
|
-
display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', borderRadius: 6, cursor: 'pointer',
|
|
78
|
-
background: user.id === currentUserId ? '#1e293b' : 'transparent',
|
|
79
|
-
borderLeft: user.id === currentUserId ? '3px solid #6366f1' : '3px solid transparent',
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
<div style={{ width: 32, height: 32, borderRadius: '50%', background: '#334155', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14, fontWeight: 700 }}>
|
|
83
|
-
{user.avatar ? <img src={user.avatar} alt="" style={{ width: 32, height: 32, borderRadius: '50%' }} /> : user.name[0]}
|
|
84
|
-
</div>
|
|
85
|
-
<div style={{ flex: 1 }}>
|
|
86
|
-
<div style={{ fontWeight: 600, fontSize: 13 }}>
|
|
87
|
-
{user.name} {user.id === originalUserId && <span style={{ fontSize: 10, color: '#64748b' }}>(you)</span>}
|
|
88
|
-
</div>
|
|
89
|
-
<div style={{ fontSize: 11, color: '#64748b' }}>{user.email}</div>
|
|
90
|
-
</div>
|
|
91
|
-
<span style={{ padding: '2px 8px', borderRadius: 10, fontSize: 10, fontWeight: 700, background: roleColors[user.role] ?? '#64748b', color: 'white', textTransform: 'uppercase' }}>
|
|
92
|
-
{user.role}
|
|
93
|
-
</span>
|
|
94
|
-
</div>
|
|
95
|
-
))}
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ─── Theme Generator Panel ───────────────────────────────────
|
|
102
|
-
|
|
103
|
-
interface ThemeVariable {
|
|
104
|
-
key: string
|
|
105
|
-
label: string
|
|
106
|
-
type: 'color' | 'size' | 'font' | 'number'
|
|
107
|
-
value: string
|
|
108
|
-
category: string
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
interface ThemeGeneratorProps {
|
|
112
|
-
variables: ThemeVariable[]
|
|
113
|
-
onUpdate: (key: string, value: string) => void
|
|
114
|
-
onExport: (variables: ThemeVariable[]) => void
|
|
115
|
-
onReset: () => void
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* White-Label Theme Generator — Interactive theming playground.
|
|
120
|
-
* Live-edit CSS variables with instant preview.
|
|
121
|
-
*/
|
|
122
|
-
export function ThemeGenerator({ variables, onUpdate, onExport, onReset }: ThemeGeneratorProps) {
|
|
123
|
-
const [search, setSearch] = useState('')
|
|
124
|
-
|
|
125
|
-
const categories = useMemo(() => {
|
|
126
|
-
const cats = new Map<string, ThemeVariable[]>()
|
|
127
|
-
const filtered = search
|
|
128
|
-
? variables.filter(v => v.label.toLowerCase().includes(search.toLowerCase()) || v.key.toLowerCase().includes(search.toLowerCase()))
|
|
129
|
-
: variables
|
|
130
|
-
for (const v of filtered) {
|
|
131
|
-
if (!cats.has(v.category)) cats.set(v.category, [])
|
|
132
|
-
cats.get(v.category)!.push(v)
|
|
133
|
-
}
|
|
134
|
-
return cats
|
|
135
|
-
}, [variables, search])
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<div style={{ fontFamily: 'system-ui', fontSize: 13, color: '#e2e8f0', background: '#0f172a', borderRadius: 8, overflow: 'hidden' }}>
|
|
139
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 12px', background: '#1e293b', borderBottom: '1px solid #334155' }}>
|
|
140
|
-
<span style={{ fontWeight: 700, fontSize: 13 }}>🎨 Theme Generator</span>
|
|
141
|
-
<div style={{ display: 'flex', gap: 6 }}>
|
|
142
|
-
<input
|
|
143
|
-
placeholder="Search..."
|
|
144
|
-
value={search}
|
|
145
|
-
onChange={e => setSearch(e.target.value)}
|
|
146
|
-
style={{ padding: '4px 8px', borderRadius: 4, border: '1px solid #334155', background: '#0f172a', color: '#e2e8f0', fontSize: 11, width: 100 }}
|
|
147
|
-
/>
|
|
148
|
-
<button onClick={() => onExport(variables)} style={btn}>Export</button>
|
|
149
|
-
<button onClick={onReset} style={btn}>Reset</button>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
<div style={{ maxHeight: 400, overflow: 'auto', padding: 8 }}>
|
|
153
|
-
{[...categories.entries()].map(([cat, vars]) => (
|
|
154
|
-
<div key={cat}>
|
|
155
|
-
<div style={{ padding: '8px 6px 4px', color: '#64748b', fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 1 }}>{cat}</div>
|
|
156
|
-
{vars.map(v => (
|
|
157
|
-
<div key={v.key} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 6px' }}>
|
|
158
|
-
<span style={{ flex: 1, fontSize: 12 }}>{v.label}</span>
|
|
159
|
-
{v.type === 'color' ? (
|
|
160
|
-
<input
|
|
161
|
-
type="color"
|
|
162
|
-
value={v.value}
|
|
163
|
-
onChange={e => onUpdate(v.key, e.target.value)}
|
|
164
|
-
style={{ width: 32, height: 24, border: 'none', borderRadius: 4, cursor: 'pointer' }}
|
|
165
|
-
/>
|
|
166
|
-
) : v.type === 'number' ? (
|
|
167
|
-
<input
|
|
168
|
-
type="number"
|
|
169
|
-
value={v.value}
|
|
170
|
-
onChange={e => onUpdate(v.key, e.target.value)}
|
|
171
|
-
style={{ width: 60, padding: '2px 6px', borderRadius: 4, border: '1px solid #334155', background: '#0f172a', color: '#e2e8f0', fontSize: 11 }}
|
|
172
|
-
/>
|
|
173
|
-
) : (
|
|
174
|
-
<input
|
|
175
|
-
type="text"
|
|
176
|
-
value={v.value}
|
|
177
|
-
onChange={e => onUpdate(v.key, e.target.value)}
|
|
178
|
-
style={{ width: 120, padding: '2px 6px', borderRadius: 4, border: '1px solid #334155', background: '#0f172a', color: '#e2e8f0', fontSize: 11 }}
|
|
179
|
-
/>
|
|
180
|
-
)}
|
|
181
|
-
<code style={{ fontSize: 9, color: '#64748b', maxWidth: 80, overflow: 'hidden', textOverflow: 'ellipsis' }}>{v.key}</code>
|
|
182
|
-
</div>
|
|
183
|
-
))}
|
|
184
|
-
</div>
|
|
185
|
-
))}
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const btn: React.CSSProperties = { padding: '4px 10px', borderRadius: 4, border: '1px solid #334155', background: '#0f172a', color: '#e2e8f0', cursor: 'pointer', fontSize: 11, fontWeight: 600 }
|