@owdproject/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bin/bridge.js +4 -2
- package/bin/cli.js +63 -5
- package/bin/cp.js +42 -23
- package/bin/lib/catalog.js +3 -0
- package/bin/lib/config.js +51 -8
- package/bin/lib/cpFormat.js +2 -2
- package/bin/lib/create.js +246 -0
- package/bin/lib/desktopConfig.js +48 -8
- package/bin/lib/generateTemplate.js +2 -1
- package/bin/lib/install.js +17 -21
- package/bin/lib/installWizardFlow.js +90 -24
- package/bin/lib/packageSources.js +10 -0
- package/bin/lib/status.js +2 -0
- package/bin/lib/validateModule.js +112 -2
- package/bin/lib/workspace.js +68 -1
- package/go/bridge/bridge.go +20 -0
- package/go/desktop-go +0 -0
- package/go/go.mod +17 -10
- package/go/go.sum +34 -19
- package/go/main.go +7 -3
- package/go/owd-cli +0 -0
- package/go/tui/engine.go +472 -0
- package/go/tui/engine_test.go +40 -0
- package/go/tui/handlers.go +604 -0
- package/go/tui/helpers.go +916 -0
- package/go/tui/helpers_test.go +97 -0
- package/go/tui/modals.go +423 -0
- package/go/tui/resolver.go +377 -0
- package/go/tui/resolver_test.go +86 -0
- package/go/tui/styles.go +153 -0
- package/go/tui/tasks.go +850 -0
- package/go/tui/tui.go +451 -2596
- package/go/tui/tui_test.go +69 -0
- package/go/tui/types.go +230 -0
- package/go/tui/view.go +812 -0
- package/go/tui/workqueue.go +179 -0
- package/go/tui/workqueue_test.go +90 -0
- package/package.json +2 -2
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
package tui
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"testing"
|
|
5
|
+
|
|
6
|
+
"owd-cli/bridge"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func TestCatalogSourceLabel(t *testing.T) {
|
|
10
|
+
tests := []struct {
|
|
11
|
+
name string
|
|
12
|
+
item bridge.CatalogEntry
|
|
13
|
+
localGitDirs map[string]bool
|
|
14
|
+
want string
|
|
15
|
+
}{
|
|
16
|
+
{
|
|
17
|
+
name: "not installed",
|
|
18
|
+
item: bridge.CatalogEntry{
|
|
19
|
+
ShortName: "module-foo",
|
|
20
|
+
Installed: false,
|
|
21
|
+
},
|
|
22
|
+
want: "—",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "installed via npm",
|
|
26
|
+
item: bridge.CatalogEntry{
|
|
27
|
+
ShortName: "module-foo",
|
|
28
|
+
Installed: true,
|
|
29
|
+
InPackageJson: true,
|
|
30
|
+
},
|
|
31
|
+
want: "npm",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "local source with git",
|
|
35
|
+
item: bridge.CatalogEntry{
|
|
36
|
+
ShortName: "module-foo",
|
|
37
|
+
Installed: true,
|
|
38
|
+
LocalSource: true,
|
|
39
|
+
},
|
|
40
|
+
localGitDirs: map[string]bool{"module-foo": true},
|
|
41
|
+
want: "git",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "local source without git",
|
|
45
|
+
item: bridge.CatalogEntry{
|
|
46
|
+
ShortName: "module-foo",
|
|
47
|
+
Installed: true,
|
|
48
|
+
LocalSource: true,
|
|
49
|
+
},
|
|
50
|
+
localGitDirs: map[string]bool{},
|
|
51
|
+
want: "dev",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "installed in config but missing folder",
|
|
55
|
+
item: bridge.CatalogEntry{
|
|
56
|
+
ShortName: "module-foo",
|
|
57
|
+
Installed: true,
|
|
58
|
+
LocalSource: false,
|
|
59
|
+
InPackageJson: false,
|
|
60
|
+
},
|
|
61
|
+
want: "—",
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for _, tt := range tests {
|
|
66
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
67
|
+
got := catalogSourceLabel(tt.item, tt.localGitDirs)
|
|
68
|
+
if got != tt.want {
|
|
69
|
+
t.Fatalf("catalogSourceLabel() = %q, want %q", got, tt.want)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func TestActiveThemeName(t *testing.T) {
|
|
76
|
+
nova := "@owdproject/theme-nova"
|
|
77
|
+
gnome := "@owdproject/theme-gnome"
|
|
78
|
+
ctx := &bridge.WorkspaceContext{
|
|
79
|
+
Config: bridge.Config{Theme: &nova},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if got := activeThemeName(ctx, nil); got != nova {
|
|
83
|
+
t.Fatalf("expected saved theme %q, got %q", nova, got)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if got := activeThemeName(ctx, &gnome); got != gnome {
|
|
87
|
+
t.Fatalf("expected pending theme %q, got %q", gnome, got)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if got := activeThemeName(nil, &gnome); got != gnome {
|
|
91
|
+
t.Fatalf("expected pending theme without ctx %q, got %q", gnome, got)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if got := activeThemeName(nil, nil); got != "" {
|
|
95
|
+
t.Fatalf("expected empty theme, got %q", got)
|
|
96
|
+
}
|
|
97
|
+
}
|
package/go/tui/modals.go
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
package tui
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"owd-cli/bridge"
|
|
10
|
+
|
|
11
|
+
"github.com/charmbracelet/lipgloss"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// ─────────────────────────────────────────────
|
|
15
|
+
// ProgressBar & Modal Renderers
|
|
16
|
+
// ─────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
func renderProgressBar(step, total, width int) string {
|
|
19
|
+
if total <= 0 {
|
|
20
|
+
return progressBarEmptyStyle.Render(strings.Repeat("░", width))
|
|
21
|
+
}
|
|
22
|
+
filledWidth := (step * width) / total
|
|
23
|
+
if filledWidth > width {
|
|
24
|
+
filledWidth = width
|
|
25
|
+
}
|
|
26
|
+
if filledWidth < 0 {
|
|
27
|
+
filledWidth = 0
|
|
28
|
+
}
|
|
29
|
+
emptyWidth := width - filledWidth
|
|
30
|
+
filled := progressBarFilledStyle.Render(strings.Repeat("█", filledWidth))
|
|
31
|
+
empty := progressBarEmptyStyle.Render(strings.Repeat("░", emptyWidth))
|
|
32
|
+
return filled + empty
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func (m *TuiModel) renderModal(prompt PromptType) string {
|
|
36
|
+
pkg := m.promptPkg
|
|
37
|
+
switch prompt {
|
|
38
|
+
case PromptInstallMethod:
|
|
39
|
+
return m.renderInstallMethodModal(pkg)
|
|
40
|
+
case PromptUninstallConfirm:
|
|
41
|
+
return m.renderUninstallConfirmModal(pkg)
|
|
42
|
+
case PromptForceReinstallConfirm:
|
|
43
|
+
return m.renderForceReinstallConfirmModal(pkg)
|
|
44
|
+
case PromptResolveDependency:
|
|
45
|
+
return m.renderResolveDependencyModal()
|
|
46
|
+
case PromptSetupProgress:
|
|
47
|
+
return m.renderSetupProgressModal()
|
|
48
|
+
case PromptManagePackage:
|
|
49
|
+
return m.renderManagePackageModal(pkg)
|
|
50
|
+
case PromptSettings:
|
|
51
|
+
return m.renderSettingsModal()
|
|
52
|
+
case PromptWipeWorkspaceConfirm:
|
|
53
|
+
return m.renderWipeWorkspaceConfirmModal()
|
|
54
|
+
default:
|
|
55
|
+
return ""
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func (m *TuiModel) renderInstallMethodModal(pkg *bridge.CatalogEntry) string {
|
|
60
|
+
var content strings.Builder
|
|
61
|
+
content.WriteString(boldStyle.Render("Install source for ") + accentStyle.Render(pkg.ShortName) + "\n\n")
|
|
62
|
+
methods := m.getInstallMethods(pkg)
|
|
63
|
+
for i, mth := range methods {
|
|
64
|
+
if i > 0 {
|
|
65
|
+
if mth.Name != "local" && methods[i-1].Name == "local" {
|
|
66
|
+
content.WriteString("\n " + subtleStyle.Render(strings.Repeat("─", 62)) + "\n\n")
|
|
67
|
+
} else {
|
|
68
|
+
content.WriteString("\n")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
var btn string
|
|
72
|
+
if i == m.promptSel {
|
|
73
|
+
btn = modalOptionActive.Render(mth.Label)
|
|
74
|
+
} else {
|
|
75
|
+
btn = modalOptionInactive.Render(mth.Label)
|
|
76
|
+
}
|
|
77
|
+
content.WriteString(" " + btn + "\n")
|
|
78
|
+
content.WriteString(" " + mutedStyle.Render(mth.Desc) + "\n")
|
|
79
|
+
}
|
|
80
|
+
content.WriteString("\n" + subtleStyle.Render("↑↓ select Enter confirm Esc cancel"))
|
|
81
|
+
return modalStyle.Width(72).Render(content.String())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func (m *TuiModel) renderUninstallConfirmModal(pkg *bridge.CatalogEntry) string {
|
|
85
|
+
var content strings.Builder
|
|
86
|
+
content.WriteString(boldStyle.Render("Uninstall ") + errStyle.Render(pkg.ShortName) + boldStyle.Render("?") + "\n\n")
|
|
87
|
+
opts := []string{"Yes, uninstall", "No, keep it"}
|
|
88
|
+
for i, opt := range opts {
|
|
89
|
+
if i == m.promptSel {
|
|
90
|
+
bgStyle := modalConfirmActiveErr
|
|
91
|
+
if i == 1 {
|
|
92
|
+
bgStyle = modalConfirmActiveAccent
|
|
93
|
+
}
|
|
94
|
+
content.WriteString(bgStyle.Render(" "+opt+" "))
|
|
95
|
+
} else {
|
|
96
|
+
content.WriteString(modalOptionInactive.Render(" " + opt + " "))
|
|
97
|
+
}
|
|
98
|
+
content.WriteString(" ")
|
|
99
|
+
}
|
|
100
|
+
content.WriteString("\n\n" + subtleStyle.Render("← → select Enter confirm Esc cancel"))
|
|
101
|
+
return modalStyle.Render(content.String())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func (m *TuiModel) renderForceReinstallConfirmModal(pkg *bridge.CatalogEntry) string {
|
|
105
|
+
var content strings.Builder
|
|
106
|
+
content.WriteString(boldStyle.Render("Force re-download ") + errStyle.Render(pkg.ShortName) + boldStyle.Render("?") + "\n")
|
|
107
|
+
content.WriteString(warnStyle.Render("⚠ WARNING: This will delete the local directory and lose all modifications!") + "\n\n")
|
|
108
|
+
opts := []string{"Yes, wipe & reinstall", "No, cancel"}
|
|
109
|
+
for i, opt := range opts {
|
|
110
|
+
if i == m.promptSel {
|
|
111
|
+
bgStyle := modalConfirmActiveErr
|
|
112
|
+
if i == 1 {
|
|
113
|
+
bgStyle = modalConfirmActiveAccent
|
|
114
|
+
}
|
|
115
|
+
content.WriteString(bgStyle.Render(" "+opt+" "))
|
|
116
|
+
} else {
|
|
117
|
+
content.WriteString(modalOptionInactive.Render(" " + opt + " "))
|
|
118
|
+
}
|
|
119
|
+
content.WriteString(" ")
|
|
120
|
+
}
|
|
121
|
+
content.WriteString("\n\n" + subtleStyle.Render("← → select Enter confirm Esc cancel"))
|
|
122
|
+
return modalStyle.Render(content.String())
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func (m *TuiModel) renderResolveDependencyModal() string {
|
|
126
|
+
var content strings.Builder
|
|
127
|
+
item := m.promptItem
|
|
128
|
+
shortName := "package"
|
|
129
|
+
if item != nil {
|
|
130
|
+
shortName = item.ShortName
|
|
131
|
+
}
|
|
132
|
+
if item != nil && item.Discovered {
|
|
133
|
+
content.WriteString(boldStyle.Render(shortName) + " was discovered as a required dependency\n\n")
|
|
134
|
+
} else {
|
|
135
|
+
content.WriteString(boldStyle.Render(shortName) + " is configured as workspace:*\n\n")
|
|
136
|
+
}
|
|
137
|
+
content.WriteString("How do you want to resolve it?\n\n")
|
|
138
|
+
|
|
139
|
+
var options []struct {
|
|
140
|
+
Name string
|
|
141
|
+
Label string
|
|
142
|
+
}
|
|
143
|
+
if isLocallyAvailable(m.workspaceRoot, shortName) {
|
|
144
|
+
options = append(options, struct{ Name, Label string }{"local", "Use Existing Local Folder"})
|
|
145
|
+
}
|
|
146
|
+
options = append(options,
|
|
147
|
+
struct{ Name, Label string }{"git-ssh", "Git SSH"},
|
|
148
|
+
struct{ Name, Label string }{"git-https", "Git HTTPS"},
|
|
149
|
+
struct{ Name, Label string }{"npm", "NPM Package"},
|
|
150
|
+
)
|
|
151
|
+
for i, opt := range options {
|
|
152
|
+
if i == m.promptSel {
|
|
153
|
+
content.WriteString(" " + modalOptionActive.Render(opt.Label) + "\n")
|
|
154
|
+
} else {
|
|
155
|
+
content.WriteString(" " + modalOptionInactive.Render(opt.Label) + "\n")
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
content.WriteString("\n" + subtleStyle.Render("↑↓ select Enter confirm Esc cancel"))
|
|
159
|
+
return modalStyle.Width(72).Render(content.String())
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func (m *TuiModel) renderSetupProgressModal() string {
|
|
163
|
+
var content strings.Builder
|
|
164
|
+
spinnerFrames := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
|
165
|
+
frame := spinnerFrames[m.tickCount%len(spinnerFrames)]
|
|
166
|
+
spin := spinnerStyle.Render(frame)
|
|
167
|
+
|
|
168
|
+
title := "Installing packages…"
|
|
169
|
+
if strings.Contains(strings.ToLower(m.setupLabel), "clean") || strings.Contains(strings.ToLower(m.setupLabel), "reset") || strings.Contains(strings.ToLower(m.setupLabel), "wipe") {
|
|
170
|
+
title = "Resetting workspace…"
|
|
171
|
+
}
|
|
172
|
+
content.WriteString(boldStyle.Render(title) + "\n\n")
|
|
173
|
+
content.WriteString(" " + spin + " " + boldStyle.Render(m.setupLabel) + "\n\n")
|
|
174
|
+
|
|
175
|
+
if m.setupTotalSteps > 0 {
|
|
176
|
+
bar := renderProgressBar(m.setupStep, m.setupTotalSteps, 40)
|
|
177
|
+
content.WriteString(" " + bar + "\n")
|
|
178
|
+
content.WriteString(fmt.Sprintf(" Progress %d/%d\n", m.setupStep, m.setupTotalSteps))
|
|
179
|
+
if m.enginePhase != PhaseIdle && m.enginePhase != "" {
|
|
180
|
+
content.WriteString(" " + mutedStyle.Render("Phase: "+string(m.enginePhase)) + "\n")
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
content.WriteString(" " + renderProgressBar(0, 1, 40) + "\n")
|
|
184
|
+
content.WriteString(" Preparing...\n")
|
|
185
|
+
}
|
|
186
|
+
return modalStyle.Width(72).Render(content.String())
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func (m *TuiModel) renderManagePackageModal(pkg *bridge.CatalogEntry) string {
|
|
190
|
+
var content strings.Builder
|
|
191
|
+
var leftCols []string
|
|
192
|
+
leftCols = append(leftCols, boldStyle.Render("Package:")+" "+accentStyle.Render(pkg.ShortName))
|
|
193
|
+
leftCols = append(leftCols, mutedStyle.Render("Type: ")+" "+boldStyle.Render(strings.ToUpper(pkg.Kind)))
|
|
194
|
+
|
|
195
|
+
status := mutedStyle.Render("Not Installed")
|
|
196
|
+
if pkg.Installed {
|
|
197
|
+
status = accentStyle.Render("Installed")
|
|
198
|
+
if pkg.LocalSource {
|
|
199
|
+
status += " " + localSourceTag.Render("(workspace)")
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
leftCols = append(leftCols, mutedStyle.Render("Status: ")+" "+status)
|
|
203
|
+
|
|
204
|
+
src := "NPM"
|
|
205
|
+
if pkg.LocalSource {
|
|
206
|
+
src = "LOC (workspace)"
|
|
207
|
+
} else if pkg.Installed && !pkg.LocalSource && !pkg.InPackageJson {
|
|
208
|
+
src = "GIT"
|
|
209
|
+
}
|
|
210
|
+
leftCols = append(leftCols, mutedStyle.Render("Source: ")+" "+boldStyle.Render(src))
|
|
211
|
+
|
|
212
|
+
// Kits
|
|
213
|
+
var kits []string
|
|
214
|
+
for _, dep := range m.promptPkgDeps {
|
|
215
|
+
if strings.HasPrefix(dep, "@owdproject/kit-") {
|
|
216
|
+
shortKit := dep[strings.LastIndex(dep, "/")+1:]
|
|
217
|
+
kits = append(kits, shortKit)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
kitsStr := "none"
|
|
221
|
+
if len(kits) > 0 {
|
|
222
|
+
var styledKits []string
|
|
223
|
+
for _, k := range kits {
|
|
224
|
+
styledKits = append(styledKits, accentStyle.Render(k))
|
|
225
|
+
}
|
|
226
|
+
kitsStr = strings.Join(styledKits, ", ")
|
|
227
|
+
}
|
|
228
|
+
leftCols = append(leftCols, mutedStyle.Render("Kits: ")+" "+kitsStr)
|
|
229
|
+
|
|
230
|
+
gitStatus := "—"
|
|
231
|
+
if pkg.LocalSource {
|
|
232
|
+
kindDir := ""
|
|
233
|
+
switch pkg.Kind {
|
|
234
|
+
case "app":
|
|
235
|
+
kindDir = "apps"
|
|
236
|
+
case "module":
|
|
237
|
+
kindDir = "packages"
|
|
238
|
+
case "theme":
|
|
239
|
+
kindDir = "themes"
|
|
240
|
+
}
|
|
241
|
+
if kindDir != "" {
|
|
242
|
+
pkgPath := filepath.Join(m.workspaceRoot, kindDir, pkg.ShortName)
|
|
243
|
+
gitDir := filepath.Join(pkgPath, ".git")
|
|
244
|
+
if _, err := os.Stat(gitDir); err == nil {
|
|
245
|
+
branch := gitBranch(pkgPath)
|
|
246
|
+
changes := gitChanges(pkgPath)
|
|
247
|
+
gitStatus = fmt.Sprintf("branch: %s (%s)", branch, changes)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
leftCols = append(leftCols, mutedStyle.Render("Git: ")+" "+gitStatus)
|
|
252
|
+
|
|
253
|
+
// NPM & GitHub Links
|
|
254
|
+
npmLink := fmt.Sprintf("https://www.npmjs.com/package/%s", pkg.Name)
|
|
255
|
+
leftCols = append(leftCols, mutedStyle.Render("NPM: ")+" "+cyanStyle.Render(npmLink))
|
|
256
|
+
|
|
257
|
+
ghLink := fmt.Sprintf("https://github.com/owdproject/%s", pkg.ShortName)
|
|
258
|
+
if pkg.HtmlUrl != nil && *pkg.HtmlUrl != "" {
|
|
259
|
+
ghLink = *pkg.HtmlUrl
|
|
260
|
+
}
|
|
261
|
+
leftCols = append(leftCols, mutedStyle.Render("GitHub: ")+" "+cyanStyle.Render(ghLink))
|
|
262
|
+
|
|
263
|
+
// Deps (excluding kits)
|
|
264
|
+
var otherDeps []string
|
|
265
|
+
for _, d := range m.promptPkgDeps {
|
|
266
|
+
if !strings.HasPrefix(d, "@owdproject/kit-") {
|
|
267
|
+
s := d
|
|
268
|
+
if idx := strings.LastIndex(d, "/"); idx >= 0 {
|
|
269
|
+
s = d[idx+1:]
|
|
270
|
+
}
|
|
271
|
+
otherDeps = append(otherDeps, s)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
depsStr := "none"
|
|
275
|
+
if len(otherDeps) > 0 {
|
|
276
|
+
depsStr = strings.Join(otherDeps, ", ")
|
|
277
|
+
}
|
|
278
|
+
leftCols = append(leftCols, mutedStyle.Render("Deps: ")+" "+boldStyle.Render(depsStr))
|
|
279
|
+
|
|
280
|
+
leftPanel := strings.Join(leftCols, "\n")
|
|
281
|
+
|
|
282
|
+
var rightCols []string
|
|
283
|
+
rightCols = append(rightCols, boldStyle.Render("Management Actions:")+"\n")
|
|
284
|
+
|
|
285
|
+
actions := ManagePackageActions
|
|
286
|
+
|
|
287
|
+
for i, act := range actions {
|
|
288
|
+
if i == m.promptSel {
|
|
289
|
+
rightCols = append(rightCols, modalOptionActive.Render(fmt.Sprintf(" %s ", act)))
|
|
290
|
+
} else {
|
|
291
|
+
rightCols = append(rightCols, modalOptionInactive.Render(fmt.Sprintf(" %s ", act)))
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
rightPanel := strings.Join(rightCols, "\n")
|
|
296
|
+
|
|
297
|
+
splitLayout := lipgloss.JoinHorizontal(lipgloss.Top,
|
|
298
|
+
managePackageLeftPanelStyle.Render(leftPanel),
|
|
299
|
+
managePackageRightBorder.Render(rightPanel),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
content.WriteString(splitLayout)
|
|
303
|
+
content.WriteString("\n\n" + subtleStyle.Render("↑↓ select Enter confirm Esc close"))
|
|
304
|
+
|
|
305
|
+
return modalStyle.Width(110).Render(content.String())
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
func (m *TuiModel) renderSettingsModal() string {
|
|
309
|
+
var content strings.Builder
|
|
310
|
+
content.WriteString(boldStyle.Render("Control Panel Settings") + "\n\n")
|
|
311
|
+
|
|
312
|
+
// 1. Install Mode
|
|
313
|
+
modeLabel := "1. Install Mode"
|
|
314
|
+
var modeVal string
|
|
315
|
+
if m.settingsInstallMode == "npm" {
|
|
316
|
+
modeVal = modalOptionActive.Render(" NPM registry ") + " " + modalOptionInactive.Render(" workspace ")
|
|
317
|
+
} else {
|
|
318
|
+
modeVal = modalOptionInactive.Render(" NPM registry ") + " " + modalOptionActive.Render(" workspace ")
|
|
319
|
+
}
|
|
320
|
+
if m.settingsSel == 0 {
|
|
321
|
+
content.WriteString(accentStyle.Render("▶ ") + boldStyle.Render(modeLabel) + "\n " + modeVal + "\n")
|
|
322
|
+
} else {
|
|
323
|
+
content.WriteString(" " + boldStyle.Render(modeLabel) + "\n " + modeVal + "\n")
|
|
324
|
+
}
|
|
325
|
+
content.WriteString("\n")
|
|
326
|
+
|
|
327
|
+
// 2. Catalog Sort
|
|
328
|
+
sortLabel := "2. Catalog Sort"
|
|
329
|
+
var sortVal strings.Builder
|
|
330
|
+
sortModes := []string{"updated", "name", "stars", "installed"}
|
|
331
|
+
for _, sm := range sortModes {
|
|
332
|
+
if sm == m.settingsCatalogSort {
|
|
333
|
+
sortVal.WriteString(modalOptionActive.Render(" "+sm+" ") + " ")
|
|
334
|
+
} else {
|
|
335
|
+
sortVal.WriteString(modalOptionInactive.Render(" "+sm+" ") + " ")
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if m.settingsSel == 1 {
|
|
339
|
+
content.WriteString(accentStyle.Render("▶ ") + boldStyle.Render(sortLabel) + "\n " + sortVal.String() + "\n")
|
|
340
|
+
} else {
|
|
341
|
+
content.WriteString(" " + boldStyle.Render(sortLabel) + "\n " + sortVal.String() + "\n")
|
|
342
|
+
}
|
|
343
|
+
content.WriteString("\n")
|
|
344
|
+
|
|
345
|
+
// 3. Trusted Orgs (Authors)
|
|
346
|
+
orgsLabel := "3. Trusted Orgs"
|
|
347
|
+
var orgsValText string
|
|
348
|
+
if m.settingsSel == 2 {
|
|
349
|
+
orgsValText = m.settingsOrgsInput.View()
|
|
350
|
+
content.WriteString(accentStyle.Render("▶ ") + boldStyle.Render(orgsLabel) + "\n " + orgsValText + "\n")
|
|
351
|
+
} else {
|
|
352
|
+
orgsValText = subtleStyle.Render(m.settingsOrgsInput.Value())
|
|
353
|
+
content.WriteString(" " + boldStyle.Render(orgsLabel) + "\n " + orgsValText + "\n")
|
|
354
|
+
}
|
|
355
|
+
content.WriteString("\n")
|
|
356
|
+
|
|
357
|
+
// 4. GitHub User
|
|
358
|
+
ghLabel := "4. GitHub User"
|
|
359
|
+
var ghValText string
|
|
360
|
+
if m.settingsSel == 3 {
|
|
361
|
+
ghValText = m.settingsUserInput.View()
|
|
362
|
+
content.WriteString(accentStyle.Render("▶ ") + boldStyle.Render(ghLabel) + "\n " + ghValText + "\n")
|
|
363
|
+
} else {
|
|
364
|
+
val := m.settingsUserInput.Value()
|
|
365
|
+
if val == "" {
|
|
366
|
+
val = "(not set)"
|
|
367
|
+
}
|
|
368
|
+
ghValText = subtleStyle.Render(val)
|
|
369
|
+
content.WriteString(" " + boldStyle.Render(ghLabel) + "\n " + ghValText + "\n")
|
|
370
|
+
}
|
|
371
|
+
content.WriteString("\n")
|
|
372
|
+
|
|
373
|
+
// 5. Reset Workspace
|
|
374
|
+
resetLabel := "5. Reset Workspace"
|
|
375
|
+
resetVal := modalOptionInactive.Render(" [ WIPE EVERYTHING ] ")
|
|
376
|
+
if m.settingsSel == 4 {
|
|
377
|
+
resetVal = modalConfirmActiveErr.Render(" [ WIPE EVERYTHING ] ")
|
|
378
|
+
content.WriteString(accentStyle.Render("▶ ") + boldStyle.Render(resetLabel) + "\n " + resetVal + "\n")
|
|
379
|
+
} else {
|
|
380
|
+
content.WriteString(" " + boldStyle.Render(resetLabel) + "\n " + resetVal + "\n")
|
|
381
|
+
}
|
|
382
|
+
content.WriteString("\n\n")
|
|
383
|
+
|
|
384
|
+
// Buttons Row: Save and Cancel
|
|
385
|
+
var saveBtn, cancelBtn string
|
|
386
|
+
if m.settingsSel == 5 {
|
|
387
|
+
saveBtn = settingsSaveActive.Render(" SAVE ")
|
|
388
|
+
} else {
|
|
389
|
+
saveBtn = settingsSaveInactive.Render(" SAVE ")
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if m.settingsSel == 6 {
|
|
393
|
+
cancelBtn = settingsCancelActive.Render(" CANCEL ")
|
|
394
|
+
} else {
|
|
395
|
+
cancelBtn = settingsCancelInactive.Render(" CANCEL ")
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
content.WriteString(" " + saveBtn + " " + cancelBtn + "\n\n")
|
|
399
|
+
content.WriteString(subtleStyle.Render("↑↓ select item ←→ toggle/buttons Enter confirm Esc exit"))
|
|
400
|
+
return modalStyle.Width(72).Render(content.String())
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
func (m *TuiModel) renderWipeWorkspaceConfirmModal() string {
|
|
404
|
+
var content strings.Builder
|
|
405
|
+
content.WriteString(boldStyle.Render("Wipe & Reset Workspace?") + "\n")
|
|
406
|
+
content.WriteString(errStyle.Render("⚠ CAUTION: This will delete ALL non-core applications, modules, and themes!") + "\n")
|
|
407
|
+
content.WriteString(subtleStyle.Render("Your configuration files (desktop.config.ts) and desktop package.json will be reset.") + "\n\n")
|
|
408
|
+
opts := []string{"Yes, wipe everything", "No, cancel"}
|
|
409
|
+
for i, opt := range opts {
|
|
410
|
+
if i == m.promptSel {
|
|
411
|
+
bgStyle := modalConfirmActiveErr
|
|
412
|
+
if i == 1 {
|
|
413
|
+
bgStyle = modalConfirmActiveAccent
|
|
414
|
+
}
|
|
415
|
+
content.WriteString(bgStyle.Render(" " + opt + " "))
|
|
416
|
+
} else {
|
|
417
|
+
content.WriteString(modalOptionInactive.Render(" " + opt + " "))
|
|
418
|
+
}
|
|
419
|
+
content.WriteString(" ")
|
|
420
|
+
}
|
|
421
|
+
content.WriteString("\n\n" + subtleStyle.Render("← → select Enter confirm Esc cancel"))
|
|
422
|
+
return modalStyle.Render(content.String())
|
|
423
|
+
}
|