termcast 1.3.32 → 1.3.34
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/action-utils.d.ts.map +1 -1
- package/dist/action-utils.js +8 -0
- package/dist/action-utils.js.map +1 -1
- package/dist/apis/cache.d.ts +1 -2
- package/dist/apis/cache.d.ts.map +1 -1
- package/dist/apis/cache.js +138 -54
- package/dist/apis/cache.js.map +1 -1
- package/dist/apis/clipboard.d.ts.map +1 -1
- package/dist/apis/clipboard.js +4 -0
- package/dist/apis/clipboard.js.map +1 -1
- package/dist/apis/oauth.d.ts.map +1 -1
- package/dist/apis/oauth.js +31 -4
- package/dist/apis/oauth.js.map +1 -1
- package/dist/build.d.ts +0 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +30 -51
- package/dist/build.js.map +1 -1
- package/dist/cli.js +31 -14
- package/dist/cli.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +5 -1
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.d.ts +14 -0
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +151 -59
- package/dist/components/actions.js.map +1 -1
- package/dist/components/alert.d.ts.map +1 -1
- package/dist/components/alert.js +6 -5
- package/dist/components/alert.js.map +1 -1
- package/dist/components/animation-tick.d.ts +1 -1
- package/dist/components/animation-tick.js +1 -1
- package/dist/components/animation-tick.js.map +1 -1
- package/dist/components/detail.d.ts +5 -31
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +36 -52
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts +1 -1
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +50 -22
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/footer.d.ts.map +1 -1
- package/dist/components/footer.js +19 -18
- package/dist/components/footer.js.map +1 -1
- package/dist/components/form/checkbox.d.ts.map +1 -1
- package/dist/components/form/checkbox.js +12 -11
- package/dist/components/form/checkbox.js.map +1 -1
- package/dist/components/form/date-picker.d.ts.map +1 -1
- package/dist/components/form/date-picker.js +7 -22
- package/dist/components/form/date-picker.js.map +1 -1
- package/dist/components/form/description.d.ts +1 -1
- package/dist/components/form/description.d.ts.map +1 -1
- package/dist/components/form/description.js +6 -5
- package/dist/components/form/description.js.map +1 -1
- package/dist/components/form/dropdown.d.ts.map +1 -1
- package/dist/components/form/dropdown.js +53 -50
- package/dist/components/form/dropdown.js.map +1 -1
- package/dist/components/form/file-autocomplete.d.ts.map +1 -1
- package/dist/components/form/file-autocomplete.js +5 -4
- 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 +23 -22
- package/dist/components/form/file-picker.js.map +1 -1
- package/dist/components/form/form-end.d.ts.map +1 -1
- package/dist/components/form/form-end.js +6 -4
- package/dist/components/form/form-end.js.map +1 -1
- package/dist/components/form/form-field-wrapper.d.ts +15 -0
- package/dist/components/form/form-field-wrapper.d.ts.map +1 -0
- package/dist/components/form/form-field-wrapper.js +29 -0
- package/dist/components/form/form-field-wrapper.js.map +1 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +31 -30
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/password-field.d.ts.map +1 -1
- package/dist/components/form/password-field.js +7 -6
- package/dist/components/form/password-field.js.map +1 -1
- package/dist/components/form/separator.d.ts.map +1 -1
- package/dist/components/form/separator.js +3 -2
- package/dist/components/form/separator.js.map +1 -1
- package/dist/components/form/tagpicker.d.ts.map +1 -1
- package/dist/components/form/tagpicker.js +2 -1
- package/dist/components/form/tagpicker.js.map +1 -1
- package/dist/components/form/text-area.d.ts.map +1 -1
- package/dist/components/form/text-area.js +7 -6
- package/dist/components/form/text-area.js.map +1 -1
- package/dist/components/form/text-field.d.ts.map +1 -1
- package/dist/components/form/text-field.js +7 -6
- package/dist/components/form/text-field.js.map +1 -1
- package/dist/components/form/use-form-navigation.d.ts.map +1 -1
- package/dist/components/form/use-form-navigation.js +4 -4
- package/dist/components/form/use-form-navigation.js.map +1 -1
- package/dist/components/form/with-left-border.d.ts +15 -0
- package/dist/components/form/with-left-border.d.ts.map +1 -1
- package/dist/components/form/with-left-border.js +21 -9
- package/dist/components/form/with-left-border.js.map +1 -1
- package/dist/components/icon.d.ts +14 -0
- package/dist/components/icon.d.ts.map +1 -1
- package/dist/components/icon.js +60 -0
- package/dist/components/icon.js.map +1 -1
- package/dist/components/image.d.ts +47 -2
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/image.js +46 -7
- package/dist/components/image.js.map +1 -1
- package/dist/components/list.d.ts +5 -0
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +188 -132
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.d.ts.map +1 -1
- package/dist/components/loading-bar.js +4 -3
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/metadata.d.ts +70 -0
- package/dist/components/metadata.d.ts.map +1 -0
- package/dist/components/metadata.js +82 -0
- package/dist/components/metadata.js.map +1 -0
- package/dist/components/theme-picker.d.ts.map +1 -1
- package/dist/components/theme-picker.js +3 -2
- package/dist/components/theme-picker.js.map +1 -1
- package/dist/descendants-v2.d.ts +60 -0
- package/dist/descendants-v2.d.ts.map +1 -0
- package/dist/descendants-v2.js +144 -0
- package/dist/descendants-v2.js.map +1 -0
- package/dist/examples/actions-context.d.ts +2 -0
- package/dist/examples/actions-context.d.ts.map +1 -0
- package/dist/examples/actions-context.js +33 -0
- package/dist/examples/actions-context.js.map +1 -0
- package/dist/examples/form-basic.d.ts.map +1 -1
- package/dist/examples/form-basic.js +1 -1
- package/dist/examples/form-basic.js.map +1 -1
- package/dist/examples/form-dropdown.js +1 -1
- package/dist/examples/form-dropdown.js.map +1 -1
- package/dist/examples/internal/custom-action-renderables.d.ts +70 -0
- package/dist/examples/internal/custom-action-renderables.d.ts.map +1 -0
- package/dist/examples/internal/custom-action-renderables.js +163 -0
- package/dist/examples/internal/custom-action-renderables.js.map +1 -0
- package/dist/examples/internal/custom-dropdown.d.ts +99 -0
- package/dist/examples/internal/custom-dropdown.d.ts.map +1 -0
- package/dist/examples/internal/custom-dropdown.js +270 -0
- package/dist/examples/internal/custom-dropdown.js.map +1 -0
- package/dist/examples/internal/custom-renderable-form.d.ts +43 -0
- package/dist/examples/internal/custom-renderable-form.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-form.js +284 -0
- package/dist/examples/internal/custom-renderable-form.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list-default-search.d.ts +2 -0
- package/dist/examples/internal/custom-renderable-list-default-search.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list-default-search.js +16 -0
- package/dist/examples/internal/custom-renderable-list-default-search.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.d.ts +2 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.js +24 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2.d.ts +189 -0
- package/dist/examples/internal/custom-renderable-list-v2.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2.js +708 -0
- package/dist/examples/internal/custom-renderable-list-v2.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list.d.ts +72 -0
- package/dist/examples/internal/custom-renderable-list.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list.js +544 -0
- package/dist/examples/internal/custom-renderable-list.js.map +1 -0
- package/dist/examples/internal/rhf-custom-ref.js +5 -4
- package/dist/examples/internal/rhf-custom-ref.js.map +1 -1
- package/dist/examples/internal/scrollbox-with-descendants.js +4 -2
- package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
- package/dist/examples/list-controlled-search.d.ts +2 -0
- package/dist/examples/list-controlled-search.d.ts.map +1 -0
- package/dist/examples/list-controlled-search.js +12 -0
- package/dist/examples/list-controlled-search.js.map +1 -0
- package/dist/examples/list-detail-metadata.js +1 -1
- package/dist/examples/list-detail-metadata.js.map +1 -1
- package/dist/examples/simple-image-mask.d.ts +8 -0
- package/dist/examples/simple-image-mask.d.ts.map +1 -0
- package/dist/examples/simple-image-mask.js +12 -0
- package/dist/examples/simple-image-mask.js.map +1 -0
- package/dist/examples/toast-variations.js +1 -1
- package/dist/examples/toast-variations.js.map +1 -1
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +3 -2
- package/dist/extensions/dev.js.map +1 -1
- package/dist/extensions/react-refresh-init.d.ts.map +1 -1
- package/dist/extensions/react-refresh-init.js +4 -3
- package/dist/extensions/react-refresh-init.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/date-picker-widget.d.ts.map +1 -1
- package/dist/internal/date-picker-widget.js +2 -1
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/dialog.d.ts +6 -0
- package/dist/internal/dialog.d.ts.map +1 -1
- package/dist/internal/dialog.js +59 -18
- package/dist/internal/dialog.js.map +1 -1
- package/dist/internal/navigation.d.ts.map +1 -1
- package/dist/internal/navigation.js +8 -1
- package/dist/internal/navigation.js.map +1 -1
- package/dist/internal/offscreen.d.ts +3 -0
- package/dist/internal/offscreen.d.ts.map +1 -1
- package/dist/internal/offscreen.js +5 -0
- package/dist/internal/offscreen.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +20 -3
- package/dist/internal/providers.js.map +1 -1
- package/dist/internal/scrollbox.d.ts.map +1 -1
- package/dist/internal/scrollbox.js +3 -2
- package/dist/internal/scrollbox.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/preload.js +5 -17
- package/dist/preload.js.map +1 -1
- package/dist/state.d.ts +4 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +4 -0
- package/dist/state.js.map +1 -1
- package/dist/test-border-overlay.d.ts +2 -0
- package/dist/test-border-overlay.d.ts.map +1 -0
- package/dist/test-border-overlay.js +7 -0
- package/dist/test-border-overlay.js.map +1 -0
- package/dist/test-layout-2.d.ts +2 -0
- package/dist/test-layout-2.d.ts.map +1 -0
- package/dist/test-layout-2.js +5 -0
- package/dist/test-layout-2.js.map +1 -0
- package/dist/test-layout.d.ts +2 -0
- package/dist/test-layout.d.ts.map +1 -0
- package/dist/test-layout.js +7 -0
- package/dist/test-layout.js.map +1 -0
- package/dist/theme.d.ts +1 -2
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +5 -9
- package/dist/theme.js.map +1 -1
- package/dist/utils/run-command.d.ts +1 -1
- package/dist/utils/run-command.d.ts.map +1 -1
- package/dist/utils/run-command.js +27 -7
- package/dist/utils/run-command.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +44 -23
- package/dist/utils.js.map +1 -1
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +24 -4
- package/dist/watcher.js.map +1 -1
- package/package.json +14 -12
- package/src/action-utils.tsx +10 -0
- package/src/apis/cache.test.ts +35 -3
- package/src/apis/cache.tsx +184 -59
- package/src/apis/clipboard.tsx +5 -0
- package/src/apis/oauth.tsx +33 -4
- package/src/build.tsx +35 -58
- package/src/cli.tsx +156 -134
- package/src/compile.tsx +6 -3
- package/src/compile.vitest.tsx +33 -15
- package/src/components/actions.tsx +230 -99
- package/src/components/alert.tsx +11 -10
- package/src/components/animation-tick.tsx +1 -1
- package/src/components/detail.tsx +56 -151
- package/src/components/dropdown.tsx +70 -36
- package/src/components/footer.tsx +58 -33
- package/src/components/form/checkbox.tsx +30 -32
- package/src/components/form/date-picker.tsx +27 -47
- package/src/components/form/description.tsx +19 -18
- package/src/components/form/dropdown.tsx +95 -103
- package/src/components/form/file-autocomplete.tsx +9 -8
- package/src/components/form/file-picker.tsx +46 -46
- package/src/components/form/form-end.tsx +6 -4
- package/src/components/form/index.tsx +38 -48
- package/src/components/form/password-field.tsx +25 -27
- package/src/components/form/separator.tsx +3 -2
- package/src/components/form/tagpicker.tsx +2 -1
- package/src/components/form/text-area.tsx +25 -30
- package/src/components/form/text-field.tsx +25 -27
- package/src/components/form/use-form-navigation.tsx +4 -5
- package/src/components/form/with-left-border.tsx +48 -10
- package/src/components/icon.tsx +69 -0
- package/src/components/image.tsx +60 -7
- package/src/components/list.tsx +270 -202
- package/src/components/loading-bar.tsx +4 -3
- package/src/components/metadata.tsx +217 -0
- package/src/components/theme-picker.tsx +3 -2
- package/src/examples/actions-context.tsx +63 -0
- package/src/examples/actions-context.vitest.tsx +110 -0
- package/src/examples/actions-dialog-layout.vitest.tsx +2 -1
- package/src/examples/file-autocomplete.vitest.tsx +15 -15
- package/src/examples/form-basic.tsx +12 -0
- package/src/examples/form-basic.vitest.tsx +74 -74
- package/src/examples/form-dropdown.tsx +8 -0
- package/src/examples/form-dropdown.vitest.tsx +364 -421
- package/src/examples/form-tagpicker.vitest.tsx +56 -54
- package/src/examples/github.vitest.tsx +252 -0
- package/src/examples/internal/rhf-custom-ref.tsx +16 -15
- package/src/examples/internal/scrollbox-with-descendants.tsx +4 -2
- package/src/examples/internal/simple-dialog.tsx +1 -1
- package/src/examples/internal/simple-scrollbox.vitest.tsx +14 -9
- package/src/examples/list-controlled-search.tsx +28 -0
- package/src/examples/list-controlled-search.vitest.tsx +49 -0
- package/src/examples/list-detail-metadata.tsx +8 -5
- package/src/examples/list-detail-metadata.vitest.tsx +22 -22
- package/src/examples/list-dropdown-default.vitest.tsx +12 -12
- package/src/examples/list-scrollbox.vitest.tsx +52 -38
- package/src/examples/list-with-detail.vitest.tsx +45 -41
- package/src/examples/list-with-dropdown.vitest.tsx +5 -5
- package/src/examples/list-with-sections.vitest.tsx +65 -12
- package/src/examples/list-with-toast.vitest.tsx +4 -4
- package/src/examples/simple-file-picker.vitest.tsx +12 -12
- package/src/examples/simple-grid.vitest.tsx +53 -53
- package/src/examples/simple-image-mask.tsx +58 -0
- package/src/examples/simple-navigation.vitest.tsx +19 -19
- package/src/examples/store.vitest.tsx +1 -1
- package/src/examples/swift-extension.vitest.tsx +4 -2
- package/src/examples/synonyms.vitest.tsx +31 -9
- package/src/examples/toast-action.vitest.tsx +8 -8
- package/src/examples/toast-variations.tsx +1 -1
- package/src/examples/toast-variations.vitest.tsx +69 -134
- package/src/extensions/dev.tsx +3 -2
- package/src/extensions/dev.vitest.tsx +65 -28
- package/src/extensions/react-refresh-init.tsx +4 -3
- package/src/index.tsx +3 -1
- package/src/internal/date-picker-widget.tsx +2 -1
- package/src/internal/dialog.tsx +100 -28
- package/src/internal/navigation.tsx +8 -1
- package/src/internal/offscreen.tsx +10 -0
- package/src/internal/providers.tsx +34 -8
- package/src/internal/scrollbox.tsx +4 -2
- package/src/logger.tsx +4 -0
- package/src/preload.tsx +5 -17
- package/src/state.tsx +12 -0
- package/src/theme.tsx +6 -9
- package/src/utils/run-command.tsx +32 -8
- package/src/utils.tsx +58 -23
- package/src/watcher.tsx +26 -6
package/src/index.tsx
CHANGED
|
@@ -97,11 +97,13 @@ export type {
|
|
|
97
97
|
// Icons and Images
|
|
98
98
|
export { Icon, getIconEmoji, getIconShape, IconComponent } from 'termcast/src/components/icon'
|
|
99
99
|
export { Image, ImageMask } from 'termcast/src/components/image'
|
|
100
|
+
export type { ImageType } from 'termcast/src/components/image'
|
|
100
101
|
export type {
|
|
101
102
|
ImageProps,
|
|
102
103
|
ImageSource,
|
|
103
104
|
FileIcon,
|
|
104
105
|
ImageLike,
|
|
106
|
+
ImageFallback,
|
|
105
107
|
} from 'termcast/src/components/image'
|
|
106
108
|
|
|
107
109
|
// Alerts
|
|
@@ -208,7 +210,7 @@ export type { PreferenceValues } from 'termcast/src/apis/preferences'
|
|
|
208
210
|
export type { CommonProps } from 'termcast/src/utils'
|
|
209
211
|
|
|
210
212
|
// Theme
|
|
211
|
-
export {
|
|
213
|
+
export { useTheme } from 'termcast/src/theme'
|
|
212
214
|
|
|
213
215
|
// Logger
|
|
214
216
|
export { logger } from 'termcast/src/logger'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useKeyboard } from '@opentui/react'
|
|
2
2
|
import { useMemo, useState, useRef } from 'react'
|
|
3
|
-
import
|
|
3
|
+
import { useTheme } from '../theme'
|
|
4
4
|
|
|
5
5
|
// ----- Helpers -----
|
|
6
6
|
type Focus = 'year' | 'month' | 'grid'
|
|
@@ -76,6 +76,7 @@ export function DatePickerWidget({
|
|
|
76
76
|
onFirstRowUpKey?: () => void
|
|
77
77
|
onLastRowDownKey?: () => void
|
|
78
78
|
}) {
|
|
79
|
+
const Theme = useTheme()
|
|
79
80
|
const today = useMemo(() => new Date(), [])
|
|
80
81
|
const [focus, setFocus] = useState<Focus>('grid') // can be "year" | "month" | "grid"
|
|
81
82
|
const [selected, setSelected] = useState<Date>(initialValue || new Date()) // focused day
|
package/src/internal/dialog.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useKeyboard } from '@opentui/react'
|
|
2
|
-
import React, { type ReactNode, useRef, useContext } from 'react'
|
|
3
|
-
import {
|
|
2
|
+
import React, { type ReactNode, useRef, useContext, useCallback } from 'react'
|
|
3
|
+
import { useTheme } from 'termcast/src/theme'
|
|
4
4
|
import { InFocus, useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
5
5
|
import { CommonProps } from 'termcast/src/utils'
|
|
6
6
|
import { useStore, type DialogPosition } from 'termcast/src/state'
|
|
@@ -20,6 +20,7 @@ export function Dialog({
|
|
|
20
20
|
position = 'center',
|
|
21
21
|
onClickOutside,
|
|
22
22
|
}: DialogProps): any {
|
|
23
|
+
const theme = useTheme()
|
|
23
24
|
const inFocus = useIsInFocus()
|
|
24
25
|
const clickedInsideDialog = useRef(false)
|
|
25
26
|
|
|
@@ -69,7 +70,7 @@ export function Dialog({
|
|
|
69
70
|
alignItems={positionStyles.alignItems}
|
|
70
71
|
justifyContent={positionStyles.justifyContent}
|
|
71
72
|
padding={positionStyles.padding}
|
|
72
|
-
// backgroundColor={
|
|
73
|
+
// backgroundColor={theme.background}
|
|
73
74
|
onMouseDown={handleBackdropClick}
|
|
74
75
|
>
|
|
75
76
|
<box
|
|
@@ -77,8 +78,8 @@ export function Dialog({
|
|
|
77
78
|
borderStyle='rounded'
|
|
78
79
|
width={76}
|
|
79
80
|
maxWidth='95%'
|
|
80
|
-
backgroundColor={
|
|
81
|
-
borderColor={
|
|
81
|
+
backgroundColor={theme.backgroundPanel}
|
|
82
|
+
borderColor={theme.accent}
|
|
82
83
|
paddingTop={1}
|
|
83
84
|
onMouseDown={handleDialogClick}
|
|
84
85
|
>
|
|
@@ -94,13 +95,34 @@ interface DialogProviderProps {
|
|
|
94
95
|
|
|
95
96
|
export function DialogProvider(props: DialogProviderProps): any {
|
|
96
97
|
const dialogStack = useStore((state) => state.dialogStack)
|
|
98
|
+
const showActionsDialog = useStore((state) => state.showActionsDialog)
|
|
97
99
|
const inFocus = useIsInFocus()
|
|
98
100
|
|
|
99
101
|
useKeyboard((evt) => {
|
|
100
102
|
if (!inFocus) return
|
|
101
103
|
if (evt.name === 'escape') {
|
|
102
104
|
const state = useStore.getState()
|
|
105
|
+
|
|
106
|
+
// Handle actions dialog first
|
|
107
|
+
if (state.showActionsDialog) {
|
|
108
|
+
// Check if there's a search input with text that should be cleared first
|
|
109
|
+
const activeSearchInputRef = state.activeSearchInputRef
|
|
110
|
+
if (activeSearchInputRef && activeSearchInputRef.plainText) {
|
|
111
|
+
activeSearchInputRef.setText('')
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
useStore.setState({ showActionsDialog: false })
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
if (state.dialogStack.length > 0) {
|
|
119
|
+
// Check if there's a search input with text that should be cleared first
|
|
120
|
+
const activeSearchInputRef = state.activeSearchInputRef
|
|
121
|
+
if (activeSearchInputRef && activeSearchInputRef.plainText) {
|
|
122
|
+
// Clear the search text instead of closing dialog
|
|
123
|
+
activeSearchInputRef.setText('')
|
|
124
|
+
return
|
|
125
|
+
}
|
|
104
126
|
useStore.setState({
|
|
105
127
|
dialogStack: state.dialogStack.slice(0, -1),
|
|
106
128
|
})
|
|
@@ -108,8 +130,8 @@ export function DialogProvider(props: DialogProviderProps): any {
|
|
|
108
130
|
}
|
|
109
131
|
})
|
|
110
132
|
|
|
111
|
-
// Children lose focus only when there's a dialog
|
|
112
|
-
const childrenInFocus = !dialogStack?.length
|
|
133
|
+
// Children lose focus only when there's a dialog or actions dialog
|
|
134
|
+
const childrenInFocus = !dialogStack?.length && !showActionsDialog
|
|
113
135
|
|
|
114
136
|
return (
|
|
115
137
|
<>
|
|
@@ -119,38 +141,88 @@ export function DialogProvider(props: DialogProviderProps): any {
|
|
|
119
141
|
)
|
|
120
142
|
}
|
|
121
143
|
|
|
144
|
+
/**
|
|
145
|
+
* DialogOverlay renders dialog stack items and provides a portal target for
|
|
146
|
+
* ActionPanel. The portal target is always mounted so ActionPanel can use
|
|
147
|
+
* createPortal to render its Dropdown here while keeping its React context
|
|
148
|
+
* (FormSubmitContext, NavigationContext, etc.) from the original tree.
|
|
149
|
+
*/
|
|
122
150
|
export function DialogOverlay(): any {
|
|
123
151
|
const dialogStack = useStore((state) => state.dialogStack)
|
|
152
|
+
const showActionsDialog = useStore((state) => state.showActionsDialog)
|
|
124
153
|
const navContext = useContext(NavigationContext)
|
|
154
|
+
const theme = useTheme()
|
|
155
|
+
|
|
156
|
+
const setActionsPortalTargetRef = useCallback((node: any) => {
|
|
157
|
+
if (!node) {
|
|
158
|
+
useStore.setState({ actionsPortalTarget: null })
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
useStore.setState({ actionsPortalTarget: node })
|
|
125
163
|
|
|
126
|
-
|
|
164
|
+
return () => {
|
|
165
|
+
if (useStore.getState().actionsPortalTarget === node) {
|
|
166
|
+
useStore.setState({ actionsPortalTarget: null })
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}, [])
|
|
170
|
+
|
|
171
|
+
if (dialogStack.length === 0 && !showActionsDialog) {
|
|
127
172
|
return null
|
|
128
173
|
}
|
|
129
174
|
|
|
130
|
-
// Only render the topmost dialog
|
|
131
175
|
const topIndex = dialogStack.length - 1
|
|
132
|
-
const item = dialogStack[topIndex]
|
|
176
|
+
const item = topIndex >= 0 ? dialogStack[topIndex] : undefined
|
|
133
177
|
|
|
134
178
|
return (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
position=
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
dialogStack: state.dialogStack.slice(0, -1),
|
|
144
|
-
})
|
|
145
|
-
}
|
|
146
|
-
}}
|
|
179
|
+
<>
|
|
180
|
+
{showActionsDialog && (
|
|
181
|
+
<box
|
|
182
|
+
position='absolute'
|
|
183
|
+
width='100%'
|
|
184
|
+
height='100%'
|
|
185
|
+
flexDirection='column'
|
|
186
|
+
backgroundColor={theme.background}
|
|
147
187
|
>
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
188
|
+
<InFocus inFocus={true}>
|
|
189
|
+
<Dialog
|
|
190
|
+
position='center'
|
|
191
|
+
onClickOutside={() => {
|
|
192
|
+
useStore.setState({ showActionsDialog: false })
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
<box
|
|
196
|
+
ref={setActionsPortalTargetRef}
|
|
197
|
+
flexDirection='column'
|
|
198
|
+
flexGrow={1}
|
|
199
|
+
/>
|
|
200
|
+
</Dialog>
|
|
201
|
+
</InFocus>
|
|
202
|
+
</box>
|
|
203
|
+
)}
|
|
204
|
+
{item && (
|
|
205
|
+
<box position='absolute' width='100%' height='100%' flexDirection='column'>
|
|
206
|
+
<InFocus inFocus={true}>
|
|
207
|
+
<Dialog
|
|
208
|
+
position={item.position}
|
|
209
|
+
onClickOutside={() => {
|
|
210
|
+
const state = useStore.getState()
|
|
211
|
+
if (state.dialogStack.length > 0) {
|
|
212
|
+
useStore.setState({
|
|
213
|
+
dialogStack: state.dialogStack.slice(0, -1),
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
<NavigationContext.Provider value={navContext}>
|
|
219
|
+
{item.element}
|
|
220
|
+
</NavigationContext.Provider>
|
|
221
|
+
</Dialog>
|
|
222
|
+
</InFocus>
|
|
223
|
+
</box>
|
|
224
|
+
)}
|
|
225
|
+
</>
|
|
154
226
|
)
|
|
155
227
|
}
|
|
156
228
|
|
|
@@ -165,7 +165,14 @@ export function NavigationProvider(props: NavigationProviderProps): any {
|
|
|
165
165
|
)
|
|
166
166
|
pop()
|
|
167
167
|
} else {
|
|
168
|
-
// At root with
|
|
168
|
+
// At root - check if there's a search input with text that should be cleared first
|
|
169
|
+
const activeSearchInputRef = useStore.getState().activeSearchInputRef
|
|
170
|
+
if (activeSearchInputRef && activeSearchInputRef.plainText) {
|
|
171
|
+
// Clear the search text instead of exiting
|
|
172
|
+
activeSearchInputRef.setText('')
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
// At root with no dialogs and no search text - exit the CLI
|
|
169
176
|
renderer.destroy()
|
|
170
177
|
}
|
|
171
178
|
}
|
|
@@ -13,3 +13,13 @@ export function Offscreen({ children }: { children: ReactNode }): any {
|
|
|
13
13
|
</OffscreenContext.Provider>
|
|
14
14
|
)
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
// Resets the offscreen context to false. Used by portals that render content
|
|
18
|
+
// from an offscreen tree into a visible overlay area.
|
|
19
|
+
export function Onscreen({ children }: { children: ReactNode }): any {
|
|
20
|
+
return (
|
|
21
|
+
<OffscreenContext.Provider value={false}>
|
|
22
|
+
{children}
|
|
23
|
+
</OffscreenContext.Provider>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -8,15 +8,16 @@ import { QueryClient } from '@tanstack/react-query'
|
|
|
8
8
|
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
|
|
9
9
|
import { DialogProvider, DialogOverlay } from 'termcast/src/internal/dialog'
|
|
10
10
|
import { NavigationProvider } from 'termcast/src/internal/navigation'
|
|
11
|
-
import { CommonProps } from 'termcast/src/utils'
|
|
11
|
+
import { CommonProps, termcastMaxContentWidth } from 'termcast/src/utils'
|
|
12
12
|
import { Cache } from 'termcast/src/apis/cache'
|
|
13
13
|
import { logger } from 'termcast/src/logger'
|
|
14
|
-
import {
|
|
14
|
+
import { useTheme, initializeTheme } from 'termcast/src/theme'
|
|
15
15
|
import { useStore } from 'termcast/src/state'
|
|
16
|
-
import {
|
|
16
|
+
import { useKeyboard, useRenderer } from '@opentui/react'
|
|
17
17
|
import { initializeErrorHandlers } from 'termcast/src/internal/error-handler'
|
|
18
18
|
|
|
19
19
|
import { InFocus } from './focus-context'
|
|
20
|
+
import { Clipboard } from '../apis/clipboard'
|
|
20
21
|
|
|
21
22
|
// Initialize error handlers at module load time
|
|
22
23
|
initializeErrorHandlers()
|
|
@@ -99,9 +100,12 @@ class ErrorBoundaryClass extends Component<
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
function ErrorDisplay({ error }: { error: Error | null }): any {
|
|
103
|
+
const theme = useTheme()
|
|
102
104
|
return (
|
|
103
105
|
<box padding={2}>
|
|
104
|
-
<text fg={
|
|
106
|
+
<text fg={theme.error} wrapMode='none'>
|
|
107
|
+
{error?.stack}
|
|
108
|
+
</text>
|
|
105
109
|
</box>
|
|
106
110
|
)
|
|
107
111
|
}
|
|
@@ -109,7 +113,18 @@ function ErrorDisplay({ error }: { error: Error | null }): any {
|
|
|
109
113
|
const ErrorBoundary = ErrorBoundaryClass as any
|
|
110
114
|
|
|
111
115
|
export function TermcastProvider(props: ProvidersProps): any {
|
|
112
|
-
|
|
116
|
+
const theme = useTheme()
|
|
117
|
+
const renderer = useRenderer()
|
|
118
|
+
useKeyboard((key) => {
|
|
119
|
+
if (!renderer) return
|
|
120
|
+
if (key.ctrl && key.name === 'd') {
|
|
121
|
+
renderer.console.onCopySelection = (text: any) => {
|
|
122
|
+
Clipboard.copy(text)
|
|
123
|
+
}
|
|
124
|
+
renderer?.toggleDebugOverlay()
|
|
125
|
+
renderer?.console.toggle()
|
|
126
|
+
}
|
|
127
|
+
})
|
|
113
128
|
|
|
114
129
|
return (
|
|
115
130
|
<ErrorBoundary>
|
|
@@ -124,15 +139,26 @@ export function TermcastProvider(props: ProvidersProps): any {
|
|
|
124
139
|
<box
|
|
125
140
|
minHeight={'100%'}
|
|
126
141
|
justifyContent='flex-start'
|
|
127
|
-
backgroundColor={
|
|
142
|
+
backgroundColor={theme.background}
|
|
143
|
+
width='100%'
|
|
144
|
+
flexGrow={1}
|
|
145
|
+
alignItems='center'
|
|
128
146
|
// borderColor={Theme.border}
|
|
129
147
|
// fg={Theme.text}
|
|
130
148
|
>
|
|
131
|
-
<box
|
|
149
|
+
<box
|
|
150
|
+
padding={2}
|
|
151
|
+
width='100%'
|
|
152
|
+
maxWidth={termcastMaxContentWidth}
|
|
153
|
+
// flexShrink={1}
|
|
154
|
+
// flexGrow={1}
|
|
155
|
+
>
|
|
132
156
|
<DialogProvider>
|
|
133
157
|
{/* NavigationProvider must be last to ensure parent providers remain in the tree when navigation changes */}
|
|
134
158
|
<NavigationProvider overlay={<DialogOverlay />}>
|
|
135
|
-
<box
|
|
159
|
+
<box width='100%' flexGrow={1} flexShrink={1}>
|
|
160
|
+
{props.children}
|
|
161
|
+
</box>
|
|
136
162
|
</NavigationProvider>
|
|
137
163
|
</DialogProvider>
|
|
138
164
|
</box>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import
|
|
2
|
+
import { useTheme } from '../theme'
|
|
3
3
|
import { MacOSScrollAccel } from '@opentui/core'
|
|
4
4
|
import { ScrollBoxProps } from '@opentui/react'
|
|
5
5
|
|
|
@@ -13,6 +13,8 @@ export function ScrollBox({
|
|
|
13
13
|
ref,
|
|
14
14
|
...props
|
|
15
15
|
}: ScrollBoxProps): any {
|
|
16
|
+
const theme = useTheme()
|
|
17
|
+
|
|
16
18
|
return (
|
|
17
19
|
<scrollbox
|
|
18
20
|
ref={ref}
|
|
@@ -41,7 +43,7 @@ export function ScrollBox({
|
|
|
41
43
|
// visible: true,
|
|
42
44
|
// showArrows: true,
|
|
43
45
|
trackOptions: {
|
|
44
|
-
foregroundColor:
|
|
46
|
+
foregroundColor: theme.textMuted,
|
|
45
47
|
|
|
46
48
|
// backgroundColor: '#414868',
|
|
47
49
|
},
|
package/src/logger.tsx
CHANGED
|
@@ -25,18 +25,21 @@ export const logger = {
|
|
|
25
25
|
const formattedMessages = messages.map(serialize).join(' ')
|
|
26
26
|
const logEntry = `[${timestamp}] ${formattedMessages}\n`
|
|
27
27
|
fs.appendFileSync(LOG_FILE, logEntry)
|
|
28
|
+
console.log(...messages)
|
|
28
29
|
},
|
|
29
30
|
error: (...messages: any[]) => {
|
|
30
31
|
const timestamp = new Date().toISOString()
|
|
31
32
|
const formattedMessages = messages.map(serialize).join(' ')
|
|
32
33
|
const logEntry = `[${timestamp}] ERROR: ${formattedMessages}\n`
|
|
33
34
|
fs.appendFileSync(LOG_FILE, logEntry)
|
|
35
|
+
console.error(...messages)
|
|
34
36
|
},
|
|
35
37
|
warn: (...messages: any[]) => {
|
|
36
38
|
const timestamp = new Date().toISOString()
|
|
37
39
|
const formattedMessages = messages.map(serialize).join(' ')
|
|
38
40
|
const logEntry = `[${timestamp}] WARN: ${formattedMessages}\n`
|
|
39
41
|
fs.appendFileSync(LOG_FILE, logEntry)
|
|
42
|
+
console.warn(...messages)
|
|
40
43
|
},
|
|
41
44
|
trace: (...messages: any[]) => {
|
|
42
45
|
const timestamp = new Date().toISOString()
|
|
@@ -51,6 +54,7 @@ export const logger = {
|
|
|
51
54
|
const formattedMessages = messages.map(serialize).join(' ')
|
|
52
55
|
const logEntry = `[${timestamp}] TRACE: ${formattedMessages}\n${stack}\n`
|
|
53
56
|
fs.appendFileSync(LOG_FILE, logEntry)
|
|
57
|
+
console.trace(...messages)
|
|
54
58
|
},
|
|
55
59
|
}
|
|
56
60
|
|
package/src/preload.tsx
CHANGED
|
@@ -2,31 +2,19 @@ import { plugin } from 'bun'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { logger } from './logger'
|
|
4
4
|
|
|
5
|
+
// Path to our forked raycast-utils with termcast OAuth proxy URLs
|
|
6
|
+
const RAYCAST_UTILS_PATH = path.resolve(__dirname, '../../raycast-utils/src/index.ts')
|
|
7
|
+
|
|
5
8
|
plugin({
|
|
6
9
|
name: 'alias-raycast-to-termcast',
|
|
7
10
|
setup(build) {
|
|
8
|
-
|
|
11
|
+
// Redirect @raycast/api to termcast
|
|
12
|
+
build.onResolve({ filter: /^@raycast\/api$/ }, () => {
|
|
9
13
|
return {
|
|
10
14
|
path: require.resolve('termcast'),
|
|
11
15
|
}
|
|
12
16
|
})
|
|
13
|
-
// build.onResolve({ filter: /@raycast\/utils/ }, (args) => {
|
|
14
|
-
// return {
|
|
15
|
-
// path: require.resolve('@raycast/utils', {
|
|
16
|
-
// paths: [args.importer],
|
|
17
|
-
// }),
|
|
18
17
|
|
|
19
|
-
// }
|
|
20
|
-
// })
|
|
21
|
-
// build.onLoad({ filter: /@raycast\/utils/ }, (args) => {
|
|
22
|
-
// const filePath = require
|
|
23
|
-
// .resolve(args.path.replace('file:', ''))
|
|
24
|
-
// .replace('file:', '')
|
|
25
|
-
// return {
|
|
26
|
-
// contents: require('fs').readFileSync(filePath, 'utf8'),
|
|
27
18
|
|
|
28
|
-
// loader: 'js',
|
|
29
|
-
// }
|
|
30
|
-
// })
|
|
31
19
|
},
|
|
32
20
|
})
|
package/src/state.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { create } from 'zustand'
|
|
2
2
|
import { type ReactNode } from 'react'
|
|
3
|
+
import type { TextareaRenderable } from '@opentui/core'
|
|
3
4
|
import type { RaycastPackageJson } from './package-json'
|
|
4
5
|
|
|
5
6
|
// Toast action keyboard shortcuts (ctrl+t for primary, ctrl+g for secondary)
|
|
@@ -62,8 +63,15 @@ interface AppState {
|
|
|
62
63
|
shouldAutoExecuteFirstAction: boolean
|
|
63
64
|
// First action title for footer display (set by offscreen ActionPanel)
|
|
64
65
|
firstActionTitle: string
|
|
66
|
+
// Flag to show actions dialog via portal
|
|
67
|
+
showActionsDialog: boolean
|
|
68
|
+
// Portal target node for rendering ActionPanel dialog in the overlay area.
|
|
69
|
+
// Set by DialogOverlay, consumed by ActionPanel via createPortal.
|
|
70
|
+
actionsPortalTarget: any
|
|
65
71
|
// Theme state
|
|
66
72
|
currentThemeName: string
|
|
73
|
+
// Active search input ref - used to clear search before exiting on ESC
|
|
74
|
+
activeSearchInputRef: TextareaRenderable | null
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
export const useStore = create<AppState>(() => ({
|
|
@@ -86,6 +94,10 @@ export const useStore = create<AppState>(() => ({
|
|
|
86
94
|
// Actions state
|
|
87
95
|
shouldAutoExecuteFirstAction: false,
|
|
88
96
|
firstActionTitle: '',
|
|
97
|
+
showActionsDialog: false,
|
|
98
|
+
actionsPortalTarget: null,
|
|
89
99
|
// Theme state
|
|
90
100
|
currentThemeName: 'termcast',
|
|
101
|
+
// Active search input ref
|
|
102
|
+
activeSearchInputRef: null,
|
|
91
103
|
}))
|
package/src/theme.tsx
CHANGED
|
@@ -41,14 +41,11 @@ export function initializeTheme(): void {
|
|
|
41
41
|
useStore.setState({ currentThemeName: themeName })
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
export
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return resolved[prop as keyof ResolvedTheme]
|
|
50
|
-
},
|
|
51
|
-
})
|
|
44
|
+
// Reactive hook for theme - use this in React components
|
|
45
|
+
export function useTheme(): ResolvedTheme {
|
|
46
|
+
const themeName = useStore((state) => state.currentThemeName)
|
|
47
|
+
return getResolvedTheme(themeName)
|
|
48
|
+
}
|
|
52
49
|
|
|
53
50
|
export function getMarkdownSyntaxStyle(): SyntaxStyle {
|
|
54
51
|
const themeName = useStore.getState().currentThemeName
|
|
@@ -84,4 +81,4 @@ export const markdownSyntaxStyle = new Proxy({} as SyntaxStyle, {
|
|
|
84
81
|
},
|
|
85
82
|
})
|
|
86
83
|
|
|
87
|
-
|
|
84
|
+
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { goke } from 'goke'
|
|
3
3
|
import { useStore } from 'termcast/src/state'
|
|
4
4
|
import { showToast, Toast } from 'termcast/src/apis/toast'
|
|
5
5
|
import { LocalStorage } from 'termcast/src/apis/localstorage'
|
|
6
6
|
import { CommandArguments } from 'termcast/src/components/command-arguments'
|
|
7
7
|
import { ExtensionPreferences } from 'termcast/src/components/extension-preferences'
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
RaycastArgument,
|
|
10
|
+
RaycastPackageJson,
|
|
11
|
+
} from 'termcast/src/package-json'
|
|
9
12
|
import type { LaunchProps } from 'termcast/src/apis/environment'
|
|
10
13
|
import { logger } from '../logger'
|
|
11
14
|
|
|
@@ -25,14 +28,16 @@ export interface ParsedExtensionArgs {
|
|
|
25
28
|
* Parse CLI args to extract command name and help flag.
|
|
26
29
|
* @param skipArgv - Number of subcommand args to skip (e.g. 1 for "dev" in "termcast dev")
|
|
27
30
|
*/
|
|
28
|
-
export function parseExtensionArgs({
|
|
29
|
-
|
|
31
|
+
export function parseExtensionArgs({
|
|
32
|
+
skipArgv = 0,
|
|
33
|
+
}: { skipArgv?: number } = {}): ParsedExtensionArgs {
|
|
34
|
+
// Build argv for goke: keep first 2 (binary + script), skip subcommand args, keep the rest
|
|
30
35
|
const argv = [
|
|
31
36
|
process.argv[0],
|
|
32
37
|
process.argv[1],
|
|
33
38
|
...process.argv.slice(2 + skipArgv),
|
|
34
39
|
]
|
|
35
|
-
const parsed =
|
|
40
|
+
const parsed = goke().parse(argv, { run: false })
|
|
36
41
|
return {
|
|
37
42
|
commandName: parsed.args[0] as string | undefined,
|
|
38
43
|
showHelp: Boolean(parsed.options.help || parsed.options.h),
|
|
@@ -183,9 +188,26 @@ export async function runCommand(options: RunCommandOptions): Promise<void> {
|
|
|
183
188
|
const devRebuildCount = state.devRebuildCount + 1
|
|
184
189
|
useStore.setState({ devRebuildCount })
|
|
185
190
|
const importPath = `${bundledPath}?rebuild=${devRebuildCount}`
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
191
|
+
logger.log(`importing ${importPath}`)
|
|
192
|
+
try {
|
|
193
|
+
const module = await import(importPath)
|
|
194
|
+
CommandComponent = module.default
|
|
195
|
+
} catch (error) {
|
|
196
|
+
logger.error('Failed to import command module:', {
|
|
197
|
+
importPath,
|
|
198
|
+
error: error instanceof Error ? error.message : String(error),
|
|
199
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
200
|
+
})
|
|
201
|
+
await showToast({
|
|
202
|
+
style: Toast.Style.Failure,
|
|
203
|
+
title: 'Failed to load command',
|
|
204
|
+
message:
|
|
205
|
+
error instanceof Error
|
|
206
|
+
? error.message
|
|
207
|
+
: 'Unknown error ' + String(error).slice(0, 300),
|
|
208
|
+
})
|
|
209
|
+
return
|
|
210
|
+
}
|
|
189
211
|
|
|
190
212
|
if (!CommandComponent) {
|
|
191
213
|
await showToast({
|
|
@@ -230,7 +252,9 @@ export async function runCommand(options: RunCommandOptions): Promise<void> {
|
|
|
230
252
|
}
|
|
231
253
|
|
|
232
254
|
// For view commands, push the component
|
|
255
|
+
logger.log('pushing WrappedComponent')
|
|
233
256
|
push(<CommandComponent {...launchProps} />)
|
|
257
|
+
logger.log('pushed WrappedComponent')
|
|
234
258
|
}
|
|
235
259
|
|
|
236
260
|
async function checkRequiredPreferences({
|
package/src/utils.tsx
CHANGED
|
@@ -19,9 +19,12 @@ export async function renderWithProviders(element: ReactNode): Promise<void> {
|
|
|
19
19
|
process.exit(0)
|
|
20
20
|
},
|
|
21
21
|
})
|
|
22
|
+
|
|
22
23
|
createRoot(renderer).render(<TermcastProvider>{element}</TermcastProvider>)
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
export const termcastMaxContentWidth = 140
|
|
27
|
+
|
|
25
28
|
export type CommonProps = {
|
|
26
29
|
key?: any
|
|
27
30
|
}
|
|
@@ -41,29 +44,61 @@ export function sleep(ms: number): Promise<void> {
|
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
export async function getApplications(path?: PathLike): Promise<Application[]> {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
47
|
+
if (process.platform !== 'darwin') {
|
|
48
|
+
// Windows/Linux: return empty for now
|
|
49
|
+
return []
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { execSync } = await import('node:child_process')
|
|
53
|
+
const plist = await import('simple-plist')
|
|
54
|
+
|
|
55
|
+
const apps: Application[] = []
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
// Use mdfind (Spotlight) to find all .app bundles
|
|
59
|
+
// This is much faster than scanning directories manually
|
|
60
|
+
const appPaths = execSync(
|
|
61
|
+
'mdfind "kMDItemContentType == \'com.apple.application-bundle\'" 2>/dev/null || find /Applications /System/Applications -name "*.app" -maxdepth 3 2>/dev/null',
|
|
62
|
+
{ encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 },
|
|
63
|
+
)
|
|
64
|
+
.trim()
|
|
65
|
+
.split('\n')
|
|
66
|
+
.filter((p) => p && p.endsWith('.app'))
|
|
67
|
+
|
|
68
|
+
for (const appPath of appPaths) {
|
|
69
|
+
try {
|
|
70
|
+
const infoPlistPath = `${appPath}/Contents/Info.plist`
|
|
71
|
+
|
|
72
|
+
if (!fs.existsSync(infoPlistPath)) {
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const plistData = plist.default.readFileSync(infoPlistPath) as Record<
|
|
77
|
+
string,
|
|
78
|
+
unknown
|
|
79
|
+
>
|
|
80
|
+
const bundleId = plistData.CFBundleIdentifier as string | undefined
|
|
81
|
+
const name = (plistData.CFBundleName ||
|
|
82
|
+
plistData.CFBundleDisplayName ||
|
|
83
|
+
appPath.split('/').pop()?.replace('.app', '')) as string
|
|
84
|
+
const localizedName = (plistData.CFBundleDisplayName || name) as string
|
|
85
|
+
|
|
86
|
+
apps.push({
|
|
87
|
+
name,
|
|
88
|
+
localizedName,
|
|
89
|
+
path: appPath,
|
|
90
|
+
bundleId,
|
|
91
|
+
})
|
|
92
|
+
} catch {
|
|
93
|
+
// Skip apps with unreadable plist files
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
logger.error('Failed to get applications:', error)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return apps
|
|
67
102
|
}
|
|
68
103
|
|
|
69
104
|
export async function getDefaultApplication(
|