Files
Spark 348036daf3 feat(gui): Quick Picker, Gestionnaire, Éditeur, formulaire de variables, Paramètres
- Picker : recherche instantanée, aperçu, navigation clavier complète,
  raccourcis actifs seulement dans sa fenêtre (HotIfWinActive)
- Gestionnaire : arbre des catégories, favoris/récents/erreurs, aperçu,
  création depuis le presse-papiers, suppression par la corbeille
- Éditeur : métadonnées, insertion/déclaration de variables synchronisées,
  validation en direct, Tester (prévisualisation résolue, sans injection)
- VarForm : un contrôle par type de variable, requis validés, Échap annule
  toute l'injection et rend le focus
2026-10-11 10:17:30 -04:00

162 lines
5.3 KiB
AutoHotkey

; Picker.ahk — Le Quick Picker : surface principale de PromptKey (architecture §8).
; Fenêtre pré-créée, recherche instantanée, navigation clavier, aperçu, retour de focus propre.
class PromptPicker {
GuiObj := ""
SearchEdit := ""
LV := ""
Preview := ""
Status := ""
Items := [] ; prompts correspondant aux lignes affichées
Ctx := Map()
OnUse := "" ; Func(prompt, useDefaults)
OnEdit := "" ; Func(prompt)
ProfileLabel := ""
HotkeysArmed := false
Build(onUse, onEdit) {
this.OnUse := onUse
this.OnEdit := onEdit
g := Gui("-MaximizeBox +Resize", "PromptKey")
g.SetFont("s10", "Segoe UI")
this.GuiObj := g
g.Add("Text", "xm", "Rechercher :")
this.SearchEdit := g.Add("Edit", "x+10 w470", "")
this.SearchEdit.OnEvent("Change", (*) => this.RefreshList())
this.ProfileLabel := g.Add("Text", "x+10 w180", "")
this.LV := g.Add("ListView", "xm w500 r14", ["Titre", "Catégorie", "Étiquettes"])
this.LV.ModifyCol(1, 240)
this.LV.ModifyCol(2, 110)
this.LV.ModifyCol(3, 140)
this.LV.OnEvent("ItemSelect", (lv, row, selected) => selected ? this.UpdatePreview(row) : "")
this.LV.OnEvent("DoubleClick", (lv, row) => row > 0 ? this.UseSelected(false) : "")
this.Preview := g.Add("Edit", "x+15 w330 r14 ReadOnly -Wrap", "")
this.Status := g.Add("Text", "xm w850", "")
g.OnEvent("Escape", (*) => this.Hide())
g.OnEvent("Close", (*) => this.Hide())
}
Show(ctx) {
this.Ctx := ctx
this.SearchEdit.Value := ""
this.RefreshList()
this.GuiObj.Show("Center w880")
this.SearchEdit.Focus()
this.ArmHotkeys()
if (ctx.Has("profileName"))
this.ProfileLabel.Text := "Profil : " ctx["profileName"]
}
Hide() {
this.DisarmHotkeys()
this.GuiObj.Hide()
}
; Raccourcis propres au Picker, actifs seulement quand sa fenêtre est active (§8.2).
ArmHotkeys() {
if this.HotkeysArmed
return
HotIfWinActive("ahk_id " this.GuiObj.Hwnd)
Hotkey("Enter", (*) => this.UseSelected(false), "On")
Hotkey("^Enter", (*) => this.UseSelected(true), "On")
Hotkey("^e", (*) => this.EditSelected(), "On")
Hotkey("Down", (*) => this.MoveSelection(1), "On")
Hotkey("Up", (*) => this.MoveSelection(-1), "On")
Hotkey("^c", (*) => this.CopySelected(), "On")
HotIfWinActive()
this.HotkeysArmed := true
}
DisarmHotkeys() {
if !this.HotkeysArmed
return
HotIfWinActive("ahk_id " this.GuiObj.Hwnd)
for hk in ["Enter", "^Enter", "^e", "Down", "Up", "^c"] {
try Hotkey(hk, "Off")
}
HotIfWinActive()
this.HotkeysArmed := false
}
MoveSelection(delta) {
row := this.LV.GetNext()
if (row = 0)
row := delta > 0 ? 1 : this.Items.Length
else
row := Max(1, Min(this.Items.Length, row + delta))
if (row >= 1 && row <= this.Items.Length) {
this.LV.Modify(0, "-Select")
this.LV.Modify(row, "Select Focus Vis")
this.UpdatePreview(row)
}
}
RefreshList() {
q := this.SearchEdit.Value
usageData := App.Usage ? App.Usage.Data : Map()
this.Items := Search.Filter(App.Store.Prompts, q, usageData)
this.LV.Delete()
for p in this.Items {
marker := ""
if p.Pinned
marker .= "📌 "
if p.Favorite
marker .= "★ "
this.LV.Add("", marker p.Title, p.Category, Join(p.Tags, ", "))
}
if (this.Items.Length > 0) {
this.LV.Modify(1, "Select Focus")
this.UpdatePreview(1)
} else {
this.Preview.Value := "(aucun prompt ne correspond)"
}
this.Status.Text := this.Items.Length " résultat(s) · Entrée Utiliser · Ctrl+Entrée Valeurs par défaut · Ctrl+E Modifier · Échap Fermer"
}
UpdatePreview(row) {
if (row < 1 || row > this.Items.Length)
return
p := this.Items[row]
txt := p.Body
sel := this.Ctx.Has("selection") ? this.Ctx["selection"] : ""
if (sel != "")
txt .= "`r`n`r`n— Sélection capturée (" StrLen(sel) " caractères) : « " Util.Truncate(sel, 120) " »"
if (p.ParseError != "")
txt := "⚠ Erreur de fichier : " p.ParseError "`r`n`r`n" txt
this.Preview.Value := txt
}
SelectedPrompt() {
row := this.LV.GetNext()
if (row >= 1 && row <= this.Items.Length)
return this.Items[row]
return ""
}
UseSelected(useDefaults) {
p := this.SelectedPrompt()
if (p = "")
return
this.Hide()
this.OnUse(p, useDefaults)
}
EditSelected() {
p := this.SelectedPrompt()
if (p = "")
return
this.Hide()
this.OnEdit(p)
}
; Ctrl+C : copie le corps brut (sans résolution) — la résolution complète passe par l'injection.
CopySelected() {
p := this.SelectedPrompt()
if (p = "")
return
A_Clipboard := p.Body
TrayTip("Corps du prompt « " p.Title " » copié.", "PromptKey")
this.Hide()
}
}