etaf/examples/etaf-counter-example.el
2026-08-27 12:14:51 +08:00

142 lines
5.0 KiB
EmacsLisp
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; etaf-counter-example.el --- Retained state example -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; This example keeps state inside one retained Component, derives display
;; values with `etaf-computed', and routes named mutations through an Action.
;;; Code:
(require 'etaf)
(defconst etaf-counter-example-buffer-name "*ETAF Counter Example*"
"Buffer opened by `etaf-counter-example-open'.")
(etaf-action-define etaf-counter-example-update (runtime count operation)
"Apply OPERATION to COUNT inside RUNTIME."
(ignore runtime)
(etaf-set-value
count
(pcase operation
('increment (1+ (etaf-value count)))
('decrement (max 0 (1- (etaf-value count))))
('reset 0)
(_ (user-error "Unknown counter operation: %S" operation)))))
(defun etaf-counter-example--header (title)
"Return the counter header for TITLE."
(etaf-view
(column :class "hero"
(text :class "eyebrow" "BEST PRACTICE / RETAINED STATE")
(text :font-weight 'bold (expr :value title))
(text :color "#66706A"
"State belongs to setup; rendering only reads it."))))
(defun etaf-counter-example--metrics (count double status)
"Return metric cards for COUNT, DOUBLE, and STATUS."
(etaf-view
(flex :width '(680) :flex-flow '(row wrap) :gap '(1 (12))
(box :class "metric"
(text (expr :value (format "COUNT %d" (etaf-value count)))))
(box :class "metric"
(text (expr :value (format "DOUBLE %d" (etaf-value double)))))
(box :class "metric"
(text (expr :value (format "STATE %s" (etaf-value status))))))))
(defun etaf-counter-example--action (count label host-ref operation)
"Return one COUNT action named LABEL using HOST-REF and OPERATION."
(etaf-view
(box :class "action" :ref host-ref :role 'button
:use (list (etaf-focusable))
:on-press (lambda ()
(etaf-dispatch 'etaf-counter-example-update
count operation))
(text (expr :value label)))))
(defun etaf-counter-example--actions (count)
"Return the action group for COUNT."
(etaf-view
(flex :width '(680) :flex-flow '(row wrap) :gap '(1 (12))
(expr :value
(etaf-counter-example--action
count " DECREMENT" 'counter-decrement 'decrement))
(expr :value
(etaf-counter-example--action
count "RESET" 'counter-reset 'reset))
(expr :value
(etaf-counter-example--action
count "+ INCREMENT" 'counter-increment 'increment)))))
(etaf-define-component etaf-counter-example-card (&key title initial-value)
"Render a retained counter named TITLE starting at INITIAL-VALUE."
:styles
(styles
("&" :width (680) :color "#252A2E" :bgcolor "#F8F5EE")
(".hero" :width (680) :padding (1 (18)) :border "#8F432F"
:bgcolor "#FFFDF8" :text-align center)
(".eyebrow" :color "#8F432F" :font-weight bold)
(".metric" :flex-grow 1 :flex-shrink 1 :flex-basis (200)
:min-width (180) :padding (1 (14)) :border "#6D8A73"
:bgcolor "#DCEBDD" :text-align center)
(".action" :flex-grow 1 :flex-shrink 1 :flex-basis (160)
:min-width (140) :padding (1 (12)) :border "#4E7890"
:bgcolor "#D9EAF2" :text-align center :font-weight bold)
(".note" :width (680) :padding (1 (16)) :border "#8D887F"
:color "#4D5651" :bgcolor "#EEEAE2"))
:setup
(let* ((count (etaf-ref (or initial-value 0) :name 'counter))
(double (etaf-computed
(lambda () (* 2 (etaf-value count)))
:name 'counter-double))
(status (etaf-computed
(lambda ()
(if (zerop (etaf-value count)) "READY" "ACTIVE"))
:name 'counter-status)))
(lambda ()
(etaf-view
(column
(expr :value (etaf-counter-example--header title))
(box :height 1)
(expr :value
(etaf-counter-example--metrics count double status))
(box :height 1)
(expr :value (etaf-counter-example--actions count))
(box :height 1)
(box :class "note"
(text
"Public path: Event → Action → Ref → Computed → Runtime commit")))))))
;;;###autoload
(defun etaf-counter-example-view ()
"Return the counter example View."
(etaf-view
(etaf-counter-example-card
:title "Counter workspace"
:initial-value 2)))
;;;###autoload
(defun etaf-counter-example-open ()
"Mount the counter example and return its buffer."
(interactive)
(let ((buffer (etaf-mount etaf-counter-example-buffer-name
(etaf-counter-example-view))))
(when (called-interactively-p 'interactive)
(pop-to-buffer buffer))
buffer))
;;;###autoload
(defun etaf-counter-example-close ()
"Unmount and kill the counter example buffer."
(interactive)
(when-let* ((runtime
(etaf-runtime-for-buffer etaf-counter-example-buffer-name)))
(etaf-unmount runtime))
(when-let* ((buffer (get-buffer etaf-counter-example-buffer-name)))
(kill-buffer buffer)))
(provide 'etaf-counter-example)
;;; etaf-counter-example.el ends here