summaryrefslogtreecommitdiff
path: root/internal/ui/launch.go
diff options
context:
space:
mode:
authorspiperac <[email protected]>2026-06-15 06:42:13 +0200
committerspiperac <[email protected]>2026-06-15 06:42:13 +0200
commit0a477fd288d064d265395c88dbd0f261b723804e (patch)
tree9b129f8f41c907285b89ed8385679934bb59c14f /internal/ui/launch.go
Initial release
Diffstat (limited to 'internal/ui/launch.go')
-rw-r--r--internal/ui/launch.go219
1 files changed, 219 insertions, 0 deletions
diff --git a/internal/ui/launch.go b/internal/ui/launch.go
new file mode 100644
index 0000000..75dca54
--- /dev/null
+++ b/internal/ui/launch.go
@@ -0,0 +1,219 @@
+package ui
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "time"
+
+ "fyne.io/fyne/v2"
+ "fyne.io/fyne/v2/canvas"
+ "fyne.io/fyne/v2/container"
+ "fyne.io/fyne/v2/dialog"
+ "fyne.io/fyne/v2/layout"
+ "fyne.io/fyne/v2/storage"
+ "fyne.io/fyne/v2/theme"
+ "fyne.io/fyne/v2/widget"
+
+ "github.com/shiv/assets"
+)
+
+const maxRecentProjects = 10
+
+type RecentProject struct {
+ Path string `json:"path"`
+ LastOpen time.Time `json:"last_open"`
+}
+
+func ShowLaunchScreen(app fyne.App, onSelect func(projectPath string, w fyne.Window)) {
+ launchWin := app.NewWindow("Shiv")
+ applyTheme(app)
+ launchWin.Resize(fyne.NewSize(560, 420))
+ launchWin.CenterOnScreen()
+ launchWin.SetFixedSize(true)
+
+ var list *widget.List
+ var onDeleteBtn func(widget.ListItemID)
+
+ recents, _ := loadRecents()
+
+ list = widget.NewList(
+ func() int { return len(recents) },
+ func() fyne.CanvasObject {
+ return container.NewHBox(
+ widget.NewIcon(AppIcon("project")),
+ container.NewHBox(
+ widget.NewLabel("name"),
+ widget.NewLabel("date"),
+ ),
+ layout.NewSpacer(),
+ widget.NewButtonWithIcon("", AppIcon("delete"), nil),
+ )
+ },
+ func(i widget.ListItemID, obj fyne.CanvasObject) {
+ hbox := obj.(*fyne.Container)
+ box := hbox.Objects[1].(*fyne.Container)
+ box.Objects[0].(*widget.Label).SetText(filepath.Base(recents[i].Path))
+ box.Objects[1].(*widget.Label).SetText(recents[i].LastOpen.Format("2006-01-02 15:04"))
+ box.Objects[1].(*widget.Label).Importance = widget.LowImportance
+ hbox.Objects[3].(*widget.Button).OnTapped = func() { onDeleteBtn(i) }
+ },
+ )
+
+ onDeleteBtn = func(i widget.ListItemID) {
+ deleteRecent(recents[i].Path)
+ recents, _ = loadRecents()
+ list.Refresh()
+ }
+
+ list.OnSelected = func(i widget.ListItemID) {
+ path := recents[i].Path
+ if _, err := os.Stat(path); err != nil {
+ dialog.ShowError(err, launchWin)
+ list.UnselectAll()
+ return
+ }
+ saveRecent(path)
+ onSelect(path, launchWin)
+ }
+
+ newBtn := widget.NewButtonWithIcon("New Project", theme.DocumentCreateIcon(), func() {
+ saveDialog := dialog.NewFileSave(func(writeCloser fyne.URIWriteCloser, err error) {
+ if err != nil || writeCloser == nil {
+ return
+ }
+ writeCloser.Close()
+ path := writeCloser.URI().Path()
+ saveRecent(path)
+ onSelect(path, launchWin)
+ }, launchWin)
+ saveDialog.SetFileName("untitled.shiv")
+ saveDialog.SetFilter(storage.NewExtensionFileFilter([]string{".shiv"}))
+ saveDialog.Show()
+ })
+
+ openBtn := widget.NewButtonWithIcon("Open Project", theme.FolderOpenIcon(), func() {
+ openDialog := dialog.NewFileOpen(func(readCloser fyne.URIReadCloser, err error) {
+ if err != nil || readCloser == nil {
+ return
+ }
+ readCloser.Close()
+ path := readCloser.URI().Path()
+ saveRecent(path)
+ onSelect(path, launchWin)
+ }, launchWin)
+ openDialog.SetFilter(storage.NewExtensionFileFilter([]string{".shiv"}))
+ openDialog.Show()
+ })
+ newBtn.Importance = widget.HighImportance
+
+ logo := canvas.NewImageFromResource(fyne.NewStaticResource("logo.png", assets.Logo))
+ logo.FillMode = canvas.ImageFillContain
+ logo.SetMinSize(fyne.NewSize(128, 128))
+
+ subtitle := widget.NewLabel("HTTP/HTTPS Proxy Ninja")
+ subtitle.Importance = widget.LowImportance
+
+ recentLabel := widget.NewLabel("Recent Projects")
+ recentLabel.TextStyle = fyne.TextStyle{Bold: true}
+
+ header := container.NewVBox(
+ container.NewCenter(logo),
+ container.NewCenter(subtitle),
+ widget.NewSeparator(),
+ )
+
+ buttons := container.NewGridWithColumns(2, newBtn, openBtn)
+
+ var content fyne.CanvasObject
+ if len(recents) == 0 {
+ empty := widget.NewLabel("No recent projects")
+ empty.Importance = widget.LowImportance
+ content = container.NewBorder(
+ container.NewVBox(header, recentLabel),
+ buttons,
+ nil, nil,
+ container.NewCenter(empty),
+ )
+ } else {
+ content = container.NewBorder(
+ container.NewVBox(header, recentLabel),
+ buttons,
+ nil, nil,
+ list,
+ )
+ }
+
+ launchWin.SetContent(container.NewPadded(content))
+ launchWin.Show()
+}
+
+func recentsPath() (string, error) {
+ configDir, err := os.UserConfigDir()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(configDir, "shiv", "recent_projects.json"), nil
+}
+
+func loadRecents() ([]RecentProject, error) {
+ path, err := recentsPath()
+ if err != nil {
+ return nil, err
+ }
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ var recents []RecentProject
+ if err := json.Unmarshal(data, &recents); err != nil {
+ return nil, err
+ }
+ return recents, nil
+}
+
+func saveRecent(projectPath string) {
+ recents, _ := loadRecents()
+
+ filtered := recents[:0]
+ for _, recent := range recents {
+ if recent.Path != projectPath {
+ filtered = append(filtered, recent)
+ }
+ }
+
+ updated := append([]RecentProject{{Path: projectPath, LastOpen: time.Now()}}, filtered...)
+ if len(updated) > maxRecentProjects {
+ updated = updated[:maxRecentProjects]
+ }
+
+ path, err := recentsPath()
+ if err != nil {
+ return
+ }
+ data, err := json.Marshal(updated)
+ if err != nil {
+ return
+ }
+ os.WriteFile(path, data, 0o644)
+}
+
+func deleteRecent(projectPath string) {
+ os.Remove(projectPath)
+ recents, _ := loadRecents()
+ filtered := recents[:0]
+ for _, r := range recents {
+ if r.Path != projectPath {
+ filtered = append(filtered, r)
+ }
+ }
+ path, err := recentsPath()
+ if err != nil {
+ return
+ }
+ data, err := json.Marshal(filtered)
+ if err != nil {
+ return
+ }
+ os.WriteFile(path, data, 0o644)
+}