git.fiddlerwoaroof.com
Browse code

Add demo blog and gallery POCs

Ed Langley authored on 28/01/2019 10:59:48
Showing 2 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,172 @@
1
+(defpackage :fwoar.blog
2
+  (:use :cl :araneus :alexandria :serapeum :fw.lu)
3
+  (:export ))
4
+(in-package :fwoar.blog)
5
+
6
+(defmacro new (class &rest initializer-syms)
7
+  (multiple-value-bind (required optional rest) (parse-ordinary-lambda-list initializer-syms)
8
+    (when optional
9
+      (error "new doesn't handle optional arguments"))
10
+    (if rest
11
+        `(make-instance ,class
12
+                        ,@(mapcan (serapeum:op (list (alexandria:make-keyword _1) _1))
13
+                                  required)
14
+                        ,(make-keyword rest) ,rest)
15
+        `(make-instance ,class
16
+                        ,@(mapcan (serapeum:op (list (alexandria:make-keyword _1) _1))
17
+                                  initializer-syms)))))
18
+
19
+(defun-ct %constructor-name (class)
20
+  (format nil "~a-~a" '#:make class))
21
+
22
+(defmacro make-constructor (class &rest args)
23
+  (destructuring-bind (class &optional (constructor-name (intern (%constructor-name class))))
24
+      (ensure-list class)
25
+    `(defgeneric ,constructor-name (,@args)
26
+       (:method (,@args)
27
+         (new ',class ,@args)))))
28
+
29
+(defclass blog ()
30
+  ((%posts :initarg :posts :accessor posts))
31
+  (:default-initargs :posts ()))
32
+(make-constructor blog &rest posts)
33
+(defmethod print-object ((o blog) s)
34
+  (format s "#.(make-blog ~{~s~^ ~})"
35
+          (posts o)))
36
+
37
+
38
+(defclass post ()
39
+  ((%content :initarg :content :accessor content)))
40
+
41
+(defclass micropost (post)
42
+  ())
43
+(make-constructor micropost content)
44
+(defmethod print-object ((o micropost) s)
45
+  (format s "#.(make-micropost ~s)" (content o)))
46
+
47
+(defclass macropost (post)
48
+  ((%title :initarg :title :accessor title)))
49
+(make-constructor macropost title content)
50
+(defmethod print-object ((o macropost) s)
51
+  (format s "#.(make-macropost ~s ~s)"
52
+          (title o)
53
+          (content o)))
54
+
55
+(defmacro orc (&rest funs)
56
+  `(lambda (v)
57
+     (or ,@(loop for fun in funs
58
+                 collect `(,fun v)))))
59
+
60
+(defun slugify (string)
61
+  (substitute #\- #\space
62
+              (trim-whitespace
63
+               (remove-if-not (orc alphanumericp
64
+                                   (lambda (c) (eql c #\space))
65
+                                   (lambda (c) (eql c #\-)))
66
+                              (string-downcase string)))))
67
+
68
+(defgeneric titled-posts (blog)
69
+  (:method ((blog blog))
70
+    (mappend #'titled-posts
71
+             (posts blog)))
72
+  (:method ((post micropost))
73
+    ())
74
+  (:method ((post macropost))
75
+    (list post)))
76
+
77
+(defgeneric find-post (slug blog)
78
+  (:method ((slug string) (blog blog))
79
+    (loop with needle-slug = (slugify slug)
80
+          for post in (titled-posts blog)
81
+          for haystack-slug = (slugify (title post))
82
+          when (equal needle-slug haystack-slug)
83
+            return post)))
84
+
85
+(defclass blog-route ()
86
+  ((%blog :initarg :blog :reader blog)))
87
+
88
+(defclass index (blog-route)
89
+  ())
90
+(make-constructor (index make-blog-index))
91
+
92
+(defclass post-route (blog-route)
93
+  ((%post :initarg :post :reader post)))
94
+(make-constructor (post make-blog-post))
95
+
96
+(defmethod controller ((route index) params &key)
97
+  (posts (blog route)))
98
+
99
+(defmethod controller ((route post-route) params &key)
100
+  (post route))
101
+
102
+(defmethod view ((name post-route) (post macropost))
103
+  (spinneret:with-html-string
104
+    (:section
105
+     (:h* (title post))
106
+     (:div
107
+      (content post)))))
108
+
109
+(defmethod view ((name index) posts)
110
+  (spinneret:with-html-string
111
+    (:section
112
+     (:h* "Blog Index")
113
+     (:div
114
+      (loop for post in posts
115
+            do (call-current-view post))))))
116
+
117
+(defmethod view ((name index) (post micropost))
118
+  (spinneret:with-html
119
+    (:section.post.micropost
120
+     (content post))))
121
+
122
+
123
+(defmethod view ((name index) (post macropost))
124
+  (spinneret:with-html
125
+    (:section.post.macropost
126
+     (:h* (:a :href (format nil "/~a" (slugify (title post)))
127
+              (title post))))))
128
+
129
+(defun setup-routes (app blog)
130
+  (defroutes app
131
+    (("/" :method :GET) (as-route (make-instance 'index :blog blog)))
132
+    (("/:post" :method :GET) (lambda (params)
133
+                               (format t "~&params: ~s~%" params)
134
+                               (let* ((post-name (cdr (assoc :post params)))
135
+                                      (route (make-instance 'post-route :post (find-post post-name blog))))
136
+                                 (run-route route params))))))
137
+
138
+(defvar *blog*
139
+  (make-blog (make-micropost "first post")
140
+             (make-macropost "This is the title"
141
+                             "This is the post content"))
142
+  "The sample blog: passed lexically to the routes, so rebinding don't change nothin'")
143
+
144
+(defun setup (&optional (blog *blog*))
145
+  (prog1-bind (app (make-instance 'ningle:<app>))
146
+    (setup-routes app blog)))
147
+
148
+;;; entrypoint
149
+(defvar *handler*)
150
+
151
+(defun is-running ()
152
+  (and (boundp '*handler*)
153
+       *handler*))
154
+
155
+(defun ensure-started (&rest r &key port)
156
+  (declare (ignore port))
157
+  (setf *handler*
158
+        (if (not (is-running))
159
+            (apply 'clack:clackup (setup) r)
160
+            *handler*)))
161
+
162
+(defun stop ()
163
+  (if (is-running)
164
+      (progn
165
+        (clack:stop *handler*)
166
+        (makunbound '*handler*)
167
+        nil)
168
+      nil))
169
+
170
+#+fw.dev
171
+(define-cluser-entrypoint (&optional (port 5000))
172
+  (ensure-started :port port))
... ...
@@ -1,3 +1,14 @@
1
+#.(progn
2
+    (asdf:defsystem :fwoar.gallery
3
+      :depends-on (:lquery
4
+                   :araneus
5
+                   :drakma
6
+                   :clack
7
+                   :yason
8
+                   :ningle
9
+                   :lass))
10
+    nil)
11
+
1 12
 (defpackage :fwoar.gallery
2 13
   (:use :cl :araneus :alexandria :serapeum)
3 14
   (:export ))
... ...
@@ -74,6 +85,7 @@
74 85
 (defun get-site (uri)
75 86
   (hostname-case uri
76 87
     ("gfycat.com" :gfycat)
88
+    ("imgur.com" :imgur)
77 89
     ("i.redd.it" :ireddit)
78 90
     ("v.redd.it" :vreddit)))
79 91
 
... ...
@@ -88,6 +100,13 @@
88 100
               (fw.lu:prog1-bind (uri (puri:copy-uri uri))
89 101
                 (setf (puri:uri-host uri) "gfycat.com"))
90 102
               nil))))
103
+  (:method ((site (eql :imgur)) uri)
104
+    (make-image
105
+     (format nil "~a.jpg"
106
+             (puri:render-uri
107
+              (fw.lu:prog1-bind (uri (puri:copy-uri uri))
108
+                (setf (puri:uri-host uri) "i.imgur.com"))
109
+              nil))))
91 110
   (:method ((site (eql :vreddit)) uri)
92 111
     (make-video (puri:render-uri uri nil))))
93 112
 
... ...
@@ -112,11 +131,19 @@
112 131
                     body)
113 132
             )))
114 133
 
115
-(defmethod view :around ((name (eql 'root)) (model page))
116
-  (spinneret:with-html-string
117
-    (:html
118
-     (:head
119
-      (css
134
+(spinneret:deftag js (body attrs)
135
+  `(:script :type "text/javascript"
136
+            ,@attrs
137
+            (:raw
138
+             ,(format nil "~%")
139
+             (ps:ps
140
+               ,@body))))
141
+
142
+(defun gallery-css ()
143
+  (spinneret:with-html
144
+    (css
145
+      (:let ((bottom-gap 2em)
146
+             (gallery-height (calc (- 100vh 2em))))
120 147
         (*
121 148
          :box-sizing border-box)
122 149
         (html
... ...
@@ -127,22 +154,21 @@
127 154
 
128 155
         (.gallery
129 156
          :position relative
130
-         :display flex
157
+         :column-count 4
131 158
          :width 100vw
132
-         :height (calc (- 100vh 2em))
159
+         :height #(gallery-height)
133 160
          :overflow-y scroll
134 161
          :background "#888"
135 162
 
136 163
          :flex-wrap wrap)
137 164
         ((.gallery > div)
138 165
          :text-align center
139
-         :width 25%
140 166
          :max-height 25vh
141 167
          :overflow hidden)
142 168
         ((.gallery img)
143 169
          :width 98%
144 170
          :height 98%
145
-         :object-fit contain)
171
+         :object-fit cover)
146 172
         ((.gallery video)
147 173
          :width 98%
148 174
          :height 98%
... ...
@@ -151,7 +177,7 @@
151 177
          :position fixed
152 178
          :top 0
153 179
          :left 0
154
-         :max-height (calc (- 100vh 2em))
180
+         :max-height #(gallery-height)
155 181
          :height 100%
156 182
          :width 100%
157 183
          :background "#eee")
... ...
@@ -162,11 +188,35 @@
162 188
          :height 100%)
163 189
         ((.gallery > div.expanded > img)
164 190
          :display inline-block
191
+         :object-fit contain
165 192
          :vertical-align middle
166 193
          :height (calc (- 100vh 2em)))
167 194
         ((.gallery > div.expanded > video)
168 195
          :display inline-block
169
-         :vertical-align middle)))
196
+         :vertical-align middle)))))
197
+
198
+(defun gallery-js ()
199
+  (js
200
+   (ps:chain #() for-each
201
+             (call (ps:chain document
202
+                             (query-selector-all ".gallery > div"))
203
+                   (lambda (it)
204
+                     (ps:chain it (add-event-listener
205
+                                   "click"
206
+                                   (lambda ()
207
+                                     (ps:chain #() for-each
208
+                                               (call (ps:chain document (query-selector-all ".expanded"))
209
+                                                     (lambda (other)
210
+                                                       (unless (eql other it)
211
+                                                         (ps:chain other class-list (remove "expanded"))))))
212
+                                     (ps:chain it class-list (toggle "expanded"))))))))) )
213
+
214
+
215
+(defmethod view :around ((name (eql 'root)) (model page))
216
+  (spinneret:with-html-string
217
+    (:html
218
+     (:head
219
+      (gallery-css))
170 220
      (:body
171 221
       (let ((*gallery* (make-gallery (subseq (images (gallery model))
172 222
                                              (* 52 (1- (page model)))
... ...
@@ -178,22 +228,7 @@
178 228
         (:a :href (format nil "/?page=~d" (1+ (page model)))
179 229
             :style "width: 100%; text-align: center; display: inline-block;"
180 230
             "next"))
181
-      (:script
182
-       (:raw
183
-        (ps:ps
184
-          (ps:chain #() for-each
185
-                    (call (ps:chain document
186
-                                    (query-selector-all ".gallery > div"))
187
-                          (lambda (it)
188
-                            (ps:chain it (add-event-listener
189
-                                          "click"
190
-                                          (lambda ()
191
-                                            (ps:chain #() for-each
192
-                                                      (call (ps:chain document (query-selector-all ".expanded"))
193
-                                                            (lambda (other)
194
-                                                              (unless (eql other it)
195
-                                                                (ps:chain other class-list (remove "expanded"))))))
196
-                                            (ps:chain it class-list (toggle "expanded")))))))))))))))
231
+      (gallery-js)))))
197 232
 
198 233
 (define-view root ((model page))
199 234
   (spinneret:with-html
... ...
@@ -215,6 +250,12 @@
215 250
   (defroutes app
216 251
     (("/") (as-route 'root :gallery gallery))))
217 252
 
253
+(defun get-reddit-items (r)
254
+  (process-uri-list
255
+   (mapcar (lambda (i)
256
+             (fw.lu:pick '("data" "url") i))
257
+           (fw.lu:pick '("data" "children") r))))
258
+
218 259
 (defun main (url)
219 260
   (let* ((app (make-instance 'ningle:<app>))
220 261
          (gallery (page->gallery (plump:parse
... ...
@@ -224,12 +265,6 @@
224 265
     (initialize-app app gallery)
225 266
     (clack:clackup app)))
226 267
 
227
-(defun get-reddit-items (r)
228
-  (process-uri-list
229
-   (mapcar (lambda (i)
230
-             (fw.lu:pick '("data" "url") i))
231
-           (fw.lu:pick '("data" "children") r))))
232
-
233 268
 (defun reddit-main (subreddits)
234 269
   (let* ((app (make-instance 'ningle:<app>))
235 270
          (gallery (make-gallery
... ...
@@ -245,3 +280,8 @@
245 280
     (setf *gallery* gallery)
246 281
     (initialize-app app gallery)
247 282
     (clack:clackup app)))
283
+
284
+(defun cl-user::fwoar.gallery.main (version init)
285
+  (ecase version
286
+    (:reddit (reddit-main init))
287
+    (:dir (main init))))