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

200 lines
6.8 KiB
AutoHotkey

; Manager.ahk — Le Gestionnaire : bibliothèque en trois volets (architecture §9.1).
class PromptManager {
GuiObj := ""
TV := ""
LV := ""
Preview := ""
Status := ""
NodeFilter := Map() ; id de nœud TreeView -> filtre (catégorie ou __all__/__fav__/__recent__/__errors__)
Items := []
Filter := "__all__"
OnUse := "" ; Func(prompt)
OnEdit := "" ; Func(prompt)
Build(onUse, onEdit) {
this.OnUse := onUse
this.OnEdit := onEdit
g := Gui("-MaximizeBox +Resize", "PromptKey — Gestionnaire")
g.SetFont("s10", "Segoe UI")
this.GuiObj := g
this.TV := g.Add("TreeView", "xm w180 r18")
this.TV.OnEvent("ItemSelect", (tv, id) => this.NodeFilter.Has(id) ? this.SetFilter(this.NodeFilter[id]) : "")
this.LV := g.Add("ListView", "x+10 w430 r18", ["Titre", "Modifié", "Utilisations"])
this.LV.ModifyCol(1, 240)
this.LV.ModifyCol(2, 90)
this.LV.ModifyCol(3, 95)
this.LV.OnEvent("ItemSelect", (lv, row, selected) => selected ? this.UpdatePreview(row) : "")
this.LV.OnEvent("DoubleClick", (lv, row) => row > 0 ? this.EditSelected() : "")
this.Preview := g.Add("Edit", "x+10 w330 r18 ReadOnly -Wrap", "")
btnUse := g.Add("Button", "xm w130", "Utiliser")
btnUse.OnEvent("Click", (*) => this.UseSelected())
btnEdit := g.Add("Button", "x+8 w130", "Modifier")
btnEdit.OnEvent("Click", (*) => this.EditSelected())
btnNew := g.Add("Button", "x+8 w130", "Nouveau")
btnNew.OnEvent("Click", (*) => App.NewPromptInteractive())
btnClip := g.Add("Button", "x+8 w180", "Nouveau depuis le presse-papiers")
btnClip.OnEvent("Click", (*) => this.NewFromClipboard())
btnDup := g.Add("Button", "xm w130", "Dupliquer")
btnDup.OnEvent("Click", (*) => this.DuplicateSelected())
btnDel := g.Add("Button", "x+8 w130", "Supprimer")
btnDel.OnEvent("Click", (*) => this.DeleteSelected())
btnFolder := g.Add("Button", "x+8 w160", "Ouvrir le dossier")
btnFolder.OnEvent("Click", (*) => Run(App.Settings.LibraryPath))
btnReload := g.Add("Button", "x+8 w130", "Recharger")
btnReload.OnEvent("Click", (*) => this.Refresh())
this.Status := g.Add("Text", "xm w950", "")
g.OnEvent("Close", (*) => g.Hide())
g.OnEvent("Escape", (*) => g.Hide())
}
Show() {
this.Refresh()
this.GuiObj.Show("w990")
}
Hide() {
this.GuiObj.Hide()
}
SetFilter(filter) {
this.Filter := filter
this.RefreshList()
}
Refresh() {
App.Store.Load()
this.NodeFilter := Map()
this.TV.Delete()
root := this.TV.Add("Bibliothèque")
this.NodeFilter[root] := "__all__"
for cat in App.Store.Categories() {
node := this.TV.Add(cat, root)
this.NodeFilter[node] := cat
}
fav := this.TV.Add("★ Favoris", root)
this.NodeFilter[fav] := "__fav__"
rec := this.TV.Add("◷ Récents", root)
this.NodeFilter[rec] := "__recent__"
if (App.Store.ErrorFiles.Length > 0) {
err := this.TV.Add("⚠ En erreur (" App.Store.ErrorFiles.Length ")", root)
this.NodeFilter[err] := "__errors__"
}
this.TV.Modify(root, "Expand")
this.RefreshList()
}
FilteredPrompts() {
out := []
switch this.Filter {
case "__all__":
return App.Store.Prompts
case "__fav__":
for p in App.Store.Prompts {
if p.Favorite
out.Push(p)
}
case "__recent__":
for p in App.Store.Prompts {
if (App.Usage && App.Usage.Data.Has(p.Id))
out.Push(p)
}
case "__errors__":
return App.Store.ErrorFiles
default:
for p in App.Store.Prompts {
if (p.Category = this.Filter)
out.Push(p)
}
}
return out
}
RefreshList() {
this.Items := this.FilteredPrompts()
this.LV.Delete()
for p in this.Items {
count := App.Usage ? App.Usage.Count(p.Id) : 0
marker := (p.Favorite ? "★ " : "") (p.Pinned ? "📌 " : "")
this.LV.Add("", marker p.Title, p.Updated, count)
}
if (this.Items.Length > 0) {
this.LV.Modify(1, "Select")
this.UpdatePreview(1)
} else {
this.Preview.Value := ""
}
this.Status.Text := App.Store.Prompts.Length " prompt(s) dans la bibliothèque · filtre : " this.Filter
}
UpdatePreview(row) {
if (row < 1 || row > this.Items.Length)
return
p := this.Items[row]
txt := p.Body
if (p.ParseError != "")
txt := "⚠ Erreur d'en-tête : " p.ParseError "`r`nFichier : " p.Path "`r`n`r`n" txt
else
txt := "Catégorie : " p.Category " · Étiquettes : " Join(p.Tags, ", ") "`r`n" (p.Hotkey != "" ? "Raccourci : " p.Hotkey "`r`n" : "") (p.Hotstring != "" ? "Hotstring : ::" p.Hotstring "`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() {
p := this.SelectedPrompt()
if (p = "")
return
this.Hide()
this.OnUse(p)
}
EditSelected() {
p := this.SelectedPrompt()
if (p = "")
return
this.OnEdit(p)
}
DuplicateSelected() {
p := this.SelectedPrompt()
if (p = "")
return
App.Store.Duplicate(p)
this.Refresh()
}
DeleteSelected() {
p := this.SelectedPrompt()
if (p = "")
return
r := MsgBox("Envoyer « " p.Title " » à la corbeille ?", "PromptKey", "YesNo Icon?")
if (r != "Yes")
return
App.Store.Delete(p)
App.RefreshAfterLibraryChange()
this.Refresh()
}
NewFromClipboard() {
txt := ClipboardGuard.ClipboardText()
if (Trim(txt) = "") {
TrayTip("Le presse-papiers ne contient pas de texte.", "PromptKey")
return
}
ib := InputBox("Titre du nouveau prompt :", "PromptKey — Depuis le presse-papiers")
if (ib.Result != "OK" || Trim(ib.Value) = "")
return
item := App.Store.CreateNew(Trim(ib.Value), "Sans catégorie", txt)
App.RefreshAfterLibraryChange()
this.Refresh()
TrayTip("Prompt « " item.Title " » créé depuis le presse-papiers.", "PromptKey")
}
}