Skip to content

Commit d5fbe5d

Browse files
committed
feat(tui): implement interactive theme dependency resolution prompts
1 parent eacc75c commit d5fbe5d

1 file changed

Lines changed: 159 additions & 23 deletions

File tree

go/tui/tui.go

Lines changed: 159 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ type serverStatusMsg struct {
135135
Running bool
136136
}
137137

138+
type promptThemeDepMsg struct {
139+
DepName string
140+
ShortName string
141+
}
142+
143+
type themeDepDecision struct {
144+
install bool
145+
method string
146+
}
147+
138148
type tickMsg time.Time
139149

140150
type memTickMsg struct {
@@ -171,6 +181,8 @@ const (
171181
PromptManagePackage
172182
PromptForceReinstallConfirm
173183
PromptSettings
184+
PromptThemeDepConfirm
185+
PromptThemeDepMethod
174186
)
175187

176188
var Program *tea.Program
@@ -246,6 +258,9 @@ type TuiModel struct {
246258
finalizedAdds map[string]string // pkgName -> method
247259
finalizedRemoves []string // pkgNames
248260
finalizedTheme *string // theme name to activate
261+
262+
themeDepResolveChan chan themeDepDecision
263+
activeThemeDep string
249264
}
250265

251266
func NewModel(root string) TuiModel {
@@ -266,6 +281,7 @@ func NewModel(root string) TuiModel {
266281
pendingPackages: make(map[string]bool),
267282
pendingTheme: nil,
268283
finalizedAdds: make(map[string]string),
284+
themeDepResolveChan: make(chan themeDepDecision, 1),
269285
}
270286
}
271287

@@ -899,6 +915,29 @@ func (m TuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
899915
} else {
900916
m.statusMsg = "All packages are up to date."
901917
}
918+
case promptThemeDepMsg:
919+
m.activePrompt = PromptThemeDepConfirm
920+
m.activeThemeDep = msg.DepName
921+
m.promptSel = 0 // default to Yes (0: Yes, 1: No)
922+
923+
// Find catalog entry for this dep
924+
var entry *bridge.CatalogEntry
925+
if m.catalog != nil {
926+
for _, e := range m.catalog.Entries {
927+
if e.Name == msg.DepName {
928+
entry = &e
929+
break
930+
}
931+
}
932+
}
933+
if entry == nil {
934+
entry = &bridge.CatalogEntry{
935+
Name: msg.DepName,
936+
ShortName: msg.ShortName,
937+
Kind: "module",
938+
}
939+
}
940+
m.promptPkg = entry
902941
return m, nil
903942

904943
case logLineMsg:
@@ -953,10 +992,14 @@ func (m *TuiModel) addLog(line string) {
953992
func (m TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
954993
switch msg.String() {
955994
case "esc", "q":
995+
if m.activePrompt == PromptThemeDepConfirm || m.activePrompt == PromptThemeDepMethod {
996+
m.themeDepResolveChan <- themeDepDecision{install: false}
997+
}
956998
m.activePrompt = PromptNone
957999
m.promptPkg = nil
9581000
m.promptQueue = nil
9591001
m.promptQueueIndex = 0
1002+
m.activeThemeDep = ""
9601003
return m, nil
9611004
case "up", "k":
9621005
if m.activePrompt == PromptManagePackage {
@@ -985,9 +1028,9 @@ func (m TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
9851028
}
9861029
} else {
9871030
limit := 2
988-
if m.activePrompt == PromptUninstallConfirm || m.activePrompt == PromptForceReinstallConfirm {
1031+
if m.activePrompt == PromptUninstallConfirm || m.activePrompt == PromptForceReinstallConfirm || m.activePrompt == PromptThemeDepConfirm {
9891032
limit = 1
990-
} else if m.activePrompt == PromptInstallMethod && m.promptPkg != nil {
1033+
} else if (m.activePrompt == PromptInstallMethod || m.activePrompt == PromptThemeDepMethod) && m.promptPkg != nil {
9911034
limit = len(m.getInstallMethods(m.promptPkg)) - 1
9921035
}
9931036
if m.promptSel < limit {
@@ -999,7 +1042,7 @@ func (m TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
9991042
if m.settingsSel == 5 {
10001043
m.settingsSel = 4
10011044
}
1002-
} else if m.activePrompt != PromptManagePackage && m.activePrompt != PromptInstallMethod {
1045+
} else if m.activePrompt != PromptManagePackage && m.activePrompt != PromptInstallMethod && m.activePrompt != PromptThemeDepMethod {
10031046
if m.promptSel > 0 {
10041047
m.promptSel--
10051048
}
@@ -1009,9 +1052,9 @@ func (m TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
10091052
if m.settingsSel == 4 {
10101053
m.settingsSel = 5
10111054
}
1012-
} else if m.activePrompt != PromptManagePackage && m.activePrompt != PromptInstallMethod {
1055+
} else if m.activePrompt != PromptManagePackage && m.activePrompt != PromptInstallMethod && m.activePrompt != PromptThemeDepMethod {
10131056
limit := 2
1014-
if m.activePrompt == PromptUninstallConfirm || m.activePrompt == PromptForceReinstallConfirm {
1057+
if m.activePrompt == PromptUninstallConfirm || m.activePrompt == PromptForceReinstallConfirm || m.activePrompt == PromptThemeDepConfirm {
10151058
limit = 1
10161059
}
10171060
if m.promptSel < limit {
@@ -1022,6 +1065,41 @@ func (m TuiModel) handlePromptKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
10221065
pkg := m.promptPkg
10231066
prompt := m.activePrompt
10241067

1068+
if prompt == PromptThemeDepConfirm {
1069+
if m.promptSel == 0 { // Yes
1070+
m.activePrompt = PromptThemeDepMethod
1071+
methods := m.getInstallMethods(pkg)
1072+
selIdx := 0
1073+
for idx, mth := range methods {
1074+
if mth.Name == m.lastInstallMethod {
1075+
selIdx = idx
1076+
break
1077+
}
1078+
}
1079+
m.promptSel = selIdx
1080+
return m, nil
1081+
} else { // No
1082+
m.activePrompt = PromptNone
1083+
m.promptPkg = nil
1084+
m.activeThemeDep = ""
1085+
m.themeDepResolveChan <- themeDepDecision{install: false}
1086+
return m, nil
1087+
}
1088+
} else if prompt == PromptThemeDepMethod {
1089+
m.activePrompt = PromptNone
1090+
m.promptPkg = nil
1091+
m.activeThemeDep = ""
1092+
methods := m.getInstallMethods(pkg)
1093+
if m.promptSel >= 0 && m.promptSel < len(methods) {
1094+
selectedMethod := methods[m.promptSel].Name
1095+
m.lastInstallMethod = selectedMethod
1096+
m.themeDepResolveChan <- themeDepDecision{install: true, method: selectedMethod}
1097+
} else {
1098+
m.themeDepResolveChan <- themeDepDecision{install: false}
1099+
}
1100+
return m, nil
1101+
}
1102+
10251103
if len(m.promptQueue) > 0 {
10261104
if prompt == PromptUninstallConfirm {
10271105
if m.promptSel == 0 { // Yes
@@ -2571,6 +2649,46 @@ func (m TuiModel) renderModal() string {
25712649
}
25722650
content.WriteString("\n" + subtleStyle.Render("↑↓ select Enter confirm Esc cancel"))
25732651
return modalStyle.Width(72).Render(content.String())
2652+
} else if m.activePrompt == PromptThemeDepMethod {
2653+
content.WriteString(boldStyle.Render("Install source for theme dependency: ") + accentStyle.Render(pkg.ShortName) + "\n\n")
2654+
methods := m.getInstallMethods(pkg)
2655+
for i, mth := range methods {
2656+
if i > 0 {
2657+
if mth.Name != "local" && methods[i-1].Name == "local" {
2658+
content.WriteString("\n " + subtleStyle.Render(strings.Repeat("─", 62)) + "\n\n")
2659+
} else {
2660+
content.WriteString("\n")
2661+
}
2662+
}
2663+
var btn string
2664+
if i == m.promptSel {
2665+
btn = modalOptionActive.Render(mth.Label)
2666+
} else {
2667+
btn = modalOptionInactive.Render(mth.Label)
2668+
}
2669+
content.WriteString(" " + btn + "\n")
2670+
content.WriteString(" " + mutedStyle.Render(mth.Desc) + "\n")
2671+
}
2672+
content.WriteString("\n" + subtleStyle.Render("↑↓ select Enter confirm Esc cancel"))
2673+
return modalStyle.Width(72).Render(content.String())
2674+
} else if m.activePrompt == PromptThemeDepConfirm {
2675+
content.WriteString(boldStyle.Render("Theme dependency required: ") + accentStyle.Render(pkg.ShortName) + "\n")
2676+
content.WriteString(subtleStyle.Render("The active or queued theme requires this package to function correctly.") + "\n\n")
2677+
content.WriteString(boldStyle.Render("Install ") + accentStyle.Render(pkg.ShortName) + boldStyle.Render("?") + "\n\n")
2678+
opts := []string{"Yes, install", "No, skip"}
2679+
for i, opt := range opts {
2680+
if i == m.promptSel {
2681+
bg := colorAccent
2682+
if i == 1 {
2683+
bg = colorErr
2684+
}
2685+
content.WriteString(lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color("#000000")).Bold(true).Padding(0, 2).Render(" "+opt+" "))
2686+
} else {
2687+
content.WriteString(modalOptionInactive.Render(" " + opt + " "))
2688+
}
2689+
content.WriteString(" ")
2690+
}
2691+
content.WriteString("\n\n" + subtleStyle.Render("← → select Enter confirm Esc cancel"))
25742692
} else if m.activePrompt == PromptUninstallConfirm {
25752693
content.WriteString(boldStyle.Render("Uninstall ") + errStyle.Render(pkg.ShortName) + boldStyle.Render("?") + "\n\n")
25762694
opts := []string{"Yes, uninstall", "No, keep it"}
@@ -2941,33 +3059,51 @@ func (m *TuiModel) RunSetupTask(adds map[string]string, p *tea.Program) {
29413059
}
29423060
}
29433061
}
3062+
if !installed {
3063+
if _, exists := adds[dep]; exists {
3064+
installed = true
3065+
}
3066+
}
29443067

29453068
if !installed {
29463069
depShort := dep
29473070
if idx := strings.LastIndex(dep, "/"); idx >= 0 {
29483071
depShort = dep[idx+1:]
29493072
}
29503073

2951-
p.Send(logLineMsg(fmt.Sprintf(">>> Installing theme dependency: %s…", depShort)))
2952-
depArgs := []string{desktopJs, "add", depShort}
2953-
if method == "npm" {
2954-
depArgs = append(depArgs, "--npm")
2955-
} else {
2956-
depOwner := "owdproject"
2957-
if m.ctx != nil && m.ctx.Settings.GithubUser != nil && *m.ctx.Settings.GithubUser != "" {
2958-
depOwner = *m.ctx.Settings.GithubUser
2959-
}
2960-
2961-
var fromVal string
2962-
if method == "git-ssh" {
2963-
fromVal = fmt.Sprintf("git@github.com:%s/%s.git", depOwner, depShort)
3074+
p.Send(logLineMsg(fmt.Sprintf(">>> Theme dependency detected: %s", depShort)))
3075+
p.Send(promptThemeDepMsg{
3076+
DepName: dep,
3077+
ShortName: depShort,
3078+
})
3079+
3080+
decision := <-m.themeDepResolveChan
3081+
if decision.install {
3082+
p.Send(logLineMsg(fmt.Sprintf(">>> Installing theme dependency: %s via %s…", depShort, decision.method)))
3083+
depArgs := []string{desktopJs, "add", depShort}
3084+
if decision.method == "npm" {
3085+
depArgs = append(depArgs, "--npm")
3086+
} else if decision.method == "local" {
3087+
depArgs = append(depArgs, "--dev")
29643088
} else {
2965-
fromVal = fmt.Sprintf("https://github.com/%s/%s.git", depOwner, depShort)
3089+
depOwner := "owdproject"
3090+
if m.ctx != nil && m.ctx.Settings.GithubUser != nil && *m.ctx.Settings.GithubUser != "" {
3091+
depOwner = *m.ctx.Settings.GithubUser
3092+
}
3093+
3094+
var fromVal string
3095+
if decision.method == "git-ssh" {
3096+
fromVal = fmt.Sprintf("git@github.com:%s/%s.git", depOwner, depShort)
3097+
} else {
3098+
fromVal = fmt.Sprintf("https://github.com/%s/%s.git", depOwner, depShort)
3099+
}
3100+
depArgs = append(depArgs, "--from", fromVal)
29663101
}
2967-
depArgs = append(depArgs, "--from", fromVal)
2968-
}
2969-
if err := runProcessAndStreamLogsSilent(m.workspaceRoot, "node", depArgs, p); err != nil {
2970-
p.Send(logLineMsg(fmt.Sprintf(">>> Dependency %s failed: %v", depShort, err)))
3102+
if err := runProcessAndStreamLogsSilent(m.workspaceRoot, "node", depArgs, p); err != nil {
3103+
p.Send(logLineMsg(fmt.Sprintf(">>> Dependency %s failed: %v", depShort, err)))
3104+
}
3105+
} else {
3106+
p.Send(logLineMsg(fmt.Sprintf(">>> Skipped installing theme dependency: %s", depShort)))
29713107
}
29723108
}
29733109
}

0 commit comments

Comments
 (0)