@doriandev/devcc 0.1.2
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/LICENSE +15 -0
- package/README.md +659 -0
- package/bin/devcc.mjs +66 -0
- package/dist/devcc/app/App.d.ts +178 -0
- package/dist/devcc/app/App.d.ts.map +1 -0
- package/dist/devcc/app/App.js +1255 -0
- package/dist/devcc/app/App.js.map +1 -0
- package/dist/devcc/app/builtinCommands.d.ts +17 -0
- package/dist/devcc/app/builtinCommands.d.ts.map +1 -0
- package/dist/devcc/app/builtinCommands.js +389 -0
- package/dist/devcc/app/builtinCommands.js.map +1 -0
- package/dist/devcc/app/keymap.d.ts +21 -0
- package/dist/devcc/app/keymap.d.ts.map +1 -0
- package/dist/devcc/app/keymap.js +162 -0
- package/dist/devcc/app/keymap.js.map +1 -0
- package/dist/devcc/app/theme.d.ts +49 -0
- package/dist/devcc/app/theme.d.ts.map +1 -0
- package/dist/devcc/app/theme.js +68 -0
- package/dist/devcc/app/theme.js.map +1 -0
- package/dist/devcc/cli.d.ts +3 -0
- package/dist/devcc/cli.d.ts.map +1 -0
- package/dist/devcc/cli.js +113 -0
- package/dist/devcc/cli.js.map +1 -0
- package/dist/devcc/components/CommandPalette.d.ts +26 -0
- package/dist/devcc/components/CommandPalette.d.ts.map +1 -0
- package/dist/devcc/components/CommandPalette.js +113 -0
- package/dist/devcc/components/CommandPalette.js.map +1 -0
- package/dist/devcc/components/ConfirmDialog.d.ts +31 -0
- package/dist/devcc/components/ConfirmDialog.d.ts.map +1 -0
- package/dist/devcc/components/ConfirmDialog.js +147 -0
- package/dist/devcc/components/ConfirmDialog.js.map +1 -0
- package/dist/devcc/components/Footer.d.ts +22 -0
- package/dist/devcc/components/Footer.d.ts.map +1 -0
- package/dist/devcc/components/Footer.js +72 -0
- package/dist/devcc/components/Footer.js.map +1 -0
- package/dist/devcc/components/Header.d.ts +12 -0
- package/dist/devcc/components/Header.d.ts.map +1 -0
- package/dist/devcc/components/Header.js +84 -0
- package/dist/devcc/components/Header.js.map +1 -0
- package/dist/devcc/components/HelpOverlay.d.ts +18 -0
- package/dist/devcc/components/HelpOverlay.d.ts.map +1 -0
- package/dist/devcc/components/HelpOverlay.js +48 -0
- package/dist/devcc/components/HelpOverlay.js.map +1 -0
- package/dist/devcc/components/Overlay.d.ts +31 -0
- package/dist/devcc/components/Overlay.d.ts.map +1 -0
- package/dist/devcc/components/Overlay.js +60 -0
- package/dist/devcc/components/Overlay.js.map +1 -0
- package/dist/devcc/components/ProjectPicker.d.ts +26 -0
- package/dist/devcc/components/ProjectPicker.d.ts.map +1 -0
- package/dist/devcc/components/ProjectPicker.js +114 -0
- package/dist/devcc/components/ProjectPicker.js.map +1 -0
- package/dist/devcc/components/Sidebar.d.ts +42 -0
- package/dist/devcc/components/Sidebar.d.ts.map +1 -0
- package/dist/devcc/components/Sidebar.js +127 -0
- package/dist/devcc/components/Sidebar.js.map +1 -0
- package/dist/devcc/components/StatusBadge.d.ts +16 -0
- package/dist/devcc/components/StatusBadge.d.ts.map +1 -0
- package/dist/devcc/components/StatusBadge.js +30 -0
- package/dist/devcc/components/StatusBadge.js.map +1 -0
- package/dist/devcc/components/primitives.d.ts +62 -0
- package/dist/devcc/components/primitives.d.ts.map +1 -0
- package/dist/devcc/components/primitives.js +169 -0
- package/dist/devcc/components/primitives.js.map +1 -0
- package/dist/devcc/components/styled.d.ts +16 -0
- package/dist/devcc/components/styled.d.ts.map +1 -0
- package/dist/devcc/components/styled.js +35 -0
- package/dist/devcc/components/styled.js.map +1 -0
- package/dist/devcc/core/commands.d.ts +89 -0
- package/dist/devcc/core/commands.d.ts.map +1 -0
- package/dist/devcc/core/commands.js +68 -0
- package/dist/devcc/core/commands.js.map +1 -0
- package/dist/devcc/core/config.d.ts +81 -0
- package/dist/devcc/core/config.d.ts.map +1 -0
- package/dist/devcc/core/config.js +313 -0
- package/dist/devcc/core/config.js.map +1 -0
- package/dist/devcc/core/events.d.ts +21 -0
- package/dist/devcc/core/events.d.ts.map +1 -0
- package/dist/devcc/core/events.js +27 -0
- package/dist/devcc/core/events.js.map +1 -0
- package/dist/devcc/core/state.d.ts +104 -0
- package/dist/devcc/core/state.d.ts.map +1 -0
- package/dist/devcc/core/state.js +139 -0
- package/dist/devcc/core/state.js.map +1 -0
- package/dist/devcc/deps/DependencyService.d.ts +50 -0
- package/dist/devcc/deps/DependencyService.d.ts.map +1 -0
- package/dist/devcc/deps/DependencyService.js +109 -0
- package/dist/devcc/deps/DependencyService.js.map +1 -0
- package/dist/devcc/deps/parse.d.ts +49 -0
- package/dist/devcc/deps/parse.d.ts.map +1 -0
- package/dist/devcc/deps/parse.js +158 -0
- package/dist/devcc/deps/parse.js.map +1 -0
- package/dist/devcc/git/GitService.d.ts +60 -0
- package/dist/devcc/git/GitService.d.ts.map +1 -0
- package/dist/devcc/git/GitService.js +160 -0
- package/dist/devcc/git/GitService.js.map +1 -0
- package/dist/devcc/git/parse.d.ts +57 -0
- package/dist/devcc/git/parse.d.ts.map +1 -0
- package/dist/devcc/git/parse.js +174 -0
- package/dist/devcc/git/parse.js.map +1 -0
- package/dist/devcc/index.d.ts +37 -0
- package/dist/devcc/index.d.ts.map +1 -0
- package/dist/devcc/index.js +36 -0
- package/dist/devcc/index.js.map +1 -0
- package/dist/devcc/logs/LogBuffer.d.ts +86 -0
- package/dist/devcc/logs/LogBuffer.d.ts.map +1 -0
- package/dist/devcc/logs/LogBuffer.js +200 -0
- package/dist/devcc/logs/LogBuffer.js.map +1 -0
- package/dist/devcc/logs/merge.d.ts +36 -0
- package/dist/devcc/logs/merge.d.ts.map +1 -0
- package/dist/devcc/logs/merge.js +71 -0
- package/dist/devcc/logs/merge.js.map +1 -0
- package/dist/devcc/processes/ManagedProcess.d.ts +129 -0
- package/dist/devcc/processes/ManagedProcess.d.ts.map +1 -0
- package/dist/devcc/processes/ManagedProcess.js +80 -0
- package/dist/devcc/processes/ManagedProcess.js.map +1 -0
- package/dist/devcc/processes/ProcessManager.d.ts +112 -0
- package/dist/devcc/processes/ProcessManager.d.ts.map +1 -0
- package/dist/devcc/processes/ProcessManager.js +525 -0
- package/dist/devcc/processes/ProcessManager.js.map +1 -0
- package/dist/devcc/processes/graph.d.ts +35 -0
- package/dist/devcc/processes/graph.d.ts.map +1 -0
- package/dist/devcc/processes/graph.js +68 -0
- package/dist/devcc/processes/graph.js.map +1 -0
- package/dist/devcc/projects/ProjectRegistry.d.ts +52 -0
- package/dist/devcc/projects/ProjectRegistry.d.ts.map +1 -0
- package/dist/devcc/projects/ProjectRegistry.js +177 -0
- package/dist/devcc/projects/ProjectRegistry.js.map +1 -0
- package/dist/devcc/services/ServiceManager.d.ts +48 -0
- package/dist/devcc/services/ServiceManager.d.ts.map +1 -0
- package/dist/devcc/services/ServiceManager.js +146 -0
- package/dist/devcc/services/ServiceManager.js.map +1 -0
- package/dist/devcc/services/docker.d.ts +37 -0
- package/dist/devcc/services/docker.d.ts.map +1 -0
- package/dist/devcc/services/docker.js +179 -0
- package/dist/devcc/services/docker.js.map +1 -0
- package/dist/devcc/services/node.d.ts +40 -0
- package/dist/devcc/services/node.d.ts.map +1 -0
- package/dist/devcc/services/node.js +107 -0
- package/dist/devcc/services/node.js.map +1 -0
- package/dist/devcc/services/ollama.d.ts +27 -0
- package/dist/devcc/services/ollama.d.ts.map +1 -0
- package/dist/devcc/services/ollama.js +66 -0
- package/dist/devcc/services/ollama.js.map +1 -0
- package/dist/devcc/services/postgres.d.ts +30 -0
- package/dist/devcc/services/postgres.d.ts.map +1 -0
- package/dist/devcc/services/postgres.js +47 -0
- package/dist/devcc/services/postgres.js.map +1 -0
- package/dist/devcc/services/redis.d.ts +20 -0
- package/dist/devcc/services/redis.d.ts.map +1 -0
- package/dist/devcc/services/redis.js +39 -0
- package/dist/devcc/services/redis.js.map +1 -0
- package/dist/devcc/services/types.d.ts +51 -0
- package/dist/devcc/services/types.d.ts.map +1 -0
- package/dist/devcc/services/types.js +10 -0
- package/dist/devcc/services/types.js.map +1 -0
- package/dist/devcc/system/SystemMonitor.d.ts +87 -0
- package/dist/devcc/system/SystemMonitor.d.ts.map +1 -0
- package/dist/devcc/system/SystemMonitor.js +142 -0
- package/dist/devcc/system/SystemMonitor.js.map +1 -0
- package/dist/devcc/utils/errors.d.ts +31 -0
- package/dist/devcc/utils/errors.d.ts.map +1 -0
- package/dist/devcc/utils/errors.js +52 -0
- package/dist/devcc/utils/errors.js.map +1 -0
- package/dist/devcc/utils/exec.d.ts +41 -0
- package/dist/devcc/utils/exec.d.ts.map +1 -0
- package/dist/devcc/utils/exec.js +112 -0
- package/dist/devcc/utils/exec.js.map +1 -0
- package/dist/devcc/utils/format.d.ts +18 -0
- package/dist/devcc/utils/format.d.ts.map +1 -0
- package/dist/devcc/utils/format.js +85 -0
- package/dist/devcc/utils/format.js.map +1 -0
- package/dist/devcc/utils/fuzzy.d.ts +12 -0
- package/dist/devcc/utils/fuzzy.d.ts.map +1 -0
- package/dist/devcc/utils/fuzzy.js +20 -0
- package/dist/devcc/utils/fuzzy.js.map +1 -0
- package/dist/devcc/utils/logger.d.ts +41 -0
- package/dist/devcc/utils/logger.d.ts.map +1 -0
- package/dist/devcc/utils/logger.js +117 -0
- package/dist/devcc/utils/logger.js.map +1 -0
- package/dist/devcc/utils/packageManager.d.ts +39 -0
- package/dist/devcc/utils/packageManager.d.ts.map +1 -0
- package/dist/devcc/utils/packageManager.js +85 -0
- package/dist/devcc/utils/packageManager.js.map +1 -0
- package/dist/devcc/utils/paths.d.ts +12 -0
- package/dist/devcc/utils/paths.d.ts.map +1 -0
- package/dist/devcc/utils/paths.js +39 -0
- package/dist/devcc/utils/paths.js.map +1 -0
- package/dist/devcc/views/DependenciesView.d.ts +34 -0
- package/dist/devcc/views/DependenciesView.d.ts.map +1 -0
- package/dist/devcc/views/DependenciesView.js +170 -0
- package/dist/devcc/views/DependenciesView.js.map +1 -0
- package/dist/devcc/views/GitView.d.ts +29 -0
- package/dist/devcc/views/GitView.d.ts.map +1 -0
- package/dist/devcc/views/GitView.js +226 -0
- package/dist/devcc/views/GitView.js.map +1 -0
- package/dist/devcc/views/LogsView.d.ts +39 -0
- package/dist/devcc/views/LogsView.d.ts.map +1 -0
- package/dist/devcc/views/LogsView.js +249 -0
- package/dist/devcc/views/LogsView.js.map +1 -0
- package/dist/devcc/views/OverviewView.d.ts +16 -0
- package/dist/devcc/views/OverviewView.d.ts.map +1 -0
- package/dist/devcc/views/OverviewView.js +135 -0
- package/dist/devcc/views/OverviewView.js.map +1 -0
- package/dist/devcc/views/ProcessesView.d.ts +26 -0
- package/dist/devcc/views/ProcessesView.d.ts.map +1 -0
- package/dist/devcc/views/ProcessesView.js +161 -0
- package/dist/devcc/views/ProcessesView.js.map +1 -0
- package/dist/devcc/views/ScriptsView.d.ts +30 -0
- package/dist/devcc/views/ScriptsView.d.ts.map +1 -0
- package/dist/devcc/views/ScriptsView.js +168 -0
- package/dist/devcc/views/ScriptsView.js.map +1 -0
- package/dist/devcc/views/ServicesView.d.ts +28 -0
- package/dist/devcc/views/ServicesView.d.ts.map +1 -0
- package/dist/devcc/views/ServicesView.js +153 -0
- package/dist/devcc/views/ServicesView.js.map +1 -0
- package/dist/devcc/views/SettingsView.d.ts +20 -0
- package/dist/devcc/views/SettingsView.d.ts.map +1 -0
- package/dist/devcc/views/SettingsView.js +114 -0
- package/dist/devcc/views/SettingsView.js.map +1 -0
- package/dist/devcc/views/SystemView.d.ts +13 -0
- package/dist/devcc/views/SystemView.d.ts.map +1 -0
- package/dist/devcc/views/SystemView.js +82 -0
- package/dist/devcc/views/SystemView.js.map +1 -0
- package/dist/devcc/views/View.d.ts +35 -0
- package/dist/devcc/views/View.d.ts.map +1 -0
- package/dist/devcc/views/View.js +42 -0
- package/dist/devcc/views/View.js.map +1 -0
- package/package.json +79 -0
- package/src/devcc/app/App.ts +1463 -0
- package/src/devcc/app/builtinCommands.ts +429 -0
- package/src/devcc/app/keymap.ts +167 -0
- package/src/devcc/app/theme.ts +83 -0
- package/src/devcc/cli.ts +122 -0
- package/src/devcc/components/CommandPalette.ts +136 -0
- package/src/devcc/components/ConfirmDialog.ts +174 -0
- package/src/devcc/components/Footer.ts +85 -0
- package/src/devcc/components/Header.ts +93 -0
- package/src/devcc/components/HelpOverlay.ts +62 -0
- package/src/devcc/components/Overlay.ts +91 -0
- package/src/devcc/components/ProjectPicker.ts +138 -0
- package/src/devcc/components/Sidebar.ts +163 -0
- package/src/devcc/components/StatusBadge.ts +36 -0
- package/src/devcc/components/primitives.ts +213 -0
- package/src/devcc/components/styled.ts +44 -0
- package/src/devcc/core/commands.ts +140 -0
- package/src/devcc/core/config.ts +405 -0
- package/src/devcc/core/events.ts +41 -0
- package/src/devcc/core/state.ts +236 -0
- package/src/devcc/deps/DependencyService.ts +152 -0
- package/src/devcc/deps/parse.ts +210 -0
- package/src/devcc/git/GitService.ts +205 -0
- package/src/devcc/git/parse.ts +203 -0
- package/src/devcc/index.ts +115 -0
- package/src/devcc/logs/LogBuffer.ts +237 -0
- package/src/devcc/logs/merge.ts +87 -0
- package/src/devcc/processes/ManagedProcess.ts +195 -0
- package/src/devcc/processes/ProcessManager.ts +625 -0
- package/src/devcc/processes/graph.ts +98 -0
- package/src/devcc/projects/ProjectRegistry.ts +219 -0
- package/src/devcc/services/ServiceManager.ts +180 -0
- package/src/devcc/services/docker.ts +220 -0
- package/src/devcc/services/node.ts +137 -0
- package/src/devcc/services/ollama.ts +80 -0
- package/src/devcc/services/postgres.ts +57 -0
- package/src/devcc/services/redis.ts +47 -0
- package/src/devcc/services/types.ts +59 -0
- package/src/devcc/system/SystemMonitor.ts +207 -0
- package/src/devcc/utils/errors.ts +64 -0
- package/src/devcc/utils/exec.ts +165 -0
- package/src/devcc/utils/format.ts +83 -0
- package/src/devcc/utils/fuzzy.ts +44 -0
- package/src/devcc/utils/logger.ts +140 -0
- package/src/devcc/utils/packageManager.ts +112 -0
- package/src/devcc/utils/paths.ts +39 -0
- package/src/devcc/views/DependenciesView.ts +206 -0
- package/src/devcc/views/GitView.ts +259 -0
- package/src/devcc/views/LogsView.ts +300 -0
- package/src/devcc/views/OverviewView.ts +187 -0
- package/src/devcc/views/ProcessesView.ts +177 -0
- package/src/devcc/views/ScriptsView.ts +187 -0
- package/src/devcc/views/ServicesView.ts +188 -0
- package/src/devcc/views/SettingsView.ts +146 -0
- package/src/devcc/views/SystemView.ts +110 -0
- package/src/devcc/views/View.ts +63 -0
package/src/devcc/cli.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Entry point for `devcc` / `pnpm dev:center`.
|
|
4
|
+
*
|
|
5
|
+
* OpenTUI talks to a native Zig core over FFI, which currently means Bun 1.3+
|
|
6
|
+
* or Node 26.4+ with `--experimental-ffi`. The launcher in `bin/devcc.mjs`
|
|
7
|
+
* re-execs under a supported runtime; this file fails loudly if something
|
|
8
|
+
* bypassed it.
|
|
9
|
+
*/
|
|
10
|
+
import { createRequire } from "node:module"
|
|
11
|
+
import { pathToFileURL } from "node:url"
|
|
12
|
+
import { App } from "./app/App.js"
|
|
13
|
+
import { errorMessage } from "./utils/errors.js"
|
|
14
|
+
import { findProjectRoot } from "./utils/paths.js"
|
|
15
|
+
|
|
16
|
+
interface CliArgs {
|
|
17
|
+
cwd?: string
|
|
18
|
+
help: boolean
|
|
19
|
+
version: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Read the version from package.json rather than duplicating it here, so the
|
|
24
|
+
* CLI can never disagree with what was published. `src/devcc/cli.ts` and
|
|
25
|
+
* `dist/devcc/cli.js` sit at the same depth, so one relative path serves both.
|
|
26
|
+
*/
|
|
27
|
+
function readVersion(): string {
|
|
28
|
+
try {
|
|
29
|
+
const require = createRequire(import.meta.url)
|
|
30
|
+
const pkg = require("../../package.json") as { version?: unknown }
|
|
31
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0"
|
|
32
|
+
} catch {
|
|
33
|
+
return "0.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const VERSION = readVersion()
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv: readonly string[]): CliArgs {
|
|
40
|
+
const args: CliArgs = { help: false, version: false }
|
|
41
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
42
|
+
const arg = argv[index]
|
|
43
|
+
if (arg === "--help" || arg === "-h") args.help = true
|
|
44
|
+
else if (arg === "--version" || arg === "-v") args.version = true
|
|
45
|
+
else if (arg === "--cwd" || arg === "-C") {
|
|
46
|
+
const value = argv[index + 1]
|
|
47
|
+
if (value) {
|
|
48
|
+
args.cwd = value
|
|
49
|
+
index += 1
|
|
50
|
+
}
|
|
51
|
+
} else if (arg?.startsWith("--cwd=")) {
|
|
52
|
+
args.cwd = arg.slice("--cwd=".length)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return args
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const USAGE = `dev command center ${VERSION}
|
|
59
|
+
|
|
60
|
+
Usage:
|
|
61
|
+
devcc [options]
|
|
62
|
+
|
|
63
|
+
Options:
|
|
64
|
+
-C, --cwd <dir> Project directory (default: nearest project root)
|
|
65
|
+
-h, --help Show this help
|
|
66
|
+
-v, --version Show the version
|
|
67
|
+
|
|
68
|
+
Environment:
|
|
69
|
+
DEVCC_LOG_LEVEL debug | info | warn | error | silent (default: info)
|
|
70
|
+
|
|
71
|
+
Configuration:
|
|
72
|
+
Optional .devcc.ts or devcc.config.ts in the project root.
|
|
73
|
+
`
|
|
74
|
+
|
|
75
|
+
export async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {
|
|
76
|
+
const args = parseArgs(argv)
|
|
77
|
+
|
|
78
|
+
if (args.help) {
|
|
79
|
+
process.stdout.write(USAGE)
|
|
80
|
+
return 0
|
|
81
|
+
}
|
|
82
|
+
if (args.version) {
|
|
83
|
+
process.stdout.write(`${VERSION}\n`)
|
|
84
|
+
return 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!process.stdout.isTTY) {
|
|
88
|
+
process.stderr.write("devcc needs an interactive terminal (stdout is not a TTY).\n")
|
|
89
|
+
return 1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const app = new App({ cwd: args.cwd ? args.cwd : findProjectRoot() })
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
await app.run()
|
|
96
|
+
return 0
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// The renderer may still own the terminal; tear it down before printing.
|
|
99
|
+
try {
|
|
100
|
+
await app.quit()
|
|
101
|
+
} catch {
|
|
102
|
+
// Nothing more we can do here.
|
|
103
|
+
}
|
|
104
|
+
process.stderr.write(`devcc failed to start: ${errorMessage(error)}\n`)
|
|
105
|
+
if (error instanceof Error && error.stack) process.stderr.write(`${error.stack}\n`)
|
|
106
|
+
return 1
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const entry = process.argv[1]
|
|
111
|
+
const isDirectRun = entry !== undefined && import.meta.url === pathToFileURL(entry).href
|
|
112
|
+
|
|
113
|
+
if (isDirectRun) {
|
|
114
|
+
main()
|
|
115
|
+
.then((code) => {
|
|
116
|
+
process.exitCode = code
|
|
117
|
+
})
|
|
118
|
+
.catch((error: unknown) => {
|
|
119
|
+
process.stderr.write(`${errorMessage(error)}\n`)
|
|
120
|
+
process.exitCode = 1
|
|
121
|
+
})
|
|
122
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { BoxRenderable, InputRenderable, TextRenderable, type RenderContext } from "@opentui/core"
|
|
2
|
+
import { theme } from "../app/theme.js"
|
|
3
|
+
import type { CommandDefinition } from "../core/commands.js"
|
|
4
|
+
import { fit } from "../utils/format.js"
|
|
5
|
+
import { Overlay } from "./Overlay.js"
|
|
6
|
+
import { RowList, type ListRow } from "./primitives.js"
|
|
7
|
+
import { chunk, dim, styled } from "./styled.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fuzzy command palette (`/`).
|
|
11
|
+
*
|
|
12
|
+
* The palette is the discoverable surface for every registered command,
|
|
13
|
+
* including ones contributed by project config, so nothing is reachable only
|
|
14
|
+
* through a hidden key binding.
|
|
15
|
+
*/
|
|
16
|
+
export class CommandPalette extends Overlay {
|
|
17
|
+
readonly input: InputRenderable
|
|
18
|
+
private readonly list: RowList
|
|
19
|
+
private readonly counter: TextRenderable
|
|
20
|
+
private results: CommandDefinition[] = []
|
|
21
|
+
private selectedIndex = 0
|
|
22
|
+
|
|
23
|
+
constructor(ctx: RenderContext) {
|
|
24
|
+
super(ctx, {
|
|
25
|
+
title: " Command palette ",
|
|
26
|
+
widthRatio: 0.7,
|
|
27
|
+
minWidth: 42,
|
|
28
|
+
maxWidth: 96,
|
|
29
|
+
heightRatio: 0.6,
|
|
30
|
+
minHeight: 9,
|
|
31
|
+
maxHeight: 24,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const inputWrapper = new BoxRenderable(ctx, {
|
|
35
|
+
height: 1,
|
|
36
|
+
flexShrink: 0,
|
|
37
|
+
flexDirection: "row",
|
|
38
|
+
marginTop: 1,
|
|
39
|
+
backgroundColor: theme.panelAlt,
|
|
40
|
+
paddingLeft: 1,
|
|
41
|
+
paddingRight: 1,
|
|
42
|
+
})
|
|
43
|
+
const marker = new TextRenderable(ctx, {
|
|
44
|
+
content: "› ",
|
|
45
|
+
fg: theme.accent,
|
|
46
|
+
width: 2,
|
|
47
|
+
selectable: false,
|
|
48
|
+
})
|
|
49
|
+
this.input = new InputRenderable(ctx, {
|
|
50
|
+
flexGrow: 1,
|
|
51
|
+
backgroundColor: theme.panelAlt,
|
|
52
|
+
textColor: theme.textBright,
|
|
53
|
+
placeholder: "Type a command…",
|
|
54
|
+
placeholderColor: theme.textDim,
|
|
55
|
+
})
|
|
56
|
+
inputWrapper.add(marker)
|
|
57
|
+
inputWrapper.add(this.input)
|
|
58
|
+
|
|
59
|
+
this.list = new RowList(ctx, { marginTop: 1, flexGrow: 1, onViewportChange: () => this.paint() })
|
|
60
|
+
|
|
61
|
+
this.counter = new TextRenderable(ctx, {
|
|
62
|
+
content: "",
|
|
63
|
+
fg: theme.textDim,
|
|
64
|
+
height: 1,
|
|
65
|
+
flexShrink: 0,
|
|
66
|
+
selectable: false,
|
|
67
|
+
truncate: true,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
this.box.add(inputWrapper)
|
|
71
|
+
this.box.add(this.list.box)
|
|
72
|
+
this.box.add(this.counter)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
open(): void {
|
|
76
|
+
this.input.value = ""
|
|
77
|
+
this.selectedIndex = 0
|
|
78
|
+
this.show()
|
|
79
|
+
this.input.focus()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
close(): void {
|
|
83
|
+
this.input.blur()
|
|
84
|
+
this.hide()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get query(): string {
|
|
88
|
+
return this.input.value
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setResults(results: CommandDefinition[]): void {
|
|
92
|
+
this.results = results
|
|
93
|
+
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, results.length - 1))
|
|
94
|
+
this.paint()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
move(delta: number): void {
|
|
98
|
+
if (this.results.length === 0) return
|
|
99
|
+
const next = this.selectedIndex + delta
|
|
100
|
+
this.selectedIndex = ((next % this.results.length) + this.results.length) % this.results.length
|
|
101
|
+
this.paint()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get selected(): CommandDefinition | undefined {
|
|
105
|
+
return this.results[this.selectedIndex]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private paint(): void {
|
|
109
|
+
const width = Math.max(20, this.box.width - 4)
|
|
110
|
+
const categoryWidth = 12
|
|
111
|
+
const shortcutWidth = 8
|
|
112
|
+
|
|
113
|
+
const rows: ListRow[] = this.results.map((command) => ({
|
|
114
|
+
key: command.id,
|
|
115
|
+
content: styled(
|
|
116
|
+
dim(fit(command.category ?? "", categoryWidth)),
|
|
117
|
+
chunk(fit(command.title, Math.max(8, width - categoryWidth - shortcutWidth)), theme.text),
|
|
118
|
+
dim(command.shortcut ? fit(command.shortcut, shortcutWidth) : ""),
|
|
119
|
+
),
|
|
120
|
+
}))
|
|
121
|
+
|
|
122
|
+
if (rows.length === 0) {
|
|
123
|
+
rows.push({ key: "__empty", content: dimRow("No matching commands"), disabled: true })
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.list.setRows(rows, this.selectedIndex)
|
|
127
|
+
this.counter.content = styled(
|
|
128
|
+
dim(`${this.results.length} command${this.results.length === 1 ? "" : "s"}`),
|
|
129
|
+
dim(" ↑↓ move enter run esc close"),
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function dimRow(text: string) {
|
|
135
|
+
return styled(dim(text))
|
|
136
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { BoxRenderable, InputRenderable, TextRenderable, type RenderContext } from "@opentui/core"
|
|
2
|
+
import { theme } from "../app/theme.js"
|
|
3
|
+
import { Overlay } from "./Overlay.js"
|
|
4
|
+
import { chunk, dim, styled } from "./styled.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Yes/no modal for anything destructive or irreversible.
|
|
8
|
+
*
|
|
9
|
+
* Nothing in devcc runs a destructive command without one of these: `git
|
|
10
|
+
* restore`, `git push`, `docker compose down`, and any config-declared command
|
|
11
|
+
* marked `confirm` all route through here.
|
|
12
|
+
*/
|
|
13
|
+
export class ConfirmDialog extends Overlay {
|
|
14
|
+
private readonly message: TextRenderable
|
|
15
|
+
private readonly detail: TextRenderable
|
|
16
|
+
private resolver: ((value: boolean) => void) | null = null
|
|
17
|
+
|
|
18
|
+
constructor(ctx: RenderContext) {
|
|
19
|
+
super(ctx, {
|
|
20
|
+
title: " Confirm ",
|
|
21
|
+
widthRatio: 0.5,
|
|
22
|
+
minWidth: 36,
|
|
23
|
+
maxWidth: 72,
|
|
24
|
+
heightRatio: 0.3,
|
|
25
|
+
minHeight: 7,
|
|
26
|
+
maxHeight: 11,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
this.box.borderColor = theme.warning
|
|
30
|
+
this.box.titleColor = theme.warning
|
|
31
|
+
|
|
32
|
+
this.message = new TextRenderable(ctx, {
|
|
33
|
+
content: "",
|
|
34
|
+
fg: theme.textBright,
|
|
35
|
+
height: 1,
|
|
36
|
+
marginTop: 1,
|
|
37
|
+
selectable: false,
|
|
38
|
+
truncate: true,
|
|
39
|
+
})
|
|
40
|
+
this.detail = new TextRenderable(ctx, {
|
|
41
|
+
content: "",
|
|
42
|
+
fg: theme.text,
|
|
43
|
+
flexGrow: 1,
|
|
44
|
+
wrapMode: "word",
|
|
45
|
+
selectable: false,
|
|
46
|
+
})
|
|
47
|
+
const hints = new TextRenderable(ctx, {
|
|
48
|
+
content: styled(
|
|
49
|
+
chunk("y", theme.success),
|
|
50
|
+
dim(" confirm "),
|
|
51
|
+
chunk("n / esc", theme.danger),
|
|
52
|
+
dim(" cancel"),
|
|
53
|
+
),
|
|
54
|
+
height: 1,
|
|
55
|
+
selectable: false,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
this.box.add(this.message)
|
|
59
|
+
this.box.add(this.detail)
|
|
60
|
+
this.box.add(hints)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Show the dialog; resolves once the user answers or dismisses it. */
|
|
64
|
+
ask(title: string, message: string): Promise<boolean> {
|
|
65
|
+
this.resolve(false)
|
|
66
|
+
this.message.content = title
|
|
67
|
+
this.detail.content = message
|
|
68
|
+
this.show()
|
|
69
|
+
return new Promise<boolean>((resolve) => {
|
|
70
|
+
this.resolver = resolve
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
resolve(value: boolean): void {
|
|
75
|
+
const resolver = this.resolver
|
|
76
|
+
this.resolver = null
|
|
77
|
+
this.hide()
|
|
78
|
+
resolver?.(value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get pending(): boolean {
|
|
82
|
+
return this.resolver !== null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Single-line text prompt, used for commit messages and similar input. */
|
|
87
|
+
export class PromptDialog extends Overlay {
|
|
88
|
+
readonly input: InputRenderable
|
|
89
|
+
private readonly label: TextRenderable
|
|
90
|
+
private resolver: ((value: string | null) => void) | null = null
|
|
91
|
+
|
|
92
|
+
constructor(ctx: RenderContext) {
|
|
93
|
+
super(ctx, {
|
|
94
|
+
title: " Input ",
|
|
95
|
+
widthRatio: 0.6,
|
|
96
|
+
minWidth: 40,
|
|
97
|
+
maxWidth: 90,
|
|
98
|
+
heightRatio: 0.25,
|
|
99
|
+
minHeight: 7,
|
|
100
|
+
maxHeight: 9,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
this.label = new TextRenderable(ctx, {
|
|
104
|
+
content: "",
|
|
105
|
+
fg: theme.textBright,
|
|
106
|
+
height: 1,
|
|
107
|
+
marginTop: 1,
|
|
108
|
+
selectable: false,
|
|
109
|
+
truncate: true,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const inputWrapper = new BoxRenderable(ctx, {
|
|
113
|
+
height: 3,
|
|
114
|
+
marginTop: 1,
|
|
115
|
+
border: true,
|
|
116
|
+
borderColor: theme.border,
|
|
117
|
+
backgroundColor: theme.panelAlt,
|
|
118
|
+
paddingLeft: 1,
|
|
119
|
+
paddingRight: 1,
|
|
120
|
+
})
|
|
121
|
+
this.input = new InputRenderable(ctx, {
|
|
122
|
+
backgroundColor: theme.panelAlt,
|
|
123
|
+
textColor: theme.textBright,
|
|
124
|
+
placeholderColor: theme.textDim,
|
|
125
|
+
})
|
|
126
|
+
inputWrapper.add(this.input)
|
|
127
|
+
|
|
128
|
+
const hints = new TextRenderable(ctx, {
|
|
129
|
+
content: styled(
|
|
130
|
+
chunk("enter", theme.success),
|
|
131
|
+
dim(" submit "),
|
|
132
|
+
chunk("esc", theme.danger),
|
|
133
|
+
dim(" cancel"),
|
|
134
|
+
),
|
|
135
|
+
height: 1,
|
|
136
|
+
marginTop: 1,
|
|
137
|
+
selectable: false,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
this.box.add(this.label)
|
|
141
|
+
this.box.add(inputWrapper)
|
|
142
|
+
this.box.add(hints)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
ask(title: string, placeholder = ""): Promise<string | null> {
|
|
146
|
+
this.resolve(null)
|
|
147
|
+
this.label.content = title
|
|
148
|
+
this.input.value = ""
|
|
149
|
+
this.input.placeholder = placeholder
|
|
150
|
+
this.show()
|
|
151
|
+
this.input.focus()
|
|
152
|
+
return new Promise<string | null>((resolve) => {
|
|
153
|
+
this.resolver = resolve
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
resolve(value: string | null): void {
|
|
158
|
+
const resolver = this.resolver
|
|
159
|
+
this.resolver = null
|
|
160
|
+
if (resolver) {
|
|
161
|
+
this.input.blur()
|
|
162
|
+
this.hide()
|
|
163
|
+
resolver(value)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
submit(): void {
|
|
168
|
+
this.resolve(this.input.value)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
get pending(): boolean {
|
|
172
|
+
return this.resolver !== null
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { BoxRenderable, TextRenderable, type RenderContext, type StyledText } from "@opentui/core"
|
|
2
|
+
import { theme } from "../app/theme.js"
|
|
3
|
+
import { chunk, dim, joinStyled, styled } from "./styled.js"
|
|
4
|
+
|
|
5
|
+
export interface KeyHint {
|
|
6
|
+
keys: string
|
|
7
|
+
label: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Bottom bar: context-sensitive key hints on the left, transient status on the
|
|
12
|
+
* right. Hints are dropped from the end when the terminal is too narrow.
|
|
13
|
+
*/
|
|
14
|
+
export class Footer {
|
|
15
|
+
readonly box: BoxRenderable
|
|
16
|
+
private readonly hints: TextRenderable
|
|
17
|
+
private readonly status: TextRenderable
|
|
18
|
+
private currentHints: KeyHint[] = []
|
|
19
|
+
private width = 80
|
|
20
|
+
|
|
21
|
+
constructor(ctx: RenderContext) {
|
|
22
|
+
this.box = new BoxRenderable(ctx, {
|
|
23
|
+
height: 2,
|
|
24
|
+
flexShrink: 0,
|
|
25
|
+
flexDirection: "row",
|
|
26
|
+
alignItems: "center",
|
|
27
|
+
paddingLeft: 1,
|
|
28
|
+
paddingRight: 1,
|
|
29
|
+
border: ["top"],
|
|
30
|
+
borderColor: theme.border,
|
|
31
|
+
backgroundColor: theme.footerBackground,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
this.hints = new TextRenderable(ctx, {
|
|
35
|
+
content: "",
|
|
36
|
+
fg: theme.textDim,
|
|
37
|
+
height: 1,
|
|
38
|
+
flexGrow: 1,
|
|
39
|
+
flexShrink: 1,
|
|
40
|
+
selectable: false,
|
|
41
|
+
truncate: true,
|
|
42
|
+
})
|
|
43
|
+
this.status = new TextRenderable(ctx, {
|
|
44
|
+
content: "",
|
|
45
|
+
fg: theme.textDim,
|
|
46
|
+
height: 1,
|
|
47
|
+
flexShrink: 0,
|
|
48
|
+
selectable: false,
|
|
49
|
+
truncate: true,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
this.box.add(this.hints)
|
|
53
|
+
this.box.add(this.status)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setWidth(width: number): void {
|
|
57
|
+
this.width = width
|
|
58
|
+
this.render()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setHints(hints: KeyHint[]): void {
|
|
62
|
+
this.currentHints = hints
|
|
63
|
+
this.render()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
setStatus(message: string | null, level: "info" | "error" | "success" = "info"): void {
|
|
67
|
+
this.status.content = message ? ` ${message} ` : ""
|
|
68
|
+
this.status.fg = level === "error" ? theme.danger : level === "success" ? theme.success : theme.textDim
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private render(): void {
|
|
72
|
+
const budget = Math.max(10, this.width - 26)
|
|
73
|
+
const parts: StyledText[] = []
|
|
74
|
+
let used = 0
|
|
75
|
+
|
|
76
|
+
for (const hint of this.currentHints) {
|
|
77
|
+
const length = hint.keys.length + hint.label.length + 4
|
|
78
|
+
if (used + length > budget) break
|
|
79
|
+
used += length
|
|
80
|
+
parts.push(styled(chunk(hint.keys, theme.text), " ", dim(hint.label)))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.hints.content = joinStyled(parts, " ")
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { BoxRenderable, fg, t, TextRenderable, type RenderContext } from "@opentui/core"
|
|
2
|
+
import { theme } from "../app/theme.js"
|
|
3
|
+
import type { AppState } from "../core/state.js"
|
|
4
|
+
import { formatClock } from "../utils/format.js"
|
|
5
|
+
import { truncatePath, tildify } from "../utils/paths.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Top bar: product name on the left, project identity and clock on the right.
|
|
9
|
+
* Collapses to just the project name on narrow terminals.
|
|
10
|
+
*/
|
|
11
|
+
/** `HH:MM` plus a trailing space. */
|
|
12
|
+
const CLOCK_WIDTH = 6
|
|
13
|
+
|
|
14
|
+
export class Header {
|
|
15
|
+
readonly box: BoxRenderable
|
|
16
|
+
private readonly title: TextRenderable
|
|
17
|
+
private readonly spacer: BoxRenderable
|
|
18
|
+
private readonly meta: TextRenderable
|
|
19
|
+
private readonly clock: TextRenderable
|
|
20
|
+
|
|
21
|
+
constructor(ctx: RenderContext) {
|
|
22
|
+
this.box = new BoxRenderable(ctx, {
|
|
23
|
+
height: 2,
|
|
24
|
+
flexShrink: 0,
|
|
25
|
+
flexDirection: "row",
|
|
26
|
+
alignItems: "center",
|
|
27
|
+
paddingLeft: 1,
|
|
28
|
+
paddingRight: 1,
|
|
29
|
+
border: ["bottom"],
|
|
30
|
+
borderColor: theme.border,
|
|
31
|
+
backgroundColor: theme.headerBackground,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
this.title = new TextRenderable(ctx, {
|
|
35
|
+
content: "DEV COMMAND CENTER",
|
|
36
|
+
fg: theme.accent,
|
|
37
|
+
height: 1,
|
|
38
|
+
selectable: false,
|
|
39
|
+
truncate: true,
|
|
40
|
+
})
|
|
41
|
+
// A flexible spacer absorbs the slack, so the project block and clock sit
|
|
42
|
+
// flush right without any padding arithmetic.
|
|
43
|
+
this.spacer = new BoxRenderable(ctx, {
|
|
44
|
+
flexGrow: 1,
|
|
45
|
+
flexShrink: 1,
|
|
46
|
+
height: 1,
|
|
47
|
+
backgroundColor: "transparent",
|
|
48
|
+
})
|
|
49
|
+
this.meta = new TextRenderable(ctx, {
|
|
50
|
+
content: "",
|
|
51
|
+
fg: theme.textDim,
|
|
52
|
+
height: 1,
|
|
53
|
+
marginLeft: 2,
|
|
54
|
+
flexShrink: 1,
|
|
55
|
+
selectable: false,
|
|
56
|
+
truncate: true,
|
|
57
|
+
})
|
|
58
|
+
// The clock never gets truncated away: it sits outside the flexible column.
|
|
59
|
+
this.clock = new TextRenderable(ctx, {
|
|
60
|
+
content: "",
|
|
61
|
+
fg: theme.text,
|
|
62
|
+
height: 1,
|
|
63
|
+
// Fixed width so the flexible project block can never squeeze it out.
|
|
64
|
+
width: CLOCK_WIDTH,
|
|
65
|
+
flexShrink: 0,
|
|
66
|
+
selectable: false,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
this.box.add(this.title)
|
|
70
|
+
this.box.add(this.spacer)
|
|
71
|
+
this.box.add(this.meta)
|
|
72
|
+
this.box.add(this.clock)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
update(state: AppState): void {
|
|
76
|
+
const { width } = state.ui
|
|
77
|
+
const narrow = width < 64
|
|
78
|
+
|
|
79
|
+
this.title.content = narrow ? "DEVCC" : "DEV COMMAND CENTER"
|
|
80
|
+
|
|
81
|
+
const clockText = formatClock(state.ui.clock)
|
|
82
|
+
this.clock.content = t`${fg(theme.text)(clockText)} `
|
|
83
|
+
|
|
84
|
+
// Drop detail as the terminal narrows rather than letting it be clipped.
|
|
85
|
+
const pieces = [state.project.name]
|
|
86
|
+
if (width >= 64) pieces.push(state.project.packageManager.manager)
|
|
87
|
+
if (width >= 96)
|
|
88
|
+
pieces.push(truncatePath(tildify(state.project.rootDir), Math.max(12, Math.floor(width / 3))))
|
|
89
|
+
|
|
90
|
+
const label = pieces.join(" · ")
|
|
91
|
+
this.meta.content = t`${fg(theme.textDim)(label)} `
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { RenderContext } from "@opentui/core"
|
|
2
|
+
import { theme } from "../app/theme.js"
|
|
3
|
+
import { fit } from "../utils/format.js"
|
|
4
|
+
import { Overlay } from "./Overlay.js"
|
|
5
|
+
import { RowList, type ListRow } from "./primitives.js"
|
|
6
|
+
import { chunk, dim, styled } from "./styled.js"
|
|
7
|
+
|
|
8
|
+
export interface HelpEntry {
|
|
9
|
+
keys: string
|
|
10
|
+
description: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HelpSection {
|
|
14
|
+
title: string
|
|
15
|
+
entries: HelpEntry[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Scrollable in-app manual (`?` / `F1`). */
|
|
19
|
+
export class HelpOverlay extends Overlay {
|
|
20
|
+
private readonly list: RowList
|
|
21
|
+
|
|
22
|
+
constructor(ctx: RenderContext, sections: HelpSection[]) {
|
|
23
|
+
super(ctx, {
|
|
24
|
+
title: " Manual ",
|
|
25
|
+
// Wide enough that a full description line fits on an 80-column terminal.
|
|
26
|
+
widthRatio: 0.85,
|
|
27
|
+
minWidth: 60,
|
|
28
|
+
maxWidth: 100,
|
|
29
|
+
heightRatio: 0.85,
|
|
30
|
+
minHeight: 10,
|
|
31
|
+
maxHeight: 40,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
this.list = new RowList(ctx, { marginTop: 1, flexGrow: 1, scrollPadding: 0 })
|
|
35
|
+
this.box.add(this.list.box)
|
|
36
|
+
this.setSections(sections)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setSections(sections: HelpSection[]): void {
|
|
40
|
+
const rows: ListRow[] = []
|
|
41
|
+
for (const [index, section] of sections.entries()) {
|
|
42
|
+
if (index > 0) rows.push({ key: `spacer-${index}`, content: "", disabled: true })
|
|
43
|
+
rows.push({
|
|
44
|
+
key: `section-${section.title}`,
|
|
45
|
+
content: styled(chunk(section.title.toUpperCase(), theme.accent)),
|
|
46
|
+
disabled: true,
|
|
47
|
+
})
|
|
48
|
+
for (const entry of section.entries) {
|
|
49
|
+
rows.push({
|
|
50
|
+
key: `${section.title}-${entry.keys}`,
|
|
51
|
+
content: styled(chunk(fit(entry.keys, 14), theme.textBright), dim(entry.description)),
|
|
52
|
+
disabled: true,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this.list.setRows(rows, 0)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
scroll(delta: number): void {
|
|
60
|
+
this.list.scrollBy(delta)
|
|
61
|
+
}
|
|
62
|
+
}
|