dialog-stash.tsx 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import { useDialog } from "@tui/ui/dialog"
  2. import { DialogSelect } from "@tui/ui/dialog-select"
  3. import { createMemo, createSignal } from "solid-js"
  4. import { Locale } from "@/util/locale"
  5. import { Keybind } from "@/util/keybind"
  6. import { useTheme } from "../context/theme"
  7. import { usePromptStash, type StashEntry } from "./prompt/stash"
  8. function getRelativeTime(timestamp: number): string {
  9. const now = Date.now()
  10. const diff = now - timestamp
  11. const seconds = Math.floor(diff / 1000)
  12. const minutes = Math.floor(seconds / 60)
  13. const hours = Math.floor(minutes / 60)
  14. const days = Math.floor(hours / 24)
  15. if (seconds < 60) return "just now"
  16. if (minutes < 60) return `${minutes}m ago`
  17. if (hours < 24) return `${hours}h ago`
  18. if (days < 7) return `${days}d ago`
  19. return Locale.datetime(timestamp)
  20. }
  21. function getStashPreview(input: string, maxLength: number = 50): string {
  22. const firstLine = input.split("\n")[0].trim()
  23. return Locale.truncate(firstLine, maxLength)
  24. }
  25. export function DialogStash(props: { onSelect: (entry: StashEntry) => void }) {
  26. const dialog = useDialog()
  27. const stash = usePromptStash()
  28. const { theme } = useTheme()
  29. const [toDelete, setToDelete] = createSignal<number>()
  30. const options = createMemo(() => {
  31. const entries = stash.list()
  32. // Show most recent first
  33. return entries
  34. .map((entry, index) => {
  35. const isDeleting = toDelete() === index
  36. const lineCount = (entry.input.match(/\n/g)?.length ?? 0) + 1
  37. return {
  38. title: isDeleting ? "Press ctrl+d again to confirm" : getStashPreview(entry.input),
  39. bg: isDeleting ? theme.error : undefined,
  40. value: index,
  41. description: getRelativeTime(entry.timestamp),
  42. footer: lineCount > 1 ? `~${lineCount} lines` : undefined,
  43. }
  44. })
  45. .toReversed()
  46. })
  47. return (
  48. <DialogSelect
  49. title="Stash"
  50. options={options()}
  51. onMove={() => {
  52. setToDelete(undefined)
  53. }}
  54. onSelect={(option) => {
  55. const entries = stash.list()
  56. const entry = entries[option.value]
  57. if (entry) {
  58. stash.remove(option.value)
  59. props.onSelect(entry)
  60. }
  61. dialog.clear()
  62. }}
  63. keybind={[
  64. {
  65. keybind: Keybind.parse("ctrl+d")[0],
  66. title: "delete",
  67. onTrigger: (option) => {
  68. if (toDelete() === option.value) {
  69. stash.remove(option.value)
  70. setToDelete(undefined)
  71. return
  72. }
  73. setToDelete(option.value)
  74. },
  75. },
  76. ]}
  77. />
  78. )
  79. }