etaf/examples/etaf-counter-example.el
2026-08-31 15:18:26 +08:00

139 lines
4.7 KiB
EmacsLisp
Raw 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)))))
(etaf-define-component etaf-counter-example-action
(&key count label operation)
"Render one semantic counter action."
:view
(box :class "action" :role 'button :use (list (etaf-focusable))
:on-press
(let ((cell count) (next-operation operation))
(lambda ()
(etaf-dispatch 'etaf-counter-example-update
cell next-operation)))
(text (expr label)))
:styles
(styles
("&" :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)))
(etaf-define-component etaf-counter-example-card (&key title initial-value)
"Render a retained counter named TITLE starting at INITIAL-VALUE."
: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)))
(list :count count :double double :status status))
:view
(column
(box :class "hero"
(column
(text :class "eyebrow" "BEST PRACTICE / RETAINED STATE")
(text :font-weight 'bold (expr title))
(text :color "#66706A"
"State belongs to setup; rendering only reads it.")))
(box :height 1)
(flex :width '(680) :flex-flow '(row wrap) :gap '(1 (12))
(box :class "metric"
(text (expr
(format "COUNT %d"
(etaf-value (plist-get (etaf-state) :count))))))
(box :class "metric"
(text (expr
(format "DOUBLE %d"
(etaf-value (plist-get (etaf-state) :double))))))
(box :class "metric"
(text (expr
(format "STATE %s"
(etaf-value (plist-get (etaf-state) :status)))))))
(box :height 1)
(flex :width '(680) :flex-flow '(row wrap) :gap '(1 (12))
(etaf-counter-example-action
:ref 'counter-decrement
:count (plist-get (etaf-state) :count)
:label " DECREMENT" :operation 'decrement)
(etaf-counter-example-action
:ref 'counter-reset
:count (plist-get (etaf-state) :count)
:label "RESET" :operation 'reset)
(etaf-counter-example-action
:ref 'counter-increment
:count (plist-get (etaf-state) :count)
:label "+ INCREMENT" :operation 'increment))
(box :height 1)
(box :class "note"
(text "Public path: Event → Action → Ref → Computed → Runtime commit")))
: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)
(".note" :width (680) :padding (1 (16)) :border "#8D887F"
:color "#4D5651" :bgcolor "#EEEAE2")))
;;;###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