Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

377 řádky
15KB

  1. #|
  2. src/web/decks.lisp
  3. Deck Overview Web Interface
  4. Here we see the cards in the deck, we have the option to generate
  5. deck images, and we have the option to pull the deck from
  6. inventory.
  7. |#
  8. (in-package #:cl-deck-builder2.web)
  9. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  10. ;; TODO both YDK-DATA-TO-TEMPLATE and RENDER-YDK desperately need an overhaul.
  11. (defun ydk-to-template-data (ydk)
  12. "Transform information from YDK-QUERY on YDK into: main, side, extra decks, with RLE encodings of all three, plus metadata."
  13. (let ((data (ydk-query ydk)))
  14. (when data
  15. (labels ((filter-kind (lst kind)
  16. (remove-if
  17. (lambda (card)
  18. (if (eq (getf card :kind) kind)
  19. nil
  20. t))
  21. (reverse lst))))
  22. (let ((main-deck (filter-kind data 0))
  23. (extra-deck (filter-kind data 1))
  24. (side-deck (filter-kind data 2)))
  25. (list
  26. :name (ydk-name-of ydk)
  27. :created-by (ydk-created-by ydk)
  28. :main-deck main-deck
  29. :extra-deck extra-deck
  30. :side-deck side-deck
  31. :main-deck-rle (rle-encode-plist main-deck :key (lambda (plist) (getf plist :name)) :test #'string=)
  32. :extra-deck-rle (rle-encode-plist extra-deck :key (lambda (plist) (getf plist :name)) :test #'string=)
  33. :side-deck-rle (rle-encode-plist side-deck :key (lambda (plist) (getf plist :name)) :test #'string=)))))))
  34. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  35. ;; TODO This is ugly still. Fix it.
  36. (defun draw-deck-image-by-id (id)
  37. (let ((found (find-dao 'ydk-deck :id id)))
  38. (when found
  39. (let ((ydk (ydk-sync found)))
  40. (draw-deck-image ydk (ydk-name-of ydk))))))
  41. ;; TODO this only works with decks from the database as it uses
  42. ;; YDK-DECK-INFO-BY-ID, making the name of this function a
  43. ;; misnomer. It should be RENDER-YDK-BY-DECK-ID. I'd use YDK-QUERY for
  44. ;; now beacuse that nets you INNER-JOIN with fields coming from
  45. ;; datafly as an associated list... sloppy work.
  46. (defun render-ydk (id tpl)
  47. "Render a YDK-DECK from the databse with ID using template TPL. We also use YDK-TO-TEMPLATE-DATA to query additional deck information."
  48. (let ((found (ydk-deck-by-id id)))
  49. (if found
  50. (render-with-env
  51. tpl
  52. (append
  53. (list :active "/decks"
  54. :id id
  55. :category (with-connection (db)
  56. (ydk-category-of found))
  57. :saved-deck-list (select-ydk-deck)
  58. :saved-category-list (select-category)
  59. :files (probe-image-files-list
  60. (ydk-name-of found)))
  61. (ydk-to-template-data found)))
  62. (_ "Nothing to see here..."))))
  63. (defgeneric ydk-listing-as (id tpl)
  64. (:documentation "Looks like this is a kludge to accept ID as a string or integer. TODO Move to generics.lisp")
  65. (:method ((id string) (tpl pathname))
  66. (ydk-listing-as (parse-integer id) tpl))
  67. (:method ((id integer) (tpl pathname))
  68. (render-ydk id tpl)))
  69. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  70. (defroute ("/decks" :method :GET) (&key _parsed)
  71. "Deck List Main Route. Login Required."
  72. (with-logged-in-user
  73. (let ((id (query-param "deck-load-id" _parsed)))
  74. (if id
  75. (let ((found (ydk-deck-by-id id)))
  76. (render-with-env #P"decks/index.html"
  77. `(:active "/decks"
  78. :files ,(probe-image-files-list (ydk-name-of found))
  79. :id ,id
  80. :category ,(ydk-category-of found)
  81. :saved-deck-list ,(select-ydk-deck)
  82. :saved-category-list ,(select-category))))
  83. (render-with-env #P"decks/index.html"
  84. `(:active "/decks"
  85. :saved-deck-list ,(select-ydk-deck)
  86. :saved-category-list ,(select-category)))))))
  87. (defroute ("/decks/:id/view" :method :GET) (&key id)
  88. "Deck Viewer Main Route. Login Required. View YDK-DECK with YDK-DECK-BY-ID. Uses RENDER-YDK."
  89. (with-logged-in-user
  90. (handler-case
  91. (ratify:with-parsed-forms
  92. ((:integer id))
  93. (let ((found (ydk-deck-by-id id)))
  94. (if found
  95. (render-ydk id #P"decks/index.html")
  96. (progn
  97. (flash-error (format nil (_ "No Deck ID ~d") id))
  98. (render-with-env #P"decks/index.html"
  99. `(:active "/decks"
  100. :saved-deck-list ,(select-ydk-deck)
  101. :saved-category-list ,(select-category)))))))
  102. (ratify:combined-error (e)
  103. (flash-error (format nil "~a~%" e))
  104. (render-with-env #P"decks/index.html"
  105. `(:active "/decks"
  106. :saved-deck-list ,(select-ydk-deck)
  107. :saved-category-list ,(select-category)))))))
  108. (defroute ("/decks/:id/generate-cover-images" :method :GET) (&key id)
  109. "Deck Viewer - Generate Cover Image. Login Required. Render a static image and display it with DECK-IMAGE-LISTING template."
  110. (v:info :decks "GET /decks/~d/generate-cover-images" id)
  111. (with-logged-in-user
  112. (handler-case
  113. (ratify:with-parsed-forms
  114. ((:integer id))
  115. (if (draw-deck-image-by-id id)
  116. (ydk-listing-as id #P"decks/deck-image-listing.html")
  117. (_ "Something went wrong. Try again?")))
  118. (ratify:combined-error (e)
  119. (flash-error e)))))
  120. (defroute ("/decks/:id/delete-generated-images" :method :DELETE) (&key id)
  121. "Deck Viewer - Delete Generate Cover Image. Login Required."
  122. (v:info :decks "DELETE /decks/~d/delete-generated-images" id)
  123. (with-logged-in-user
  124. (handler-case
  125. (ratify:with-parsed-forms
  126. ((:integer id))
  127. (let ((found (ydk-deck-by-id id)))
  128. (when found
  129. (if (notany #'null
  130. (mapcar #'uiop:delete-file-if-exists
  131. (probe-image-files-list (ydk-name-of found) nil)))
  132. (flash-message (_ "Success!"))
  133. (flash-error (_ "Something went wrong. Try again?"))))))
  134. (ratify:combined-error (e)
  135. (flash-error e)))))
  136. (defroute ("/decks/:id/to-category" :method :POST) (&key id _parsed)
  137. "POST route to send deck ID to category ID."
  138. (v:info :decks "POST /decks/~d/to-category._parsed = ~a~%" id _parsed)
  139. (with-logged-in-user
  140. (let ((cat-id (query-param "category-id" _parsed)))
  141. (handler-case
  142. (ratify:with-parsed-forms
  143. ((:integer id)
  144. (:integer cat-id))
  145. (let ((found (ydk-deck-by-id id)))
  146. (when found
  147. (setf (ydk-category-of found)
  148. (find-dao 'category :id cat-id))
  149. (with-connection (db)
  150. (mito:save-dao found))
  151. (render-with-env #P"decks/category-select.html"
  152. `(:category-id ,(mito:object-id
  153. (ydk-category-of found))
  154. :deck-id ,(mito:object-id found)
  155. :categories ,(select-category))))))
  156. (ratify:combined-error (e)
  157. (flash-error e))))))
  158. (defroute ("/decks/:id/rename" :method :POST) (&key id _parsed)
  159. "Deck Viewer - Rename deck ID with new name. Will also rename the generated image files, if they exist."
  160. (v:info :decks "GET /decks/~d/rename._parsed = ~a~%" id _parsed)
  161. (with-logged-in-user
  162. (let ((name (query-param "name" _parsed)))
  163. ;; We want to change the name of LOAD-NAME to CREATE-NAME.
  164. (handler-case
  165. (ratify:with-parsed-forms
  166. ((:integer id)
  167. (:string name))
  168. (let ((found (ydk-deck-by-id id)))
  169. ;; Ahaha that was a fun hunt. Using YDK-DECK-RENAME
  170. ;; before RENAME-FILES caused the OLD-NAME in
  171. ;; RENAME-FILES to be NEW-NAME... Rename the files
  172. ;; before renaming the database entry.
  173. (v:info :decks "RENAME ~a => ~a" (ydk-name-of found) name)
  174. (ydk-rename-files (ydk-name-of found) name)
  175. (ydk-rename-deck found name)
  176. (flash-message
  177. (format nil "Found entry ~A (~d); renaming to ~a.~%"
  178. (ydk-name-of found) id name))
  179. (redirect (format nil "/decks/~d/view" id) 302)))
  180. (ratify:combined-error (e)
  181. (flash-error e))))))
  182. (defroute ("/decks/:id/pull" :method :GET) (&key id)
  183. "Pull deck ID using YDK-DECK-PULL-FROM-INVENTORY.
  184. TODO I think This is outdated."
  185. (v:info :decks "GET /decks/~d/pull" id)
  186. ;; We want to "pull" the deck from inventory. That is, take the list
  187. ;; of cards in the deck and remove them from the inventory count.
  188. (with-logged-in-user
  189. (handler-case
  190. (ratify:with-parsed-forms
  191. ((:integer id))
  192. (let ((found (ydk-deck-by-id id)))
  193. ;; The deck exists by ID
  194. (when found
  195. (let ((pulled (ydk-deck-pull-from-inventory found)))
  196. (if pulled
  197. (progn
  198. (flash-message
  199. (format nil "Found entry ~A (~d); pulling cards.~%"
  200. (ydk-name-of found) id))
  201. (redirect (format nil "/construct/~d/view"
  202. (mito:object-id pulled))
  203. 302))
  204. (flash-message
  205. (format nil "Unable to pull deck ~d?~%" id)))))))
  206. (ratify:combined-error (e)
  207. (flash-error e)))))
  208. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  209. (defroute ("/decks/cards-in-decks" :method :GET) (&key _parsed)
  210. "Route to display all the unique cards in all decks."
  211. (v:info :decks "GET /decks/cards-in-decks => ~a" _parsed)
  212. (render #P"decks/cards-in-decks.html"
  213. (list :active "/decks"
  214. :table (cards-in-all-decks))))
  215. (defroute ("/decks/:id/name" :method :GET) (&key id)
  216. "Route to display the name of the YDK-DECK by this ID.
  217. TODO This should be a batch request."
  218. (with-logged-in-user
  219. (handler-case
  220. (ratify:with-parsed-forms
  221. ((:integer id))
  222. (let ((found (ydk-deck-by-id id)))
  223. (if found
  224. ;; Deck names like "29036" are coming back as INTEGER
  225. ;; which is causing issues with Caveman2. It seems to
  226. ;; use REDUCE #'LENGTH to measure the content length.
  227. ;; It didn't like: (LENGTH 29036)
  228. (princ-to-string (ydk-name-of found))
  229. (_ "No Name")))))))
  230. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  231. (defroute ("/decks/by-category/:id" :method :GET) (&key id _parsed)
  232. "Query a list of decks by category."
  233. (v:info :decks "GET /decks/by-category/~d => ~a" id _parsed)
  234. (with-logged-in-user
  235. (render #P"decks/decks-by-category.html"
  236. (list :active "/decks"
  237. :table (by-category 'ydk-deck id)))))
  238. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  239. ;; All the various kinds of deck representations: HTML, HTML as Text,
  240. ;; Text as Text Area, Resulting Deck File Images
  241. (defroute ("/decks/:id/html-listing" :method :GET) (&key id _parsed)
  242. "Helper route. Get the builder HTML listing using YDK-LISTING-AS and DECK-HTML-RESULTS template."
  243. (v:info :decks "GET /decks/~d/html-listing => ~a" id _parsed)
  244. (with-logged-in-user
  245. (ydk-listing-as id #P"decks/deck-html-results.html")))
  246. (defroute ("/decks/:id/html-text-listing" :method :GET) (&key id _parsed)
  247. "Helper route. Get the HTML listing using YDK-LISTING-AS and DECK-TEXT-THREE-COLUMN template."
  248. (v:info :decks "GET /decks/~d/html-text-listing => ~a" id _parsed)
  249. (with-logged-in-user
  250. (ydk-listing-as id #P"decks/deck-text-three-column.html")))
  251. (defroute ("/decks/:id/text-listing" :method :GET) (&key id _parsed)
  252. "Helper route. Get the text listing using YDK-LISTING-AS and DECK-TEXT-TEXTAREA template."
  253. (v:info :decks "GET /decks/~d/text-listing => ~a" id _parsed)
  254. (with-logged-in-user
  255. (ydk-listing-as id #P"decks/deck-text-textarea.html")))
  256. (defroute ("/decks/:id/text-listing-with-brs" :method :GET) (&key id _parsed)
  257. "Helper route. Get the text listing using YDK-LISTING-AS and DECK-TEXT-TEXTAREA template with <brs>."
  258. (v:info :decks "GET /decks/~d/text-listing-with-brs => ~a" id _parsed)
  259. (with-logged-in-user
  260. (ydk-listing-as id #P"decks/deck-text-textarea-with-brs.html")))
  261. (defroute ("/decks/:id/image-listing" :method :GET) (&key id _parsed)
  262. "Helper route. Get the image listing using YDK-LISTING-AS and DECK-IMAGE-LISTING template."
  263. (v:info :decks "GET /decks/~d/text-listing => ~a" id _parsed)
  264. (with-logged-in-user
  265. (ydk-listing-as id #P"decks/deck-image-listing.html")))
  266. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  267. (defroute ("/decks/:id/delete" :method :DELETE) (&key id)
  268. "Delete Deck by ID."
  269. (v:info :builder "DELETE /decks/:id/delete => ~a" id)
  270. (with-logged-in-user
  271. (handler-case
  272. (ratify:with-parsed-forms
  273. ((:integer id))
  274. (ydk-deck-delete-by-id id)
  275. (flash-message (format nil (_ "Deck ~a deleted success!" id))))
  276. (ratify:combined-error (e)
  277. (flash-error e)))
  278. (redirect "/decks")))
  279. (defroute ("/decks/search" :method :GET) (&key _parsed)
  280. "Deck Viewer Search Main Route."
  281. (v:info :builder "GET /decks/search => ~a" _parsed)
  282. (with-logged-in-user
  283. (render-with-env #P"decks/search.html"
  284. (list :active "/decks"))))
  285. (defroute ("/decks/search" :method :POST) (&key _parsed)
  286. "Deck Viewer Search POST route.
  287. TODO Ensure this is functional."
  288. (v:info :builder "GET /decks/search => ~a" _parsed)
  289. (with-logged-in-user
  290. (let* ((name (query-param "name" _parsed))
  291. (decks (select-ydk-deck
  292. (sxql:where (%sxql-like :name name)))))
  293. (render-with-env #P"decks/search.html"
  294. (list :active "/decks"
  295. :decks decks)))))
  296. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  297. ;; XXX Duplicate code from /builder/saved-deck-list
  298. (defroute ("/decks/deck-select" :method :GET) (&key |id|)
  299. "Deck Select Route Helper."
  300. (with-logged-in-user
  301. (render-with-env #P"decks/deck-select.html"
  302. (list :active "/decks"
  303. :id (ignore-errors
  304. (parse-integer |id|))
  305. :decks (select-ydk-deck)))))
  306. (defroute ("/decks/category-select" :method :GET) (&key |deck-id| |category-id|)
  307. "Category Select Route Helper.
  308. TODO Return Sub-categories."
  309. (with-logged-in-user
  310. (render-with-env #P"decks/category-select.html"
  311. (list :active "/decks"
  312. :category-id (ignore-errors
  313. (parse-integer |category-id|))
  314. :deck-id (ignore-errors
  315. (parse-integer |deck-id|))
  316. :categories (select-category)))))
  317. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  318. (defroute ("/decks/kde/:id" :method :GET) (&key id)
  319. "KDE Team Deck List Viewer. View Deck by ID"
  320. (render-with-env #P"kde-team.html"
  321. (ydk-to-kde
  322. (ydk-sync
  323. (ydk-deck-by-id id)))))