termcast 1.3.30 → 1.3.32
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/dist/apis/cache.d.ts.map +1 -1
- package/dist/apis/cache.js +4 -39
- package/dist/apis/cache.js.map +1 -1
- package/dist/apis/hud.d.ts.map +1 -1
- package/dist/apis/hud.js +13 -31
- package/dist/apis/hud.js.map +1 -1
- package/dist/apis/localstorage.d.ts.map +1 -1
- package/dist/apis/localstorage.js +3 -27
- package/dist/apis/localstorage.js.map +1 -1
- package/dist/apis/toast.d.ts +16 -43
- package/dist/apis/toast.d.ts.map +1 -1
- package/dist/apis/toast.js +78 -177
- package/dist/apis/toast.js.map +1 -1
- package/dist/build.d.ts +3 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +52 -2
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +206 -25
- package/dist/cli.js.map +1 -1
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +1 -0
- package/dist/colors.js.map +1 -1
- package/dist/compile.d.ts +0 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +18 -23
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +30 -15
- package/dist/components/actions.js.map +1 -1
- package/dist/components/animation-tick.d.ts +12 -0
- package/dist/components/animation-tick.d.ts.map +1 -0
- package/dist/components/animation-tick.js +63 -0
- package/dist/components/animation-tick.js.map +1 -0
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +10 -13
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts +1 -0
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +27 -26
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/extension-preferences.d.ts.map +1 -1
- package/dist/components/extension-preferences.js +15 -10
- package/dist/components/extension-preferences.js.map +1 -1
- package/dist/components/footer.d.ts +13 -0
- package/dist/components/footer.d.ts.map +1 -0
- package/dist/components/footer.js +106 -0
- package/dist/components/footer.js.map +1 -0
- package/dist/components/form/file-autocomplete.d.ts +19 -4
- package/dist/components/form/file-autocomplete.d.ts.map +1 -1
- package/dist/components/form/file-autocomplete.js +56 -55
- package/dist/components/form/file-autocomplete.js.map +1 -1
- package/dist/components/form/file-picker.d.ts.map +1 -1
- package/dist/components/form/file-picker.js +26 -15
- package/dist/components/form/file-picker.js.map +1 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +17 -15
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/with-left-border.d.ts.map +1 -1
- package/dist/components/form/with-left-border.js +4 -12
- package/dist/components/form/with-left-border.js.map +1 -1
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +126 -86
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.d.ts.map +1 -1
- package/dist/components/loading-bar.js +5 -22
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/loading-text.d.ts.map +1 -1
- package/dist/components/loading-text.js +3 -22
- package/dist/components/loading-text.js.map +1 -1
- package/dist/components/theme-picker.d.ts +2 -0
- package/dist/components/theme-picker.d.ts.map +1 -0
- package/dist/components/theme-picker.js +37 -0
- package/dist/components/theme-picker.js.map +1 -0
- package/dist/descendants.d.ts +6 -0
- package/dist/descendants.d.ts.map +1 -1
- package/dist/descendants.js +74 -8
- package/dist/descendants.js.map +1 -1
- package/dist/examples/internal/descendants-rerender.d.ts +14 -0
- package/dist/examples/internal/descendants-rerender.d.ts.map +1 -0
- package/dist/examples/internal/descendants-rerender.js +145 -0
- package/dist/examples/internal/descendants-rerender.js.map +1 -0
- package/dist/examples/internal/simple-dialog.js +4 -1
- package/dist/examples/internal/simple-dialog.js.map +1 -1
- package/dist/examples/internal/simple-scrollbox.js +1 -1
- package/dist/examples/internal/simple-scrollbox.js.map +1 -1
- package/dist/examples/list-with-dropdown.js +1 -1
- package/dist/examples/list-with-dropdown.js.map +1 -1
- package/dist/examples/miscellaneous.js +1 -1
- package/dist/examples/miscellaneous.js.map +1 -1
- package/dist/examples/toast-action.d.ts +2 -0
- package/dist/examples/toast-action.d.ts.map +1 -0
- package/dist/examples/toast-action.js +76 -0
- package/dist/examples/toast-action.js.map +1 -0
- package/dist/examples/toast-variations.js +38 -36
- package/dist/examples/toast-variations.js.map +1 -1
- package/dist/extensions/dev.d.ts +1 -1
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +62 -30
- package/dist/extensions/dev.js.map +1 -1
- package/dist/extensions/home.d.ts.map +1 -1
- package/dist/extensions/home.js +4 -3
- package/dist/extensions/home.js.map +1 -1
- package/dist/extensions/react-refresh-init.d.ts +5 -0
- package/dist/extensions/react-refresh-init.d.ts.map +1 -0
- package/dist/extensions/react-refresh-init.js +52 -0
- package/dist/extensions/react-refresh-init.js.map +1 -0
- package/dist/internal/date-picker-widget.js +1 -1
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/dialog.d.ts +8 -3
- package/dist/internal/dialog.d.ts.map +1 -1
- package/dist/internal/dialog.js +37 -53
- package/dist/internal/dialog.js.map +1 -1
- package/dist/internal/navigation.d.ts +1 -0
- package/dist/internal/navigation.d.ts.map +1 -1
- package/dist/internal/navigation.js +25 -1
- package/dist/internal/navigation.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +9 -197
- package/dist/internal/providers.js.map +1 -1
- package/dist/internal/scrollbox.d.ts.map +1 -1
- package/dist/internal/scrollbox.js +1 -0
- package/dist/internal/scrollbox.js.map +1 -1
- package/dist/release.d.ts +1 -0
- package/dist/release.d.ts.map +1 -1
- package/dist/release.js +16 -9
- package/dist/release.js.map +1 -1
- package/dist/state.d.ts +27 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +6 -0
- package/dist/state.js.map +1 -1
- package/dist/theme.d.ts +6 -19
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +76 -45
- package/dist/theme.js.map +1 -1
- package/dist/themes/aura.json +69 -0
- package/dist/themes/ayu.json +80 -0
- package/dist/themes/catppuccin-frappe.json +233 -0
- package/dist/themes/catppuccin-macchiato.json +233 -0
- package/dist/themes/catppuccin.json +112 -0
- package/dist/themes/cobalt2.json +228 -0
- package/dist/themes/cursor.json +249 -0
- package/dist/themes/dracula.json +219 -0
- package/dist/themes/everforest.json +241 -0
- package/dist/themes/flexoki.json +237 -0
- package/dist/themes/github-light.json +56 -0
- package/dist/themes/github.json +241 -0
- package/dist/themes/gruvbox.json +95 -0
- package/dist/themes/kanagawa.json +77 -0
- package/dist/themes/lucent-orng.json +227 -0
- package/dist/themes/material.json +235 -0
- package/dist/themes/matrix.json +77 -0
- package/dist/themes/mercury.json +245 -0
- package/dist/themes/monokai.json +221 -0
- package/dist/themes/nightowl.json +221 -0
- package/dist/themes/nord.json +223 -0
- package/dist/themes/one-dark.json +84 -0
- package/dist/themes/opencode-light.json +62 -0
- package/dist/themes/opencode.json +245 -0
- package/dist/themes/orng.json +245 -0
- package/dist/themes/palenight.json +222 -0
- package/dist/themes/rosepine.json +234 -0
- package/dist/themes/solarized.json +223 -0
- package/dist/themes/synthwave84.json +226 -0
- package/dist/themes/termcast.json +226 -0
- package/dist/themes/tokyonight.json +243 -0
- package/dist/themes/vercel.json +255 -0
- package/dist/themes/vesper.json +218 -0
- package/dist/themes/zenburn.json +223 -0
- package/dist/themes.d.ts +57 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +181 -0
- package/dist/themes.js.map +1 -0
- package/dist/utils/run-command.d.ts +2 -1
- package/dist/utils/run-command.d.ts.map +1 -1
- package/dist/utils/run-command.js +20 -10
- package/dist/utils/run-command.js.map +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +90 -17
- package/dist/utils.js.map +1 -1
- package/dist/watcher.d.ts +3 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +16 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +16 -10
- package/src/apis/cache.tsx +5 -44
- package/src/apis/hud.tsx +17 -62
- package/src/apis/localstorage.tsx +3 -32
- package/src/apis/toast.tsx +91 -275
- package/src/build.test.tsx +10 -0
- package/src/build.tsx +61 -1
- package/src/cli.tsx +365 -103
- package/src/colors.tsx +1 -0
- package/src/compile.tsx +21 -29
- package/src/compile.vitest.tsx +300 -0
- package/src/components/actions.tsx +64 -45
- package/src/components/animation-tick.tsx +85 -0
- package/src/components/detail.tsx +31 -35
- package/src/components/dropdown.tsx +32 -21
- package/src/components/extension-preferences.tsx +14 -10
- package/src/components/footer.tsx +241 -0
- package/src/components/form/file-autocomplete.tsx +80 -60
- package/src/components/form/file-picker.tsx +37 -25
- package/src/components/form/index.tsx +45 -41
- package/src/components/form/with-left-border.tsx +4 -14
- package/src/components/list.tsx +181 -121
- package/src/components/loading-bar.tsx +5 -25
- package/src/components/loading-text.tsx +4 -23
- package/src/components/theme-picker.tsx +57 -0
- package/src/descendants.tsx +98 -9
- package/src/examples/actions-dialog-layout.vitest.tsx +112 -0
- package/src/examples/file-autocomplete.vitest.tsx +131 -122
- package/src/examples/form-basic.vitest.tsx +463 -644
- package/src/examples/form-dropdown.vitest.tsx +553 -571
- package/src/examples/form-scroll.vitest.tsx +112 -102
- package/src/examples/form-tagpicker.vitest.tsx +364 -338
- package/src/examples/internal/descendants-rerender.tsx +273 -0
- package/src/examples/internal/descendants-rerender.vitest.tsx +194 -0
- package/src/examples/internal/simple-dialog.tsx +4 -4
- package/src/examples/internal/simple-scrollbox.tsx +2 -2
- package/src/examples/internal/simple-scrollbox.vitest.tsx +43 -31
- package/src/examples/list-detail-metadata.vitest.tsx +34 -30
- package/src/examples/list-dropdown-default.vitest.tsx +84 -72
- package/src/examples/list-empty-view.vitest.tsx +93 -0
- package/src/examples/list-fetch-data.vitest.tsx +36 -30
- package/src/examples/list-scrollbox.vitest.tsx +59 -39
- package/src/examples/list-with-detail.vitest.tsx +339 -314
- package/src/examples/list-with-dropdown.tsx +1 -0
- package/src/examples/list-with-dropdown.vitest.tsx +176 -150
- package/src/examples/list-with-sections.vitest.tsx +289 -270
- package/src/examples/list-with-toast.vitest.tsx +44 -44
- package/src/examples/miscellaneous.tsx +10 -0
- package/src/examples/simple-file-picker.vitest.tsx +90 -86
- package/src/examples/simple-grid.vitest.tsx +275 -249
- package/src/examples/simple-navigation.vitest.tsx +192 -168
- package/src/examples/store.vitest.tsx +6 -4
- package/src/examples/swift-extension.vitest.tsx +31 -19
- package/src/examples/synonyms.vitest.tsx +93 -83
- package/src/examples/toast-action.tsx +160 -0
- package/src/examples/toast-action.vitest.tsx +404 -0
- package/src/examples/toast-variations.tsx +58 -57
- package/src/examples/toast-variations.vitest.tsx +186 -166
- package/src/extensions/dev.tsx +74 -33
- package/src/extensions/dev.vitest.tsx +162 -69
- package/src/extensions/home.tsx +5 -6
- package/src/extensions/react-refresh-init.tsx +59 -0
- package/src/internal/date-picker-widget.tsx +1 -1
- package/src/internal/dialog.tsx +59 -83
- package/src/internal/navigation.tsx +37 -4
- package/src/internal/providers.tsx +27 -315
- package/src/internal/scrollbox.tsx +1 -0
- package/src/release.tsx +16 -10
- package/src/state.tsx +36 -3
- package/src/theme.tsx +82 -51
- package/src/themes/aura.json +69 -0
- package/src/themes/ayu.json +80 -0
- package/src/themes/catppuccin-frappe.json +233 -0
- package/src/themes/catppuccin-macchiato.json +233 -0
- package/src/themes/catppuccin.json +112 -0
- package/src/themes/cobalt2.json +228 -0
- package/src/themes/cursor.json +249 -0
- package/src/themes/dracula.json +219 -0
- package/src/themes/everforest.json +241 -0
- package/src/themes/flexoki.json +237 -0
- package/src/themes/github-light.json +56 -0
- package/src/themes/github.json +241 -0
- package/src/themes/gruvbox.json +95 -0
- package/src/themes/kanagawa.json +77 -0
- package/src/themes/lucent-orng.json +227 -0
- package/src/themes/material.json +235 -0
- package/src/themes/matrix.json +77 -0
- package/src/themes/mercury.json +252 -0
- package/src/themes/monokai.json +221 -0
- package/src/themes/nightowl.json +221 -0
- package/src/themes/nord.json +223 -0
- package/src/themes/one-dark.json +84 -0
- package/src/themes/opencode-light.json +62 -0
- package/src/themes/opencode.json +245 -0
- package/src/themes/orng.json +245 -0
- package/src/themes/palenight.json +222 -0
- package/src/themes/rosepine.json +234 -0
- package/src/themes/solarized.json +223 -0
- package/src/themes/synthwave84.json +226 -0
- package/src/themes/termcast.json +227 -0
- package/src/themes/tokyonight.json +243 -0
- package/src/themes/vercel.json +255 -0
- package/src/themes/vesper.json +218 -0
- package/src/themes/zenburn.json +223 -0
- package/src/themes.ts +291 -0
- package/src/utils/run-command.tsx +23 -12
- package/src/utils.tsx +115 -18
- package/src/watcher.tsx +19 -0
package/src/internal/dialog.tsx
CHANGED
|
@@ -1,30 +1,11 @@
|
|
|
1
|
-
import { useKeyboard
|
|
1
|
+
import { useKeyboard } from '@opentui/react'
|
|
2
2
|
import React, { type ReactNode, useRef, useContext } from 'react'
|
|
3
3
|
import { Theme } from 'termcast/src/theme'
|
|
4
4
|
import { InFocus, useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
5
5
|
import { CommonProps } from 'termcast/src/utils'
|
|
6
|
-
import {
|
|
7
|
-
useStore,
|
|
8
|
-
type DialogPosition,
|
|
9
|
-
type DialogStackItem,
|
|
10
|
-
} from 'termcast/src/state'
|
|
11
|
-
import { logger } from '../logger'
|
|
12
|
-
import { ToastOverlay } from 'termcast/src/apis/toast'
|
|
13
|
-
import { NavigationContext } from 'termcast/src/internal/navigation'
|
|
6
|
+
import { useStore, type DialogPosition } from 'termcast/src/state'
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
topLeft: '┏',
|
|
17
|
-
topRight: '┓',
|
|
18
|
-
bottomLeft: '',
|
|
19
|
-
bottomRight: '',
|
|
20
|
-
horizontal: '━',
|
|
21
|
-
vertical: '┃',
|
|
22
|
-
topT: '+',
|
|
23
|
-
bottomT: '+',
|
|
24
|
-
leftT: '+',
|
|
25
|
-
rightT: '+',
|
|
26
|
-
cross: '+',
|
|
27
|
-
}
|
|
8
|
+
import { NavigationContext } from 'termcast/src/internal/navigation'
|
|
28
9
|
|
|
29
10
|
export type { DialogPosition } from 'termcast/src/state'
|
|
30
11
|
|
|
@@ -39,7 +20,6 @@ export function Dialog({
|
|
|
39
20
|
position = 'center',
|
|
40
21
|
onClickOutside,
|
|
41
22
|
}: DialogProps): any {
|
|
42
|
-
const dimensions = useTerminalDimensions()
|
|
43
23
|
const inFocus = useIsInFocus()
|
|
44
24
|
const clickedInsideDialog = useRef(false)
|
|
45
25
|
|
|
@@ -61,30 +41,20 @@ export function Dialog({
|
|
|
61
41
|
return {
|
|
62
42
|
alignItems: 'flex-end' as const,
|
|
63
43
|
justifyContent: 'flex-start' as const,
|
|
64
|
-
|
|
65
|
-
paddingRight: 2,
|
|
66
|
-
paddingBottom: undefined,
|
|
67
|
-
paddingLeft: undefined,
|
|
44
|
+
padding: 2,
|
|
68
45
|
}
|
|
69
46
|
case 'bottom-right':
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// paddingBottom: 2,
|
|
76
|
-
// paddingRight: 2,
|
|
77
|
-
// paddingLeft: undefined
|
|
78
|
-
// }
|
|
47
|
+
return {
|
|
48
|
+
alignItems: 'flex-end' as const,
|
|
49
|
+
justifyContent: 'flex-end' as const,
|
|
50
|
+
padding: 2,
|
|
51
|
+
}
|
|
79
52
|
case 'center':
|
|
80
53
|
default:
|
|
81
54
|
return {
|
|
82
55
|
alignItems: 'center' as const,
|
|
83
|
-
justifyContent: '
|
|
84
|
-
|
|
85
|
-
paddingBottom: undefined,
|
|
86
|
-
paddingLeft: undefined,
|
|
87
|
-
paddingRight: undefined,
|
|
56
|
+
justifyContent: 'center' as const,
|
|
57
|
+
padding: 0,
|
|
88
58
|
}
|
|
89
59
|
}
|
|
90
60
|
}
|
|
@@ -94,26 +64,21 @@ export function Dialog({
|
|
|
94
64
|
return (
|
|
95
65
|
<box
|
|
96
66
|
border={false}
|
|
97
|
-
|
|
98
|
-
|
|
67
|
+
flexGrow={1}
|
|
68
|
+
left={-2}
|
|
99
69
|
alignItems={positionStyles.alignItems}
|
|
100
70
|
justifyContent={positionStyles.justifyContent}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
paddingBottom={positionStyles.paddingBottom}
|
|
104
|
-
paddingLeft={positionStyles.paddingLeft}
|
|
105
|
-
paddingRight={positionStyles.paddingRight}
|
|
106
|
-
left={0}
|
|
107
|
-
top={0}
|
|
71
|
+
padding={positionStyles.padding}
|
|
72
|
+
// backgroundColor={Theme.background}
|
|
108
73
|
onMouseDown={handleBackdropClick}
|
|
109
74
|
>
|
|
110
75
|
<box
|
|
111
76
|
border
|
|
112
|
-
|
|
77
|
+
borderStyle='rounded'
|
|
113
78
|
width={76}
|
|
114
|
-
maxWidth=
|
|
79
|
+
maxWidth='95%'
|
|
115
80
|
backgroundColor={Theme.backgroundPanel}
|
|
116
|
-
borderColor={Theme.
|
|
81
|
+
borderColor={Theme.accent}
|
|
117
82
|
paddingTop={1}
|
|
118
83
|
onMouseDown={handleDialogClick}
|
|
119
84
|
>
|
|
@@ -143,12 +108,13 @@ export function DialogProvider(props: DialogProviderProps): any {
|
|
|
143
108
|
}
|
|
144
109
|
})
|
|
145
110
|
|
|
111
|
+
// Children lose focus only when there's a dialog (toast uses unique shortcuts so no focus stealing needed)
|
|
112
|
+
const childrenInFocus = !dialogStack?.length
|
|
113
|
+
|
|
146
114
|
return (
|
|
147
115
|
<>
|
|
148
|
-
<InFocus inFocus={
|
|
149
|
-
|
|
150
|
-
<ToastOverlay />
|
|
151
|
-
</InFocus>
|
|
116
|
+
<InFocus inFocus={childrenInFocus}>{props.children}</InFocus>
|
|
117
|
+
|
|
152
118
|
</>
|
|
153
119
|
)
|
|
154
120
|
}
|
|
@@ -161,31 +127,29 @@ export function DialogOverlay(): any {
|
|
|
161
127
|
return null
|
|
162
128
|
}
|
|
163
129
|
|
|
130
|
+
// Only render the topmost dialog
|
|
131
|
+
const topIndex = dialogStack.length - 1
|
|
132
|
+
const item = dialogStack[topIndex]
|
|
133
|
+
|
|
164
134
|
return (
|
|
165
|
-
<box position='absolute'>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
{item.element}
|
|
184
|
-
</NavigationContext.Provider>
|
|
185
|
-
</Dialog>
|
|
186
|
-
</InFocus>
|
|
187
|
-
)
|
|
188
|
-
})}
|
|
135
|
+
<box position='absolute' width='100%' height='100%' flexDirection='column'>
|
|
136
|
+
<InFocus inFocus={true}>
|
|
137
|
+
<Dialog
|
|
138
|
+
position={item.position}
|
|
139
|
+
onClickOutside={() => {
|
|
140
|
+
const state = useStore.getState()
|
|
141
|
+
if (state.dialogStack.length > 0) {
|
|
142
|
+
useStore.setState({
|
|
143
|
+
dialogStack: state.dialogStack.slice(0, -1),
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<NavigationContext.Provider value={navContext}>
|
|
149
|
+
{item.element}
|
|
150
|
+
</NavigationContext.Provider>
|
|
151
|
+
</Dialog>
|
|
152
|
+
</InFocus>
|
|
189
153
|
</box>
|
|
190
154
|
)
|
|
191
155
|
}
|
|
@@ -193,13 +157,24 @@ export function DialogOverlay(): any {
|
|
|
193
157
|
export function useDialog() {
|
|
194
158
|
const dialogStack = useStore((state) => state.dialogStack)
|
|
195
159
|
|
|
196
|
-
const pushDialog = (
|
|
160
|
+
const pushDialog = (args: {
|
|
161
|
+
element: ReactNode
|
|
162
|
+
position?: DialogPosition
|
|
163
|
+
type?: 'actions'
|
|
164
|
+
}) => {
|
|
197
165
|
const state = useStore.getState()
|
|
198
166
|
useStore.setState({
|
|
199
|
-
dialogStack: [
|
|
167
|
+
dialogStack: [
|
|
168
|
+
...state.dialogStack,
|
|
169
|
+
{ element: args.element, position: args.position, type: args.type },
|
|
170
|
+
],
|
|
200
171
|
})
|
|
201
172
|
}
|
|
202
173
|
|
|
174
|
+
const pushActions = (element: ReactNode, position: DialogPosition='center') => {
|
|
175
|
+
pushDialog({ element, position, type: 'actions' })
|
|
176
|
+
}
|
|
177
|
+
|
|
203
178
|
const clearDialogs = () => {
|
|
204
179
|
useStore.setState({ dialogStack: [] })
|
|
205
180
|
}
|
|
@@ -210,6 +185,7 @@ export function useDialog() {
|
|
|
210
185
|
|
|
211
186
|
return {
|
|
212
187
|
push: pushDialog,
|
|
188
|
+
pushActions,
|
|
213
189
|
clear: clearDialogs,
|
|
214
190
|
replace: replaceDialog,
|
|
215
191
|
stack: dialogStack,
|
|
@@ -16,6 +16,7 @@ import { logger } from '../logger'
|
|
|
16
16
|
|
|
17
17
|
interface Navigation {
|
|
18
18
|
push: (element: ReactNode, onPop?: () => void) => void
|
|
19
|
+
replace: (element: ReactNode, onPop?: () => void) => void
|
|
19
20
|
pop: () => void
|
|
20
21
|
popToRoot: () => void
|
|
21
22
|
}
|
|
@@ -26,9 +27,9 @@ interface NavigationContextType {
|
|
|
26
27
|
isPending: boolean
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export const NavigationContext = createContext<
|
|
30
|
-
undefined
|
|
31
|
-
)
|
|
30
|
+
export const NavigationContext = createContext<
|
|
31
|
+
NavigationContextType | undefined
|
|
32
|
+
>(undefined)
|
|
32
33
|
|
|
33
34
|
interface NavigationProviderProps extends CommonProps {
|
|
34
35
|
children: ReactNode
|
|
@@ -63,6 +64,34 @@ export function NavigationProvider(props: NavigationProviderProps): any {
|
|
|
63
64
|
useStore.setState({
|
|
64
65
|
navigationStack: [...currentStack, { element, onPop }],
|
|
65
66
|
dialogStack: [],
|
|
67
|
+
toast: null,
|
|
68
|
+
toastWithPrimaryAction: false,
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
}, [])
|
|
72
|
+
|
|
73
|
+
const replace = useCallback((element: any, onPop?: () => void) => {
|
|
74
|
+
if (!element) {
|
|
75
|
+
throw new Error(`cannot replace with falsy value ${element}`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
logger.log(
|
|
79
|
+
'replacing',
|
|
80
|
+
(element as any)?.type?.name || (element as any)?.type,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const currentStack = useStore.getState().navigationStack
|
|
84
|
+
const newStack =
|
|
85
|
+
currentStack.length > 0
|
|
86
|
+
? [...currentStack.slice(0, -1), { element, onPop }]
|
|
87
|
+
: [{ element, onPop }]
|
|
88
|
+
|
|
89
|
+
startNavigationTransition(() => {
|
|
90
|
+
useStore.setState({
|
|
91
|
+
navigationStack: newStack,
|
|
92
|
+
dialogStack: [],
|
|
93
|
+
toast: null,
|
|
94
|
+
toastWithPrimaryAction: false,
|
|
66
95
|
})
|
|
67
96
|
})
|
|
68
97
|
}, [])
|
|
@@ -101,10 +130,11 @@ export function NavigationProvider(props: NavigationProviderProps): any {
|
|
|
101
130
|
const navigation = React.useMemo(
|
|
102
131
|
() => ({
|
|
103
132
|
push,
|
|
133
|
+
replace,
|
|
104
134
|
pop,
|
|
105
135
|
popToRoot,
|
|
106
136
|
}),
|
|
107
|
-
[push, pop, popToRoot],
|
|
137
|
+
[push, replace, pop, popToRoot],
|
|
108
138
|
)
|
|
109
139
|
|
|
110
140
|
const value = React.useMemo(
|
|
@@ -123,6 +153,9 @@ export function NavigationProvider(props: NavigationProviderProps): any {
|
|
|
123
153
|
useKeyboard((evt) => {
|
|
124
154
|
if (!inFocus) return
|
|
125
155
|
if (evt.name === 'escape') {
|
|
156
|
+
// If there's a toast visible, let toast handle escape first
|
|
157
|
+
if (useStore.getState().toast) return
|
|
158
|
+
|
|
126
159
|
if (stack.length > 1) {
|
|
127
160
|
logger.log(
|
|
128
161
|
'popping navigation',
|
|
@@ -11,19 +11,19 @@ import { NavigationProvider } from 'termcast/src/internal/navigation'
|
|
|
11
11
|
import { CommonProps } from 'termcast/src/utils'
|
|
12
12
|
import { Cache } from 'termcast/src/apis/cache'
|
|
13
13
|
import { logger } from 'termcast/src/logger'
|
|
14
|
-
import { Theme } from 'termcast/src/theme'
|
|
15
|
-
import { useKeyboard } from '@opentui/react'
|
|
16
|
-
import { TextAttributes } from '@opentui/core'
|
|
14
|
+
import { Theme, initializeTheme } from 'termcast/src/theme'
|
|
17
15
|
import { useStore } from 'termcast/src/state'
|
|
18
|
-
import
|
|
19
|
-
import * as path from 'path'
|
|
20
|
-
import { exec } from 'child_process'
|
|
21
|
-
import dedent from 'string-dedent'
|
|
16
|
+
import { useTerminalDimensions } from '@opentui/react'
|
|
22
17
|
import { initializeErrorHandlers } from 'termcast/src/internal/error-handler'
|
|
23
18
|
|
|
19
|
+
import { InFocus } from './focus-context'
|
|
20
|
+
|
|
24
21
|
// Initialize error handlers at module load time
|
|
25
22
|
initializeErrorHandlers()
|
|
26
23
|
|
|
24
|
+
// Initialize theme from persisted storage
|
|
25
|
+
initializeTheme()
|
|
26
|
+
|
|
27
27
|
const queryClient = new QueryClient({
|
|
28
28
|
defaultOptions: {
|
|
29
29
|
queries: {
|
|
@@ -75,9 +75,6 @@ class ErrorBoundaryClass extends Component<
|
|
|
75
75
|
constructor(props: { children: ReactNode }) {
|
|
76
76
|
super(props)
|
|
77
77
|
this.state = { hasError: false, error: null }
|
|
78
|
-
|
|
79
|
-
this.openGitHubIssue = this.openGitHubIssue.bind(this)
|
|
80
|
-
this.getRecentLogs = this.getRecentLogs.bind(this)
|
|
81
78
|
}
|
|
82
79
|
|
|
83
80
|
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
|
|
@@ -92,314 +89,19 @@ class ErrorBoundaryClass extends Component<
|
|
|
92
89
|
})
|
|
93
90
|
}
|
|
94
91
|
|
|
95
|
-
getRecentLogs(): string {
|
|
96
|
-
const LOG_FILE = path.join(process.cwd(), 'app.log')
|
|
97
|
-
try {
|
|
98
|
-
if (fs.existsSync(LOG_FILE)) {
|
|
99
|
-
const content = fs.readFileSync(LOG_FILE, 'utf-8')
|
|
100
|
-
const lines = content.split('\n').filter((line) => line.trim())
|
|
101
|
-
// Get last 200 lines
|
|
102
|
-
const recentLines = lines.slice(-200)
|
|
103
|
-
return recentLines.join('\n')
|
|
104
|
-
}
|
|
105
|
-
} catch (err) {
|
|
106
|
-
logger.error('Failed to read log file:', err)
|
|
107
|
-
}
|
|
108
|
-
return 'No logs available'
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
openGitHubIssue(): void {
|
|
112
|
-
const error = this.state.error
|
|
113
|
-
if (!error) return
|
|
114
|
-
|
|
115
|
-
const logs = this.getRecentLogs()
|
|
116
|
-
|
|
117
|
-
// Get navigation stack information for body
|
|
118
|
-
const navigationStack = useStore.getState().navigationStack
|
|
119
|
-
const navigationInfo = navigationStack
|
|
120
|
-
.map((item, index) => {
|
|
121
|
-
const element = item.element as any
|
|
122
|
-
const componentName =
|
|
123
|
-
element?.type?.displayName ||
|
|
124
|
-
element?.type?.name ||
|
|
125
|
-
element?.type ||
|
|
126
|
-
'Unknown'
|
|
127
|
-
return `${index + 1}. ${componentName}`
|
|
128
|
-
})
|
|
129
|
-
.join('\n')
|
|
130
|
-
|
|
131
|
-
// Get current extension/command info
|
|
132
|
-
const extensionPackageJson = useStore.getState().extensionPackageJson
|
|
133
|
-
const currentCommandName = useStore.getState().currentCommandName
|
|
134
|
-
const extensionPath = useStore.getState().extensionPath
|
|
135
|
-
|
|
136
|
-
const extensionName =
|
|
137
|
-
extensionPackageJson?.name ||
|
|
138
|
-
(extensionPath ? path.basename(extensionPath) : null)
|
|
139
|
-
|
|
140
|
-
let contextInfo = ''
|
|
141
|
-
let titlePrefix = ''
|
|
142
|
-
|
|
143
|
-
if (extensionName) {
|
|
144
|
-
contextInfo = `Extension: ${extensionName}`
|
|
145
|
-
titlePrefix = `\`${extensionName}\``
|
|
146
|
-
if (currentCommandName) {
|
|
147
|
-
contextInfo += ` | Command: ${currentCommandName}`
|
|
148
|
-
titlePrefix += ` > \`${currentCommandName}\``
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const title = encodeURIComponent(
|
|
153
|
-
titlePrefix ? `${titlePrefix}: ${error.message}` : error.message,
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
const MAX_URL_LENGTH = 4096
|
|
157
|
-
const baseUrl = 'https://github.com/remorses/termcast/issues/new?title='
|
|
158
|
-
const titlePart = `${baseUrl}${title}&body=`
|
|
159
|
-
|
|
160
|
-
// Calculate how much space we have for the body
|
|
161
|
-
const availableBodyLength = MAX_URL_LENGTH - titlePart.length
|
|
162
|
-
|
|
163
|
-
// Always create a minimal body
|
|
164
|
-
const minimalBody = dedent`
|
|
165
|
-
## Error Details
|
|
166
|
-
|
|
167
|
-
**Message:** ${error.message}
|
|
168
|
-
**Context:** ${contextInfo || 'No extension loaded'}
|
|
169
|
-
**Navigation:** \`${navigationStack
|
|
170
|
-
.map((item) => {
|
|
171
|
-
const element = item.element as any
|
|
172
|
-
return (
|
|
173
|
-
element?.type?.displayName ||
|
|
174
|
-
element?.type?.name ||
|
|
175
|
-
element?.type ||
|
|
176
|
-
'Unknown'
|
|
177
|
-
)
|
|
178
|
-
})
|
|
179
|
-
.join(' > ')}\`
|
|
180
|
-
|
|
181
|
-
## Stack Trace
|
|
182
|
-
|
|
183
|
-
\`\`\`\`
|
|
184
|
-
${error.stack || 'No stack trace available'}
|
|
185
|
-
\`\`\`\`
|
|
186
|
-
|
|
187
|
-
## Environment
|
|
188
|
-
|
|
189
|
-
- Platform: ${process.platform}
|
|
190
|
-
- Node Version: ${process.version}
|
|
191
|
-
- Date: ${new Date().toISOString()}
|
|
192
|
-
`
|
|
193
|
-
|
|
194
|
-
let body = encodeURIComponent(minimalBody)
|
|
195
|
-
|
|
196
|
-
// If still too long, just truncate it
|
|
197
|
-
if (body.length > availableBodyLength) {
|
|
198
|
-
body = body.substring(0, availableBodyLength)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const url = `${titlePart}${body}`
|
|
202
|
-
|
|
203
|
-
// Open in browser
|
|
204
|
-
const openCmd =
|
|
205
|
-
process.platform === 'darwin'
|
|
206
|
-
? 'open'
|
|
207
|
-
: process.platform === 'win32'
|
|
208
|
-
? 'start'
|
|
209
|
-
: 'xdg-open'
|
|
210
|
-
|
|
211
|
-
exec(`${openCmd} "${url}"`, (err) => {
|
|
212
|
-
if (err) {
|
|
213
|
-
logger.error('Failed to open browser:', err)
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
}
|
|
217
|
-
|
|
218
92
|
render(): any {
|
|
219
93
|
if (this.state.hasError) {
|
|
220
|
-
return
|
|
221
|
-
<ErrorDisplay
|
|
222
|
-
error={this.state.error}
|
|
223
|
-
onOpenIssue={this.openGitHubIssue}
|
|
224
|
-
getRecentLogs={this.getRecentLogs}
|
|
225
|
-
/>
|
|
226
|
-
)
|
|
94
|
+
return <ErrorDisplay error={this.state.error} />
|
|
227
95
|
}
|
|
228
96
|
|
|
229
97
|
return this.props.children
|
|
230
98
|
}
|
|
231
99
|
}
|
|
232
100
|
|
|
233
|
-
function ErrorDisplay({
|
|
234
|
-
error,
|
|
235
|
-
onOpenIssue,
|
|
236
|
-
getRecentLogs,
|
|
237
|
-
}: {
|
|
238
|
-
error: Error | null
|
|
239
|
-
onOpenIssue: () => void
|
|
240
|
-
getRecentLogs: () => string
|
|
241
|
-
}): any {
|
|
242
|
-
const [isHovered, setIsHovered] = React.useState(false)
|
|
243
|
-
const [showLogs, setShowLogs] = React.useState(false)
|
|
244
|
-
const [focusedIndex, setFocusedIndex] = React.useState(0) // 0 = issue button, 1+ = stack trace lines
|
|
245
|
-
|
|
246
|
-
// Parse stack trace to get file paths with line numbers
|
|
247
|
-
const stackLines = React.useMemo(() => {
|
|
248
|
-
if (!error?.stack) return []
|
|
249
|
-
|
|
250
|
-
const lines = error.stack.split('\n')
|
|
251
|
-
const parsedLines: Array<{ text: string; file?: string; line?: number }> =
|
|
252
|
-
[]
|
|
253
|
-
|
|
254
|
-
for (const line of lines) {
|
|
255
|
-
// Match file paths with line:column format
|
|
256
|
-
// Common patterns: "at file:///path/to/file.ts:123:45" or "at /path/to/file.ts:123:45"
|
|
257
|
-
const match = line.match(
|
|
258
|
-
/at\s+(?:.*?\s+\()?(?:file:\/\/)?([^:\s]+(?:\.tsx?|\.jsx?)):(\d+)(?::\d+)?/,
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
if (match) {
|
|
262
|
-
parsedLines.push({
|
|
263
|
-
text: line,
|
|
264
|
-
file: match[1],
|
|
265
|
-
line: parseInt(match[2], 10),
|
|
266
|
-
})
|
|
267
|
-
} else {
|
|
268
|
-
parsedLines.push({ text: line })
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return parsedLines
|
|
273
|
-
}, [error?.stack])
|
|
274
|
-
|
|
275
|
-
// Count of focusable stack lines (those with file paths)
|
|
276
|
-
const focusableStackLines = stackLines.filter((line) => line.file).length
|
|
277
|
-
|
|
278
|
-
useKeyboard(async (evt) => {
|
|
279
|
-
if (evt.name === 'down') {
|
|
280
|
-
setFocusedIndex((prev) => {
|
|
281
|
-
const maxIndex = focusableStackLines // 0 for button + focusable stack lines
|
|
282
|
-
return Math.min(prev + 1, maxIndex)
|
|
283
|
-
})
|
|
284
|
-
} else if (evt.name === 'up') {
|
|
285
|
-
setFocusedIndex((prev) => Math.max(prev - 1, 0))
|
|
286
|
-
} else if (evt.name === 'return') {
|
|
287
|
-
if (focusedIndex === 0) {
|
|
288
|
-
// Issue button is focused
|
|
289
|
-
onOpenIssue()
|
|
290
|
-
} else {
|
|
291
|
-
// Stack trace line is focused - copy file path to clipboard
|
|
292
|
-
let currentFocusableIndex = 0
|
|
293
|
-
for (const line of stackLines) {
|
|
294
|
-
if (line.file) {
|
|
295
|
-
currentFocusableIndex++
|
|
296
|
-
if (currentFocusableIndex === focusedIndex) {
|
|
297
|
-
const filePathWithLine = `${line.file}:${line.line}`
|
|
298
|
-
const { Clipboard } = await import(
|
|
299
|
-
'termcast/src/apis/clipboard'
|
|
300
|
-
)
|
|
301
|
-
await Clipboard.copy(filePathWithLine)
|
|
302
|
-
logger.log(`📋 Copied to clipboard: ${filePathWithLine}`)
|
|
303
|
-
break
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
})
|
|
310
|
-
|
|
101
|
+
function ErrorDisplay({ error }: { error: Error | null }): any {
|
|
311
102
|
return (
|
|
312
|
-
<box padding={2}
|
|
313
|
-
<text fg={Theme.error}
|
|
314
|
-
⚠️ An error occurred
|
|
315
|
-
</text>
|
|
316
|
-
|
|
317
|
-
<text fg={Theme.error}>
|
|
318
|
-
{error?.message || 'An unexpected error occurred'}
|
|
319
|
-
</text>
|
|
320
|
-
|
|
321
|
-
<box
|
|
322
|
-
paddingLeft={1}
|
|
323
|
-
paddingRight={1}
|
|
324
|
-
borderStyle='rounded'
|
|
325
|
-
border={true}
|
|
326
|
-
borderColor={
|
|
327
|
-
focusedIndex === 0
|
|
328
|
-
? Theme.highlight
|
|
329
|
-
: isHovered
|
|
330
|
-
? Theme.highlight
|
|
331
|
-
: Theme.border
|
|
332
|
-
}
|
|
333
|
-
backgroundColor={
|
|
334
|
-
focusedIndex === 0
|
|
335
|
-
? Theme.backgroundPanel
|
|
336
|
-
: isHovered
|
|
337
|
-
? Theme.backgroundPanel
|
|
338
|
-
: undefined
|
|
339
|
-
}
|
|
340
|
-
onMouseMove={() => setIsHovered(true)}
|
|
341
|
-
onMouseOut={() => setIsHovered(false)}
|
|
342
|
-
onMouseDown={onOpenIssue}
|
|
343
|
-
marginTop={1}
|
|
344
|
-
>
|
|
345
|
-
<text>
|
|
346
|
-
{focusedIndex === 0 ? '▶ ' : ' '}📝 Press Enter or click to report
|
|
347
|
-
on GitHub
|
|
348
|
-
</text>
|
|
349
|
-
</box>
|
|
350
|
-
|
|
351
|
-
<box flexDirection='column' marginTop={1}>
|
|
352
|
-
<text fg={Theme.textMuted} attributes={TextAttributes.BOLD}>
|
|
353
|
-
Stack Trace:
|
|
354
|
-
</text>
|
|
355
|
-
{stackLines.map((line, index) => {
|
|
356
|
-
let currentFocusableIndex = 0
|
|
357
|
-
let isFocused = false
|
|
358
|
-
|
|
359
|
-
// Determine if this line is focused
|
|
360
|
-
if (line.file) {
|
|
361
|
-
currentFocusableIndex = stackLines
|
|
362
|
-
.slice(0, index + 1)
|
|
363
|
-
.filter((l) => l.file).length
|
|
364
|
-
isFocused = focusedIndex === currentFocusableIndex
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return (
|
|
368
|
-
<box
|
|
369
|
-
key={index}
|
|
370
|
-
backgroundColor={isFocused ? Theme.backgroundPanel : undefined}
|
|
371
|
-
>
|
|
372
|
-
<text fg={isFocused ? Theme.highlight : Theme.error}>
|
|
373
|
-
{isFocused ? '▶ ' : ' '}
|
|
374
|
-
{line.text}
|
|
375
|
-
</text>
|
|
376
|
-
</box>
|
|
377
|
-
)
|
|
378
|
-
})}
|
|
379
|
-
</box>
|
|
380
|
-
|
|
381
|
-
<box marginTop={1} onMouseDown={() => setShowLogs(!showLogs)}>
|
|
382
|
-
<text fg={Theme.textMuted} attributes={TextAttributes.UNDERLINE}>
|
|
383
|
-
{showLogs ? '▼' : '▶'} Toggle logs (last 200 lines)
|
|
384
|
-
</text>
|
|
385
|
-
</box>
|
|
386
|
-
|
|
387
|
-
{showLogs && (
|
|
388
|
-
<box flexDirection='column' marginTop={1}>
|
|
389
|
-
<text fg={Theme.textMuted} attributes={TextAttributes.BOLD}>
|
|
390
|
-
Recent Logs:
|
|
391
|
-
</text>
|
|
392
|
-
<box
|
|
393
|
-
padding={1}
|
|
394
|
-
borderStyle='single'
|
|
395
|
-
border={true}
|
|
396
|
-
borderColor={Theme.border}
|
|
397
|
-
maxHeight={20}
|
|
398
|
-
>
|
|
399
|
-
<text fg={Theme.textMuted}>{getRecentLogs()}</text>
|
|
400
|
-
</box>
|
|
401
|
-
</box>
|
|
402
|
-
)}
|
|
103
|
+
<box padding={2}>
|
|
104
|
+
<text fg={Theme.error} wrapMode='none'>{error?.stack}</text>
|
|
403
105
|
</box>
|
|
404
106
|
)
|
|
405
107
|
}
|
|
@@ -407,6 +109,8 @@ function ErrorDisplay({
|
|
|
407
109
|
const ErrorBoundary = ErrorBoundaryClass as any
|
|
408
110
|
|
|
409
111
|
export function TermcastProvider(props: ProvidersProps): any {
|
|
112
|
+
|
|
113
|
+
|
|
410
114
|
return (
|
|
411
115
|
<ErrorBoundary>
|
|
412
116
|
<Suspense fallback={<LoadingFallback />}>
|
|
@@ -417,14 +121,22 @@ export function TermcastProvider(props: ProvidersProps): any {
|
|
|
417
121
|
maxAge: 1000 * 60 * 60 * 24, // 24 hours
|
|
418
122
|
}}
|
|
419
123
|
>
|
|
420
|
-
<
|
|
124
|
+
<box
|
|
125
|
+
minHeight={'100%'}
|
|
126
|
+
justifyContent='flex-start'
|
|
127
|
+
backgroundColor={Theme.background}
|
|
128
|
+
// borderColor={Theme.border}
|
|
129
|
+
// fg={Theme.text}
|
|
130
|
+
>
|
|
421
131
|
<box padding={2}>
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
{
|
|
425
|
-
|
|
132
|
+
<DialogProvider>
|
|
133
|
+
{/* NavigationProvider must be last to ensure parent providers remain in the tree when navigation changes */}
|
|
134
|
+
<NavigationProvider overlay={<DialogOverlay />}>
|
|
135
|
+
<box>{props.children}</box>
|
|
136
|
+
</NavigationProvider>
|
|
137
|
+
</DialogProvider>
|
|
426
138
|
</box>
|
|
427
|
-
</
|
|
139
|
+
</box>
|
|
428
140
|
</PersistQueryClientProvider>
|
|
429
141
|
</Suspense>
|
|
430
142
|
</ErrorBoundary>
|