;;;; pages.lisp -- html generation functions for dnd (in-package :dnd) ;;; RENDER PROTOCOL (defgeneric render (view object &key) (:documentation "Render OBJECT as VIEW. VIEW could be anything, but it is intended to be a keyword for usin in EQL method specializers.")) (defmacro defrender (view (spec &rest kwargs) &body body) "A helper macro for defining specializations of render." (let ((viewvar (gensym))) `(defmethod render ((,viewvar (eql ,view)) ,spec &key ,@kwargs) ,@body))) (defrender :list ((data list) (class "listview") (item-class "listitem")) "A catch all for rendering lists of renderable data items as unordered lists. CLASS is the lass string for the containing list. ITEM-CLASS is the class string for the contained list items." (with-html (:ol :class class (dolist (item data) (:li (render :list-item item :class item-class)))))) (defrender :three-column-layout (data) "A catch all specialization for rendering data in three columns. You must specialize :left :milsddle :right on your desired data type." (with-html (:div :class "three-column-layout" (:div :class "left-column" (render :left data)) (:div :class "middle-column" (render :middle data)) (:div :class "right-column" (render :right data))))) ;;; Object-Endpoint Helpers (defgeneric url-path-to (obj) (:documentation "Return a string that is the absolute url path to OBJ.")) (defmethod url-path-to ((obj campaign)) (format nil "/campaign/~a" (urlify (title obj)))) ;;; PAGES (defmacro with-page ((&key title) &body body) "A helper macro fordefining some standard page boilerplate." `(with-html-string (:doctype) (:html (:head (:title ,title)) (:body ,@body)))) (defclass/std doorkeeper () ((message))) (defun doorkeeper (&key (message "Come ye player, Wot's yer name?")) (render :page (make-instance 'doorkeeper :message message ))) (defrender :page ((page doorkeeper)) (with-page (:title "Tavern Door") (:h1 (message page)) (:form :method "POST" :action "/tavern-door" (:label :for "NICK" "Wut's yer handle?:") (:input :name "NICK") (:button :type "submit" "Announce Thyself")) (:h2 "Eh? Ye need to announce thyeself?") (:a :href "/register" "Follow me..."))) (defclass/std goddess-shrine () ()) (defrender :page ((page goddess-shrine)) (with-page (:title "A Sacred Shrine") (:header (:h1 "Pray and become a hero...")) (:form :method "POST" :action "/godess-shrine" (:label :for "NAME" "Enter the epithet by which the ages shall know thy hero:") (:input :name "NAME") (:button :type "submit" "Pray To The Goddess")))) (defclass/std player-registration () ()) (defrender :page ((page player-registration)) (with-page (:title "Register Player") (:header (:h1 "Choose a Nickname Player")) (:form :method "POST" :action "/register" (:label :for "NICK" "Choose a nickname. No spaces. Letters, Numbers, and -._") (:input :name "NICK" :placeholder "superbob") (:button :type "submit" "Register")))) (defun register () (render :page (make-instance 'player-registration))) (defclass/std tavern () ((player alerts))) (defun tavern (player) (render :page (make-instance 'tavern :player player))) (defrender :page ((tavern tavern)) (with-page (:title "A Bustling Tavern") (render :three-column-layout tavern))) (defrender :text-page ((tavern tavern)) (let ((player (player tavern))) (with-html (render :details player) (render :list (player-heroes player)) (:a :href "/spymaster" "Report a Roguish Rumour...") ;; (:table ;; (:tr (:td (:h4 "Your Heroes")) ;; (:td (:h4 "Your Campaigns"))) ;; (:tr (:td (:h4 "Gossip & Gab")) ;; (:td (:h4 "Comrades in Arms")))) ))) (defrender :list-item ((hero hero)) (with-html (with-slots ((name campaign) hero) (:p name "the" (hero-class hero) (hero-title hero) (when campaign (:span "who is off in the campaign") (:span (render :inline campaign))))))) (defrender :inline ((campaign campaign)) (with-html (:a :href (url-path-to campaign) (title campaign)))) (defrender :left ((tavern tavern)) (let ((player (player tavern))) (with-html (render :details player) (:h4 "Your Heroes") (render :list (player-heroes player))))) (defrender :middle ((tavern tavern)) (with-html (:h4 "Your Campaigns ") (render :list (player-campaigns (player tavern))))) (defrender :right ((tavern tavern)) (with-html (:h4 "Gossip & Gab") (render :list (alerts tavern)) (:h4 "Comrades in Arms") (render :list (fetch-comrades (player tavern))))) (defrender :details ((player player)) (with-html (:div :class "player details" (:h3 "Welcome " (player-nick player))))) (defun navbar () (with-html (:nav :class "navbar" :aria-label "Navigation" (:div :class "logo" :aria-label "DND logo" "DND") (:ul :class "nav-links" :aria-label "Nav links" (:li (:a :href "/hero" :aria-label "Hero profile" "🧝")) (:li (:a :href "/inventory" :aria-label "Inventory" "🎒")) (:li (:a :href "/quests" :aria-label "Quests" "📜")) (:li (:a :href "/tavern" :aria-label "Tavern" "🍺")))))) (defun hall-of-heroes () (with-html (:ul :class "hall-of-heroes" (dolist (hero (all-heroes)) (:li (hero-name hero) "the" (hero-class hero) (hero-title hero))))))