update examples. fix bugs
This commit is contained in:
+16
-15
@@ -1,36 +1,37 @@
|
||||
(ns examples.counter
|
||||
"Simple counter example - demonstrates basic Elm architecture.
|
||||
Mirrors bubbletea's simple example."
|
||||
(:require [tui.core :as tui]))
|
||||
(:require [tui.core :as tui]
|
||||
[tui.events :as ev]))
|
||||
|
||||
;; === Model ===
|
||||
(def initial-model
|
||||
{:count 0})
|
||||
|
||||
;; === Update ===
|
||||
(defn update-model [model msg]
|
||||
(defn update-fn [{:keys [model event]}]
|
||||
(cond
|
||||
;; Quit on q or ctrl+c
|
||||
(or (tui/key= msg "q")
|
||||
(tui/key= msg [:ctrl \c]))
|
||||
[model tui/quit]
|
||||
(or (ev/key= event \q)
|
||||
(ev/key= event \c #{:ctrl}))
|
||||
{:model model :events [(ev/quit)]}
|
||||
|
||||
;; Increment on up/k
|
||||
(or (tui/key= msg :up)
|
||||
(tui/key= msg "k"))
|
||||
[(update model :count inc) nil]
|
||||
(or (ev/key= event :up)
|
||||
(ev/key= event \k))
|
||||
{:model (update model :count inc)}
|
||||
|
||||
;; Decrement on down/j
|
||||
(or (tui/key= msg :down)
|
||||
(tui/key= msg "j"))
|
||||
[(update model :count dec) nil]
|
||||
(or (ev/key= event :down)
|
||||
(ev/key= event \j))
|
||||
{:model (update model :count dec)}
|
||||
|
||||
;; Reset on r
|
||||
(tui/key= msg "r")
|
||||
[(assoc model :count 0) nil]
|
||||
(ev/key= event \r)
|
||||
{:model (assoc model :count 0)}
|
||||
|
||||
:else
|
||||
[model nil]))
|
||||
{:model model}))
|
||||
|
||||
;; === View ===
|
||||
(defn view [{:keys [count]} _size]
|
||||
@@ -51,6 +52,6 @@
|
||||
(defn -main [& _args]
|
||||
(println "Starting counter...")
|
||||
(let [final-model (tui/run {:init initial-model
|
||||
:update update-model
|
||||
:update update-fn
|
||||
:view view})]
|
||||
(println "Final count:" (:count final-model))))
|
||||
|
||||
+26
-31
@@ -2,6 +2,7 @@
|
||||
"HTTP request example - demonstrates async commands.
|
||||
Mirrors bubbletea's http example."
|
||||
(:require [tui.core :as tui]
|
||||
[tui.events :as ev]
|
||||
[clojure.java.io :as io])
|
||||
(:import [java.net URL HttpURLConnection]))
|
||||
|
||||
@@ -29,41 +30,35 @@
|
||||
:error nil
|
||||
:url "https://httpstat.us/200"})
|
||||
|
||||
;; === Commands ===
|
||||
(defn fetch-url [url]
|
||||
(fn []
|
||||
(let [result (http-get url)]
|
||||
(if (:error result)
|
||||
[:http-error (:error result)]
|
||||
[:http-success (:status result)]))))
|
||||
|
||||
;; === Update ===
|
||||
(defn update-model [{:keys [url] :as model} msg]
|
||||
(cond
|
||||
;; Quit
|
||||
(or (tui/key= msg "q")
|
||||
(tui/key= msg [:ctrl \c]))
|
||||
[model tui/quit]
|
||||
(defn update-fn [{:keys [model event]}]
|
||||
(let [{:keys [url]} model]
|
||||
(cond
|
||||
;; Quit
|
||||
(or (ev/key= event \q)
|
||||
(ev/key= event \c #{:ctrl}))
|
||||
{:model model :events [(ev/quit)]}
|
||||
|
||||
;; Enter - start request
|
||||
(and (= (:state model) :idle)
|
||||
(tui/key= msg :enter))
|
||||
[(assoc model :state :loading) (fetch-url url)]
|
||||
;; Enter - start request
|
||||
(and (= (:state model) :idle)
|
||||
(ev/key= event :enter))
|
||||
{:model (assoc model :state :loading)
|
||||
:events [(ev/shell ["curl" "-s" "-o" "/dev/null" "-w" "%{http_code}" url]
|
||||
{:type :http-result})]}
|
||||
|
||||
;; r - retry/reset
|
||||
(tui/key= msg "r")
|
||||
[(assoc model :state :idle :status nil :error nil) nil]
|
||||
;; r - retry/reset
|
||||
(ev/key= event \r)
|
||||
{:model (assoc model :state :idle :status nil :error nil)}
|
||||
|
||||
;; HTTP success
|
||||
(= (first msg) :http-success)
|
||||
[(assoc model :state :success :status (second msg)) nil]
|
||||
;; HTTP result
|
||||
(= (:type event) :http-result)
|
||||
(let [{:keys [success out err]} (:result event)]
|
||||
(if success
|
||||
{:model (assoc model :state :success :status (parse-long out))}
|
||||
{:model (assoc model :state :error :error err)}))
|
||||
|
||||
;; HTTP error
|
||||
(= (first msg) :http-error)
|
||||
[(assoc model :state :error :error (second msg)) nil]
|
||||
|
||||
:else
|
||||
[model nil]))
|
||||
:else
|
||||
{:model model})))
|
||||
|
||||
;; === View ===
|
||||
(defn view [{:keys [state status error url]} _size]
|
||||
@@ -105,7 +100,7 @@
|
||||
(defn -main [& _args]
|
||||
(println "Starting HTTP demo...")
|
||||
(let [final (tui/run {:init initial-model
|
||||
:update update-model
|
||||
:update update-fn
|
||||
:view view})]
|
||||
(when (= (:state final) :success)
|
||||
(println "Request completed with status:" (:status final)))))
|
||||
|
||||
+29
-27
@@ -2,6 +2,7 @@
|
||||
"List selection example - demonstrates cursor navigation and multi-select.
|
||||
Mirrors bubbletea's list examples."
|
||||
(:require [tui.core :as tui]
|
||||
[tui.events :as ev]
|
||||
[clojure.string :as str]))
|
||||
|
||||
;; === Model ===
|
||||
@@ -12,37 +13,38 @@
|
||||
:submitted false})
|
||||
|
||||
;; === Update ===
|
||||
(defn update-model [{:keys [cursor items] :as model} msg]
|
||||
(cond
|
||||
;; Quit
|
||||
(or (tui/key= msg "q")
|
||||
(tui/key= msg [:ctrl \c]))
|
||||
[model tui/quit]
|
||||
(defn update-fn [{:keys [model event]}]
|
||||
(let [{:keys [cursor items]} model]
|
||||
(cond
|
||||
;; Quit
|
||||
(or (ev/key= event \q)
|
||||
(ev/key= event \c #{:ctrl}))
|
||||
{:model model :events [(ev/quit)]}
|
||||
|
||||
;; Move up
|
||||
(or (tui/key= msg :up)
|
||||
(tui/key= msg "k"))
|
||||
[(update model :cursor #(max 0 (dec %))) nil]
|
||||
;; Move up
|
||||
(or (ev/key= event :up)
|
||||
(ev/key= event \k))
|
||||
{:model (update model :cursor #(max 0 (dec %)))}
|
||||
|
||||
;; Move down
|
||||
(or (tui/key= msg :down)
|
||||
(tui/key= msg "j"))
|
||||
[(update model :cursor #(min (dec (count items)) (inc %))) nil]
|
||||
;; Move down
|
||||
(or (ev/key= event :down)
|
||||
(ev/key= event \j))
|
||||
{:model (update model :cursor #(min (dec (count items)) (inc %)))}
|
||||
|
||||
;; Toggle selection
|
||||
(tui/key= msg " ")
|
||||
[(update model :selected
|
||||
#(if (contains? % cursor)
|
||||
(disj % cursor)
|
||||
(conj % cursor)))
|
||||
nil]
|
||||
;; Toggle selection
|
||||
(ev/key= event \space)
|
||||
{:model (update model :selected
|
||||
#(if (contains? % cursor)
|
||||
(disj % cursor)
|
||||
(conj % cursor)))}
|
||||
|
||||
;; Submit
|
||||
(tui/key= msg :enter)
|
||||
[(assoc model :submitted true) tui/quit]
|
||||
;; Submit
|
||||
(ev/key= event :enter)
|
||||
{:model (assoc model :submitted true)
|
||||
:events [(ev/quit)]}
|
||||
|
||||
:else
|
||||
[model nil]))
|
||||
:else
|
||||
{:model model})))
|
||||
|
||||
;; === View ===
|
||||
(defn view [{:keys [cursor items selected submitted]} _size]
|
||||
@@ -83,7 +85,7 @@
|
||||
(defn -main [& _args]
|
||||
(println "Starting list selection...")
|
||||
(let [{:keys [items selected submitted]} (tui/run {:init initial-model
|
||||
:update update-model
|
||||
:update update-fn
|
||||
:view view})]
|
||||
(when submitted
|
||||
(println)
|
||||
|
||||
+33
-31
@@ -1,7 +1,8 @@
|
||||
(ns examples.spinner
|
||||
"Spinner example - demonstrates animated loading states.
|
||||
Mirrors bubbletea's spinner example."
|
||||
(:require [tui.core :as tui]))
|
||||
(:require [tui.core :as tui]
|
||||
[tui.events :as ev]))
|
||||
|
||||
;; === Spinner Frames ===
|
||||
(def spinner-styles
|
||||
@@ -21,42 +22,43 @@
|
||||
:style :dots
|
||||
:loading true
|
||||
:message "Loading..."
|
||||
:styles (keys spinner-styles)
|
||||
:styles (vec (keys spinner-styles))
|
||||
:style-idx 0})
|
||||
|
||||
;; === Update ===
|
||||
(defn update-model [{:keys [styles style-idx] :as model} msg]
|
||||
(cond
|
||||
;; Quit
|
||||
(or (tui/key= msg "q")
|
||||
(tui/key= msg [:ctrl \c]))
|
||||
[model tui/quit]
|
||||
(defn update-fn [{:keys [model event]}]
|
||||
(let [{:keys [styles style-idx]} model]
|
||||
(cond
|
||||
;; Quit
|
||||
(or (ev/key= event \q)
|
||||
(ev/key= event \c #{:ctrl}))
|
||||
{:model model :events [(ev/quit)]}
|
||||
|
||||
;; Spinner frame - advance animation
|
||||
(= msg :spinner-frame)
|
||||
(if (:loading model)
|
||||
[(update model :frame inc) (tui/after 80 :spinner-frame)]
|
||||
[model nil])
|
||||
;; Spinner frame - advance animation
|
||||
(= (:type event) :spinner-frame)
|
||||
(if (:loading model)
|
||||
{:model (update model :frame inc)
|
||||
:events [(ev/delayed-event 80 {:type :spinner-frame})]}
|
||||
{:model model})
|
||||
|
||||
;; Space - simulate completion
|
||||
(tui/key= msg " ")
|
||||
[(assoc model :loading false :message "Done!") nil]
|
||||
;; Space - simulate completion
|
||||
(ev/key= event \space)
|
||||
{:model (assoc model :loading false :message "Done!")}
|
||||
|
||||
;; Tab - change spinner style
|
||||
(tui/key= msg :tab)
|
||||
(let [new-idx (mod (inc style-idx) (count styles))]
|
||||
[(assoc model
|
||||
:style-idx new-idx
|
||||
:style (nth styles new-idx))
|
||||
nil])
|
||||
;; Tab - change spinner style
|
||||
(ev/key= event :tab)
|
||||
(let [new-idx (mod (inc style-idx) (count styles))]
|
||||
{:model (assoc model
|
||||
:style-idx new-idx
|
||||
:style (nth styles new-idx))})
|
||||
|
||||
;; r - restart
|
||||
(tui/key= msg "r")
|
||||
[(assoc model :loading true :frame 0 :message "Loading...")
|
||||
(tui/after 80 :spinner-frame)]
|
||||
;; r - restart
|
||||
(ev/key= event \r)
|
||||
{:model (assoc model :loading true :frame 0 :message "Loading...")
|
||||
:events [(ev/delayed-event 80 {:type :spinner-frame})]}
|
||||
|
||||
:else
|
||||
[model nil]))
|
||||
:else
|
||||
{:model model})))
|
||||
|
||||
;; === View ===
|
||||
(defn spinner-view [{:keys [frame style]}]
|
||||
@@ -84,7 +86,7 @@
|
||||
(defn -main [& _args]
|
||||
(println "Starting spinner...")
|
||||
(tui/run {:init initial-model
|
||||
:update update-model
|
||||
:update update-fn
|
||||
:view view
|
||||
:init-cmd (tui/after 80 :spinner-frame)})
|
||||
:init-events [(ev/delayed-event 80 {:type :spinner-frame})]})
|
||||
(println "Spinner demo finished."))
|
||||
|
||||
+20
-18
@@ -1,7 +1,8 @@
|
||||
(ns examples.timer
|
||||
"Countdown timer example - demonstrates async commands.
|
||||
Mirrors bubbletea's stopwatch/timer examples."
|
||||
(:require [tui.core :as tui]))
|
||||
(:require [tui.core :as tui]
|
||||
[tui.events :as ev]))
|
||||
|
||||
;; === Model ===
|
||||
(def initial-model
|
||||
@@ -10,37 +11,38 @@
|
||||
:done false})
|
||||
|
||||
;; === Update ===
|
||||
(defn update-model [model msg]
|
||||
(defn update-fn [{:keys [model event]}]
|
||||
(cond
|
||||
;; Quit
|
||||
(or (tui/key= msg "q")
|
||||
(tui/key= msg [:ctrl \c]))
|
||||
[model tui/quit]
|
||||
(or (ev/key= event \q)
|
||||
(ev/key= event \c #{:ctrl}))
|
||||
{:model model :events [(ev/quit)]}
|
||||
|
||||
;; Timer tick - decrement timer
|
||||
(= msg :timer-tick)
|
||||
(= (:type event) :timer-tick)
|
||||
(if (:running model)
|
||||
(let [new-seconds (dec (:seconds model))]
|
||||
(if (<= new-seconds 0)
|
||||
;; Timer done
|
||||
[(assoc model :seconds 0 :done true :running false) nil]
|
||||
{:model (assoc model :seconds 0 :done true :running false)}
|
||||
;; Continue countdown
|
||||
[(assoc model :seconds new-seconds) (tui/after 1000 :timer-tick)]))
|
||||
[model nil])
|
||||
{:model (assoc model :seconds new-seconds)
|
||||
:events [(ev/delayed-event 1000 {:type :timer-tick})]}))
|
||||
{:model model})
|
||||
|
||||
;; Space - pause/resume
|
||||
(tui/key= msg " ")
|
||||
(ev/key= event \space)
|
||||
(let [new-running (not (:running model))]
|
||||
[(assoc model :running new-running)
|
||||
(when new-running (tui/after 1000 :timer-tick))])
|
||||
{:model (assoc model :running new-running)
|
||||
:events (when new-running [(ev/delayed-event 1000 {:type :timer-tick})])})
|
||||
|
||||
;; r - reset
|
||||
(tui/key= msg "r")
|
||||
[(assoc model :seconds 10 :done false :running true)
|
||||
(tui/after 1000 :timer-tick)]
|
||||
(ev/key= event \r)
|
||||
{:model (assoc model :seconds 10 :done false :running true)
|
||||
:events [(ev/delayed-event 1000 {:type :timer-tick})]}
|
||||
|
||||
:else
|
||||
[model nil]))
|
||||
{:model model}))
|
||||
|
||||
;; === View ===
|
||||
(defn format-time [seconds]
|
||||
@@ -74,8 +76,8 @@
|
||||
(defn -main [& _args]
|
||||
(println "Starting timer...")
|
||||
(let [final-model (tui/run {:init initial-model
|
||||
:update update-model
|
||||
:update update-fn
|
||||
:view view
|
||||
:init-cmd (tui/after 1000 :timer-tick)})]
|
||||
:init-events [(ev/delayed-event 1000 {:type :timer-tick})]})]
|
||||
(when (:done final-model)
|
||||
(println "Timer completed!"))))
|
||||
|
||||
+45
-44
@@ -1,7 +1,8 @@
|
||||
(ns examples.views
|
||||
"Multiple views example - demonstrates state machine pattern.
|
||||
Mirrors bubbletea's views example."
|
||||
(:require [tui.core :as tui]))
|
||||
(:require [tui.core :as tui]
|
||||
[tui.events :as ev]))
|
||||
|
||||
;; === Model ===
|
||||
(def initial-model
|
||||
@@ -14,58 +15,58 @@
|
||||
:selected nil})
|
||||
|
||||
;; === Update ===
|
||||
(defn update-model [{:keys [view cursor items] :as model} msg]
|
||||
(case view
|
||||
;; Menu view
|
||||
:menu
|
||||
(cond
|
||||
(or (tui/key= msg "q")
|
||||
(tui/key= msg [:ctrl \c]))
|
||||
[model tui/quit]
|
||||
(defn update-fn [{:keys [model event]}]
|
||||
(let [{:keys [view cursor items]} model]
|
||||
(case view
|
||||
;; Menu view
|
||||
:menu
|
||||
(cond
|
||||
(or (ev/key= event \q)
|
||||
(ev/key= event \c #{:ctrl}))
|
||||
{:model model :events [(ev/quit)]}
|
||||
|
||||
(or (tui/key= msg :up)
|
||||
(tui/key= msg "k"))
|
||||
[(update model :cursor #(max 0 (dec %))) nil]
|
||||
(or (ev/key= event :up)
|
||||
(ev/key= event \k))
|
||||
{:model (update model :cursor #(max 0 (dec %)))}
|
||||
|
||||
(or (tui/key= msg :down)
|
||||
(tui/key= msg "j"))
|
||||
[(update model :cursor #(min (dec (count items)) (inc %))) nil]
|
||||
(or (ev/key= event :down)
|
||||
(ev/key= event \j))
|
||||
{:model (update model :cursor #(min (dec (count items)) (inc %)))}
|
||||
|
||||
(tui/key= msg :enter)
|
||||
[(assoc model
|
||||
:view :detail
|
||||
:selected (nth items cursor))
|
||||
nil]
|
||||
(ev/key= event :enter)
|
||||
{:model (assoc model
|
||||
:view :detail
|
||||
:selected (nth items cursor))}
|
||||
|
||||
:else
|
||||
[model nil])
|
||||
:else
|
||||
{:model model})
|
||||
|
||||
;; Detail view
|
||||
:detail
|
||||
(cond
|
||||
(or (tui/key= msg "q")
|
||||
(tui/key= msg [:ctrl \c]))
|
||||
[(assoc model :view :confirm) nil]
|
||||
;; Detail view
|
||||
:detail
|
||||
(cond
|
||||
(or (ev/key= event \q)
|
||||
(ev/key= event \c #{:ctrl}))
|
||||
{:model (assoc model :view :confirm)}
|
||||
|
||||
(or (tui/key= msg :escape)
|
||||
(tui/key= msg "b"))
|
||||
[(assoc model :view :menu :selected nil) nil]
|
||||
(or (ev/key= event :escape)
|
||||
(ev/key= event \b))
|
||||
{:model (assoc model :view :menu :selected nil)}
|
||||
|
||||
:else
|
||||
[model nil])
|
||||
:else
|
||||
{:model model})
|
||||
|
||||
;; Confirm quit dialog
|
||||
:confirm
|
||||
(cond
|
||||
(tui/key= msg "y")
|
||||
[model tui/quit]
|
||||
;; Confirm quit dialog
|
||||
:confirm
|
||||
(cond
|
||||
(ev/key= event \y)
|
||||
{:model model :events [(ev/quit)]}
|
||||
|
||||
(or (tui/key= msg "n")
|
||||
(tui/key= msg :escape))
|
||||
[(assoc model :view :detail) nil]
|
||||
(or (ev/key= event \n)
|
||||
(ev/key= event :escape))
|
||||
{:model (assoc model :view :detail)}
|
||||
|
||||
:else
|
||||
[model nil])))
|
||||
:else
|
||||
{:model model}))))
|
||||
|
||||
;; === Views ===
|
||||
(defn menu-view [{:keys [cursor items]}]
|
||||
@@ -112,6 +113,6 @@
|
||||
(defn -main [& _args]
|
||||
(println "Starting views demo...")
|
||||
(tui/run {:init initial-model
|
||||
:update update-model
|
||||
:update update-fn
|
||||
:view view})
|
||||
(println "Views demo finished."))
|
||||
|
||||
Reference in New Issue
Block a user