git.fiddlerwoaroof.com
Browse code

gallery: add simple image gallery

Ed Langley authored on 23/01/2019 19:09:19
Showing 2 changed files
... ...
@@ -7,3 +7,5 @@ timer.fasl
7 7
 .*fasl
8 8
 *.*fasl
9 9
 /jsonifier
10
+*.dx*fsl
11
+*.fas
10 12
new file mode 100644
... ...
@@ -0,0 +1,247 @@
1
+(defpackage :fwoar.gallery
2
+  (:use :cl :araneus :alexandria :serapeum)
3
+  (:export ))
4
+(in-package :fwoar.gallery)
5
+
6
+(defvar *url*)
7
+(defvar *gallery*)
8
+
9
+(defmacro new (class &rest initializer-syms)
10
+  `(make-instance ,class
11
+                  ,@(mapcan (serapeum:op (list (alexandria:make-keyword _1) _1))
12
+                            initializer-syms)))
13
+
14
+(defclass gallery ()
15
+  ((%images :initarg :images :initform () :reader images)))
16
+
17
+(defun make-gallery (images)
18
+  (new 'gallery images))
19
+
20
+(defclass page ()
21
+  ((%gallery :initarg :gallery :reader gallery)
22
+   (%page :initarg :page :initform 1 :reader page)))
23
+
24
+(defun make-page (gallery page)
25
+  (new 'page gallery page))
26
+
27
+(defclass image ()
28
+  ((%url :initarg :url :initform (error "need url for image") :reader url)))
29
+(defclass video ()
30
+  ((%url :initarg :url :initform (error "need url for video") :reader url)))
31
+
32
+(defmethod print-object ((o image) s)
33
+  (format s "#.(make-image \"~a\")" (url o)))
34
+(defmethod print-object ((o video) s)
35
+  (format s "#.(make-video \"~a\")" (url o)))
36
+(defmethod print-object ((o gallery) s)
37
+  (format s "#.(make-gallery (~a images))" (length (images o))))
38
+
39
+(defun make-image (url)
40
+  (new 'image url))
41
+(defun make-video (url)
42
+  (new 'video url))
43
+
44
+(defun page->gallery (dom base-url)
45
+  (check-type dom plump:node)
46
+  (make-gallery
47
+   (coerce (lquery:$ 
48
+             (inline dom)
49
+             "a" 
50
+             (attr "href") 
51
+             (filter (lambda (u) (search "jpg" u)))
52
+             (filter (complement (lambda (u) (search "thumb" u))))
53
+             (map (lambda (u)
54
+                    (with-output-to-string (s)
55
+                      (puri:render-uri
56
+                       (puri:merge-uris u base-url)
57
+                       s))))
58
+             (map 'make-image))
59
+           'list)))
60
+
61
+
62
+(define-controller root (params gallery)
63
+  (let ((page (make-page gallery
64
+                         (parse-integer
65
+                          (or (cdr (assoc "page" params
66
+                                          :test #'equalp))
67
+                              "1")))))
68
+    page))
69
+
70
+(defmacro hostname-case (uri &body cases)
71
+  `(serapeum:string-case (puri:uri-host ,uri)
72
+     ,@cases))
73
+
74
+(defun get-site (uri)
75
+  (hostname-case uri
76
+    ("gfycat.com" :gfycat)
77
+    ("i.redd.it" :ireddit)
78
+    ("v.redd.it" :vreddit)))
79
+
80
+(defgeneric transform-url (site uri)
81
+  (:method (_ uri)
82
+    (declare (ignore _))
83
+    (make-image (puri:render-uri uri nil)))
84
+  (:method ((site (eql :gfycat)) uri)
85
+    (make-video
86
+     (format nil "~a.mp4"
87
+             (puri:render-uri
88
+              (fw.lu:prog1-bind (uri (puri:copy-uri uri))
89
+                (setf (puri:uri-host uri) "gfycat.com"))
90
+              nil))))
91
+  (:method ((site (eql :vreddit)) uri)
92
+    (make-video (puri:render-uri uri nil))))
93
+
94
+(defun ensure-uri (uri)
95
+  (etypecase uri
96
+    (string (puri:parse-uri uri))
97
+    (puri:uri uri)))
98
+
99
+(defun process-uri-list (uris)
100
+  (loop for raw-uri in uris
101
+        for uri = (ensure-uri raw-uri)
102
+        for transformed = (transform-url (get-site uri) uri)
103
+        when transformed
104
+          collect transformed))
105
+
106
+(spinneret:deftag css (body attrs)
107
+  `(:style :type "text/css"
108
+           ,@attrs
109
+           (:raw
110
+            ,(format nil "~%")
111
+            ,(apply 'lass:compile-and-write
112
+                    body)
113
+            )))
114
+
115
+(defmethod view :around ((name (eql 'root)) (model page))
116
+  (spinneret:with-html-string
117
+    (:html
118
+     (:head
119
+      (css
120
+        (*
121
+         :box-sizing border-box)
122
+        (html
123
+         :outline "thin solid black")
124
+        ((:or body html div section)
125
+         :margin 0
126
+         :padding 0)
127
+
128
+        (.gallery
129
+         :position relative
130
+         :display flex
131
+         :width 100vw
132
+         :height (calc (- 100vh 2em))
133
+         :overflow-y scroll
134
+         :background "#888"
135
+
136
+         :flex-wrap wrap)
137
+        ((.gallery > div)
138
+         :text-align center
139
+         :width 25%
140
+         :max-height 25vh
141
+         :overflow hidden)
142
+        ((.gallery img)
143
+         :width 98%
144
+         :height 98%
145
+         :object-fit contain)
146
+        ((.gallery video)
147
+         :width 98%
148
+         :height 98%
149
+         :object-fit contain)
150
+        ((.gallery > div.expanded)
151
+         :position fixed
152
+         :top 0
153
+         :left 0
154
+         :max-height (calc (- 100vh 2em))
155
+         :height 100%
156
+         :width 100%
157
+         :background "#eee")
158
+        ((.gallery > (:and div.expanded :before))
159
+         :content " "
160
+         :display inline-block
161
+         :vertical-align middle
162
+         :height 100%)
163
+        ((.gallery > div.expanded > img)
164
+         :display inline-block
165
+         :vertical-align middle
166
+         :height (calc (- 100vh 2em)))
167
+        ((.gallery > div.expanded > video)
168
+         :display inline-block
169
+         :vertical-align middle)))
170
+     (:body
171
+      (let ((*gallery* (make-gallery (subseq (images (gallery model))
172
+                                             (* 52 (1- (page model)))
173
+                                             (min (* 52 (page model))
174
+                                                  (length (images (gallery model))))))))
175
+        (call-next-method))
176
+      (when (<= (* 52 (page model))
177
+                (length (images (gallery model))))
178
+        (:a :href (format nil "/?page=~d" (1+ (page model)))
179
+            :style "width: 100%; text-align: center; display: inline-block;"
180
+            "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")))))))))))))))
197
+
198
+(define-view root ((model page))
199
+  (spinneret:with-html
200
+    (:section.gallery
201
+     (loop for img in (images *gallery*)
202
+           do (call-current-view img)))))
203
+
204
+(define-view root ((model image))
205
+  (spinneret:with-html
206
+    (:div.image
207
+     (:img :src (url model)))))
208
+
209
+(define-view root ((model video))
210
+  (spinneret:with-html
211
+    (:div.image
212
+     (:video :autoplay "autoplay" (:source :src (url model) :type "video/mp4")))))
213
+
214
+(defun initialize-app (app gallery)
215
+  (defroutes app
216
+    (("/") (as-route 'root :gallery gallery))))
217
+
218
+(defun main (url)
219
+  (let* ((app (make-instance 'ningle:<app>))
220
+         (gallery (page->gallery (plump:parse
221
+                                  (babel:octets-to-string (drakma:http-request url :force-binary t)
222
+                                                          :encoding :latin-1))
223
+                                 url)))
224
+    (initialize-app app gallery)
225
+    (clack:clackup app)))
226
+
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
+(defun reddit-main (subreddits)
234
+  (let* ((app (make-instance 'ningle:<app>))
235
+         (gallery (make-gallery
236
+                   (mapcan
237
+                    (lambda (subreddit)
238
+                      (get-reddit-items
239
+                       (yason:parse
240
+                        (babel:octets-to-string
241
+                         (drakma:http-request (format nil "https://reddit.com/r/~a.json" subreddit)
242
+                                              :force-binary t)
243
+                         :encoding :latin-1))))
244
+                    subreddits))))
245
+    (setf *gallery* gallery)
246
+    (initialize-app app gallery)
247
+    (clack:clackup app)))