Forráskód Böngészése

Incoming albims with path guessing

Innocenty Enikeew 10 éve
szülő
commit
8b17a434ec
7 módosított fájl, 103 hozzáadás és 220 törlés
  1. 1 174
      assets/site.css
  2. 40 15
      assets/site.js
  3. 1 0
      exif.lisp
  4. 12 5
      index.html
  5. 2 3
      photo-store.asd
  6. 45 23
      photo-store.lisp
  7. 2 0
      utils.lisp

+ 1 - 174
assets/site.css

@@ -35,51 +35,6 @@ body {
 
 }
 
-
-
-/*code {
-  background: #F8F8F8;
-  padding: .1em .4em;
-  color: #c82829;
-  font-size: 14px;
-}*/
-code, pre {
-  font-family:  Menlo, Monaco, monospace;
-  font-size: 12px;
-  line-height: 1.45;
-  color: #333;
-  tab-size: 4;
-}
-pre {
-  padding: 0;
-  margin: 0;
-  overflow: auto;
-  
-  word-wrap: normal;
-
-  text-rendering: auto;
-  -webkit-font-smoothing: auto;
-}
-
-/*body.open-sans {
-  font-family: "open-sans";
-}
-.futura-pt {
-
-}
-.futura-pt p {
-  font-family: "adelle";
-}
-.futura-pt h1, .futura-pt h2 {
-  font-family: "futura-pt";
-  text-transform: uppercase;
-}
-.futura-pt h3 {
-  font-weight: bold;
-}
-*/
-
-
 img {
   width: auto;
   max-width: 100%;
@@ -155,24 +110,6 @@ h1 {
   
 }
 
-
-.row--nav {
-  text-align: center;
-  font-size: 32px;
-  line-height: 1.1; 
-}
-.navigation {
-  width: 100%;
-  background: #F7F7F7;
-  position: relative;
-  padding: 30px 0;
-}
-.navigation a {
-  text-decoration: none;
-  border-bottom: 1px solid #92ABCD;
-}
-
-
 h2 {
   font-size: 32px;
   line-height: 1.1;
@@ -398,24 +335,6 @@ img.img--with_border {
   border-radius: 50%;
 }
 
-/*.section--developers {
-  color: #FFF;
-  background: #222;
-  background: #1f1f1f;
-  padding: 80px 0;
-}
-.section--developers a {
-  color:#FFFFFF;
-}
-.section--developers p,
-.section--developers  ul.text-list {
-  color: #ddd;
-}
-.section--developers .section--head p,
-.section--developers .title-block p {
-  color: #ddd;
-}*/
-
 .row--modules ul {
   list-style: none;
   margin-left: 0;
@@ -428,19 +347,6 @@ img.img--with_border {
   margin-left:-10px;
 }
 
-.size-chart {
-  width: 100%;
-  display: inline-block;
-}
-
-.size-chart div {
-  height: 21px;
-  float: left;
-  font-size: 13px;
-  padding: 4px;
-  line-height: 1;
-}
-
 .block__ui-separated {
   position: relative;
 }
@@ -467,7 +373,7 @@ img.img--with_border {
   float: left;
   margin: 0 12px 12px 0;
   width: 171px;
-  line-height: 0;
+/*  line-height: 0; */
 }
 
 
@@ -477,27 +383,6 @@ a.demo-gallery__img--main {
   height: auto;
 }
 
-.ukraine-flag {
-  width: 21px;
-  height: 14px;
-  position: relative;
-  background: #ffcc00;
-  top: 1px;
-  display: inline-block;
-}
-.ukraine-flag:before {
- content:'';
- position: absolute;
- width: 21px;
- height: 7px;
- left:0;
- top:0;
- background: #0066cc;
-}
-
-.demo-gallery figure {
-  display: none;
-}
 .demo-gallery__title {
   line-height: 14px;
   font-size: 14px;
@@ -507,48 +392,7 @@ a.demo-gallery__img--main {
   float: left;
 }
 
-.share-buttons h2 {
-  text-align: center;
-  border: 0;
-  
-}
-.share-buttons {
-  text-align: center;
-  position: relative;
-  margin: 0 0 24px;
-}
-.share-buttons a {
-  -moz-border-radius: 2px;
-  border-radius: 2px;
-  display: inline-block;
-  padding: 10px 20px;
-  margin: 10px;
-  color: #FFF;
-  text-decoration: none;
-  background: #5AAF63;
-  font-size: 16px;
-  line-height: 22px;
-  cursor: pointer;
-}
-.share-buttons a:hover {
-  opacity: 0.7;
-}
-#tweet {
-  background: #0096c4;
-}
-#like {
-  background: #3b5998;
-}
-#gplus {
-  background: #d34836;
-}
-
-
-
 @media screen and (max-width: 1000px) {
-
- 
-
   .row--wide {
     max-width: 800px;
   }
@@ -673,18 +517,6 @@ a.demo-gallery__img--main {
   }
 }
 
-/*@media screen and (max-width: 490px) {
-  .demo-gallery a {
-    width: 100px;
-    margin: 0 4px 4px 0;
-  }
-  a.demo-gallery__img--main {
-    width: 137px;
-  }
-}
-*/
-
-
 @media screen and (max-width: 450px) {
   .demo-gallery a {
     width: 95px;
@@ -705,8 +537,3 @@ margin: 0 1px 1px 0;
     width: 109px;
   }
 }
-
-
-
-
-

+ 40 - 15
assets/site.js

@@ -1,3 +1,43 @@
+function setAlbums(albums) {
+    var dAlbums = document.getElementById('albums');
+    dAlbums.innerHTML = '';
+    Array.prototype.forEach.call(albums, function(alb, i){
+        var a = appendElement(dAlbums, 'a');
+        var img = appendElement(a, 'img');
+        img.src = alb.cover.src;
+        if (alb.title) {
+            var fig = appendElement(a, 'figure');
+            fig.textContent = alb.title;
+        };
+        a.addEventListener('click', function() {
+            startGallery(alb.photos);
+        });
+    });
+}
+
+function startGallery(items, index) {
+    var pswpElement = document.querySelectorAll('.pswp')[0];
+
+    var options = {
+        index: index || 0
+    };
+
+    // Initializes and opens PhotoSwipe
+    var gallery = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, items, options);
+    gallery.init();
+
+    return gallery;
+}
+
+  function appendElement(parent, tag, id, className, innerHTML) {
+    var el = document.createElement(tag);
+    if (id) el.id = id;
+    if (className) el.className = className;
+    if (innerHTML) el.innerHTML = innerHTML;
+    parent.appendChild(el);
+    return el;
+  }
+
   function xdr(url, method, data, callback, errback) {
     var req;
     if (!callback)
@@ -54,18 +94,3 @@
       callback(JSON.parse(data));
     }, errback);
   }
-
-function startGallery(items, index) {
-
-    var pswpElement = document.querySelectorAll('.pswp')[0];
-
-    var options = {
-        index: index || 0
-    };
-
-    // Initializes and opens PhotoSwipe
-    var gallery = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, items, options);
-    gallery.init();
-
-    return gallery;
-}

+ 1 - 0
exif.lisp

@@ -37,6 +37,7 @@
           (modified (file-write-date path))
           (exif (ignore-errors (zpb-exif:make-exif in))))
       (list
+       (cons :path path)
        (cons :name (pathname-name path))
        (cons :modified modified)
        (cons :length length)

+ 12 - 5
index.html

@@ -40,6 +40,12 @@
 	  </div>
     </div>
 
+    <div class="section">
+	  <div class="row">
+        <div id="albums" class="demo-gallery"></div>
+      </div>
+    </div>
+
     <div id="gallery" class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
       <div class="pswp__bg"></div>
       <div class="pswp__scroll-wrap">
@@ -52,7 +58,7 @@
           <div class="pswp__top-bar">
 			<div class="pswp__counter"></div>
 			<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
-			<button class="pswp__button pswp__button--share" title="Share"></button>
+			<!--<button class="pswp__button pswp__button--share" title="Share"></button> -->
 			<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
 			<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
 			<div class="pswp__preloader">
@@ -63,12 +69,12 @@
 			  </div>
 			</div>
           </div>
-		  <!-- <div class="pswp__loading-indicator"><div class="pswp__loading-indicator__line"></div></div> -->
-          <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
+		  <div class="pswp__loading-indicator"><div class="pswp__loading-indicator__line"></div></div>
+          <!--<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
 	        <div class="pswp__share-tooltip">
 	        </div>
 	      </div>
-
+          -->
           <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button>
           <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button>
           <div class="pswp__caption">
@@ -81,7 +87,8 @@
 
     <script type="text/javascript">
       jsonGET('/incoming/', function(data) {
-          startGallery(data);
+          setAlbums(data);
+          //startGallery(data);
       });
     </script>
   </body>

+ 2 - 3
photo-store.asd

@@ -3,13 +3,12 @@
   :description "Home photo storage"
   :author "Innokentiy Enikeev <me@enikesha.net>"
   :license "MIT"
-  :depends-on (
+  :depends-on (:cl-json
                :geo
                :local-time
                :log4cl
                :restas
-               :zpb-exif
-               )
+               :zpb-exif)
   :serial t
   :components ((:file "package")
                (:file "utils")

+ 45 - 23
photo-store.lisp

@@ -8,20 +8,40 @@
 ;; photos: id | album_id | filename | size | taken | width | height | lat | lon
 ;; albums: id | parent_id | path | date | name | description | cover_id | accessibility
 
-(defstruct photo url title width height)
+(defun imagep (path)
+  (equal (string-downcase (pathname-type path)) "jpg"))
 
+(defun dirname (directory)
+  (car (last (pathname-directory directory))))
 
-(defun load-photo (path)
-  (let ((info (load-photo-info path)))
-    (make-photo :url (map-path path)
-                :title (aget :name info)
-                :width (first (aget :dim info))
-                :height (second (aget :dim info)))))
+(defun guess-album-title (directory photos)
+  (declare (ignore photos))
+  (dirname directory))
 
-(defun load-photos-from-dir (directory)
-  (mapcar 'load-photo (remove-if-not #'(lambda (p)
-                                         (equal (string-downcase (pathname-type p)) "jpg"))
-                                     (uiop:directory-files directory))))
+(defvar *album-path-format*
+  '(:year "/" :year "-" (:month 2) "-" (:day 2) "-" :name "/"))
+
+(defun guess-album-path (directory photos)
+  (let ((from (reduce #'local-time:timestamp-minimum photos :key (agetter :created-at)))
+        (format (sublis `((:name . ,(dirname directory)))
+                        *album-path-format*)))
+    (local-time:format-timestring nil from :format format)))
+
+(defun middle (seq)
+  (elt seq (/ (length seq) 2)))
+
+(defun load-album (directory)
+  (let ((photos (mapcar 'load-photo-info (remove-if-not 'imagep (uiop:directory-files directory)))))
+    (when photos
+      (list (cons :title (guess-album-title directory photos))
+            (cons :path (guess-album-path directory photos))
+            (cons :cover (middle photos))
+            (cons :photos photos)))))
+
+(defun load-albums-from-dir (directory)
+  (remove-if #'null (list* (load-album directory)
+                           (loop for subdir in (uiop:subdirectories directory)
+                              append (load-albums-from-dir subdir)))))
 
 (defvar *path-url-mapping* nil "alist of path-2-url mapping")
 (defun map-path (path)
@@ -30,13 +50,17 @@
      when rel-path do (return (concatenate 'string base-url (namestring rel-path)))
      finally (error "Can't map path ~S" path)))
 
-(defmethod yason:encode ((photo photo) &optional (stream *standard-output*))
-  (yason:with-output (stream)
-    (yason:with-object ()
-      (yason:encode-object-element "src" (photo-url photo))
-      (yason:encode-object-element "w" (photo-width photo))
-      (yason:encode-object-element "h" (photo-height photo))
-      (yason:encode-object-element "title" (photo-title photo)))))
+(defun photo-item (info)
+  `((:src . ,(map-path (aget :path info)))
+    (:title . ,(aget :name info))
+    (:w . ,(first (aget :dim info)))
+    (:h . ,(second (aget :dim info)))))
+
+(defun album-item (album)
+  `((:title . ,(aget :title album))
+    (:cover . ,(photo-item (aget :cover album)))
+    (:path . ,(namestring (aget :path album)))
+    (:photos . ,(mapcar 'photo-item (aget :photos album)))))
 
 (restas:define-route main ("")
   (asdf:system-relative-pathname :photo-store "index.html"))
@@ -51,15 +75,13 @@
 (restas:define-route assets/js ("js/:file" :content-type "application/x-javascript")
   (merge-pathnames file *assets-path*))
 
+(defun json (object)
+  (json:encode-json-to-string object))
 
 (defvar *incoming-path* nil "Path for incoming photos")
 (restas:define-route incoming ("incoming/"
                                :content-type "application/json")
-  (with-output-to-string (stream)
-    (yason:encode
-     (loop for subdir in (uiop:subdirectories *incoming-path*)
-        append (load-photos-from-dir subdir))
-     stream)))
+  (json (mapcar 'album-item (load-albums-from-dir *incoming-path*))))
 
 (defun start (&key (address "0.0.0.0") (port 8800))
   (restas:start '#:photo-store :address address :port port))

+ 2 - 0
utils.lisp

@@ -4,6 +4,8 @@
 (defun aget (key alist)
   (cdr (assoc key alist :test #'equal)))
 
+(defun agetter (key)
+  (lambda (x) (aget key x)))
 
 ;; From 'Practical Common Lisp' by Peter Seibel
 (defun component-present-p (value)