tagging/untagging should now work fully in taggr
authorterom
Sun, 20 Jan 2008 01:07:02 +0000
changeset 24 001f52cd057e
parent 23 10841abbc01f
child 25 4b3cf12848c2
tagging/untagging should now work fully in taggr
de-cgi-bin/taggr2.py
degal.py
javascript/taggr.js
lib/db.py
lib/req.py
templates/taggr.html
templates/taggr_dir.html
--- a/de-cgi-bin/taggr2.py	Thu Jan 17 01:56:04 2008 +0000
+++ b/de-cgi-bin/taggr2.py	Sun Jan 20 01:07:02 2008 +0000
@@ -26,7 +26,6 @@
 from lib import db, shorturl, settings, utils, template, req
 
 
-
 tpl = template.Template("taggr")
 
 act = req.get_str("act", None)
@@ -55,9 +54,11 @@
             dirs.append((fname, fpath))
 
         elif utils.isImage(fname) :
-            key = shorturl.int2key(db.select("""SELECT id FROM images WHERE dirpath=? AND filename=?""", path, fname).fetchone()[0])
+            id = db.select("""SELECT id FROM images WHERE dirpath=? AND filename=?""", path, fname).fetchone()[0]
 
-            imgs.append((key, os.path.join(path, settings.THUMB_DIR, fname)))
+            tags = [tag for type, tag in db.select("""SELECT type, tag FROM tags WHERE image=?""", id)]
+
+            imgs.append((id, os.path.join(path, settings.THUMB_DIR, fname), tags))
 
     dirs.sort()
     imgs.sort()
@@ -72,6 +73,22 @@
         images                  = imgs,
     )
 
+elif act == "tag" :
+    img_list = req.get_int_list("img")
+    tag = req.get_str("tag")
+
+    db.insert_many("""INSERT INTO tags (image, tag) VALUES (?, ?)""", ((img, tag) for img in img_list))
+
+    print "OK"
+
+elif act == "untag" :
+    img = req.get_int("img")
+    tag = req.get_str("tag")
+
+    db.delete("""DELETE FROM tags WHERE image=? AND tag=?""", img, tag)
+
+    print "OK"
+
 elif act == None :
     dirs, imgs = dirlist(".")
 
@@ -80,7 +97,7 @@
         open_dir_contents = dirlist(path)
     else :
         open_dir = open_dir_contents = None
-
+    
     print template.Template("taggr").render(
         stylesheet_url          = utils.url("style.css", up=1),
         title                   = "Taggr",
--- a/degal.py	Thu Jan 17 01:56:04 2008 +0000
+++ b/degal.py	Sun Jan 20 01:07:02 2008 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.4
+#!/usr/bin/env python2.5
 #
 # DeGAL - A pretty simple web image gallery
 # Copyright (C) 2007 Tero Marttila
--- a/javascript/taggr.js	Thu Jan 17 01:56:04 2008 +0000
+++ b/javascript/taggr.js	Sun Jan 20 01:07:02 2008 +0000
@@ -1,11 +1,24 @@
+/*
+ * dir-list manipulation
+ */
 function toggle_dir (header_a_tag) {
     var div = Element.next(header_a_tag);
     
     if (div._have_contents) {
-        if (div.visible())
+        if (div.visible()) {
             Effect.BlindUp(div, {duration: 0.5});
-        else
+            func = hide_tag_image;
+        } else {
             Effect.BlindDown(div, {duration: 0.5});
+            func = show_tag_image;
+        }
+        
+        Element.up(header_a_tag)._images.each(function(key){
+            $("img_" + key)._tags.each(function(tag){
+                func(tag, key);
+            });
+        });
+
     } else {
 
         new Ajax.Updater(div, "taggr2.py", {
@@ -22,37 +35,296 @@
     }
 }
 
-function new_tag (tag_name) {
-    var tag_images = Builder.node("td", {className:"tag_images"});
+var g_last_select;
+function image_click (img, ev) {
+    var updiv = img.up(2);
 
-    $("tag_table").appendChild(
-        Builder.node("tr", [
-            Builder.node("td", {className:"tag"}, [
-                tag_name
-            ]),
-            tag_images
-        ])
-    );
+    if (img.hasClassName("selection")) {
+        img.removeClassName("selection");
+
+    } else if (ev.shiftKey && g_last_select && g_last_select.up(2) == updiv) {
+        var from = Math.min(img._offset, g_last_select._offset);
+        var to = Math.max(img._offset, g_last_select._offset);
+
+        for (var i = from; i <= to; i++) {
+            $("img_" + updiv._images[i]).addClassName("selection");
+        }
+
+        g_last_select = img;
+    } else {
+        
+        g_last_select = img;
+        img.addClassName("selection");
+    }
 }
 
-function image_dropped (img, tag, e) {
-}
-
-function image_loaded (img) {
+function selection_clear () {
+    $$(".selection").each(function(e){
+        e.removeClassName("selection");
+    });
 }
 
-function image_click (img, ev) {
+/*
+ * image-loading callback
+ */
+function image_info (img, offset, key, tags) {
+    img._key = key;
+    img._tags = $A(tags);
+    img._offset = offset;
+    
+    img._tags.each(function(tag){
+        if (!$('tag_' + tag + '_images'))
+            new_tag(tag);
 
+        tag_add_image(img, null, tag, false);
+    });
+
+    var dir = img.up(2);
+
+    if (!dir._images)
+        dir._images = new Array();
+
+    dir._images.push(img._key);
 }
 
-function init () {
+/*
+ * tag list manipulation code
+ */
+function _tag_name (tag_name) {
+    var tag_name_p = Builder.node("p", {id:"tag_" + tag_name + "_name", className: "draggable tag_name"}, [tag_name]);
+
+    tag_name_p._tag = tag_name;
+
+    return tag_name_p;
 }
 
+function new_tag (tag_name) {
+    var tag_images = Builder.node("td", {className:"tag_images", id:"tag_" + tag_name + "_images"});
+
+    tag_images._tag = tag_name;
+    tag_images._visibleCount = 0;
+    tag_images._images = new Array();
+
+    var tag_row = Builder.node("tr", {className:"tag_row", id:"tag_" + tag_name}, [
+        Builder.node("td", {className:"tag", id:"tag_" + tag_name + "_tags"}, [
+            _tag_name(tag_name)
+        ]),
+        tag_images
+    ]);
+
+    tag_row._tag = tag_name;
+    tag_row._tags = new Array(tag_name);
+
+    $("tag_table").appendChild(tag_row);
+}
+
+function tag_add_tag (new_tag, tag) {
+    var tag_row = $("tag_" + tag);
+
+    if (tag_row._tags.indexOf(new_tag) != -1)
+        return false;
+    
+    tag_row._tags.push(new_tag);
+
+    $("tag_" + tag + "_tags").appendChild(_tag_name(new_tag));
+
+/*
+    new Ajax.Request("taggr2.py", {
+        parameters: {
+            act: 'tag',
+            tag: new_tag,
+            img: $("tag_" + tag + "_images")._images,
+        },
+        method: 'get'
+    });
+*/
+}
+
+function tag_remove_tag (tag_name) {
+    var tag_row = $("tag_" + tag_name + "_name").up(1);
+    array_remove(tag_row._tags, tag_name);
+    
+    $("tag_" + tag_row._tag + "_tags").removeChild($("tag_" + tag_name + "_name"));
+
+    if (tag_row._tags.length == 0)
+        $("tag_table").removeChild(tag_row);
+}
+
+function get_tag (tag_name, postfix) {
+    return $("tag_" + $("tag_" + tag_name).up(1)._tag + "_" + postfix);
+}
+
+function tag_add_image (orig_img, copy_img, tag, saveToDB) {
+    var tag_images = $('tag_' + tag + '_images');
+
+    if (tag_images._images.indexOf(orig_img._key) != -1)
+        return false;
+
+    if (!copy_img)
+        copy_img = image_copy(orig_img);
+
+    tag_images._images.push(orig_img._key)
+    orig_img._tags.push(tag);
+
+    copy_img.id = "tag_" + tag + "_img_" + orig_img._key
+    copy_img._tag = tag;
+
+    tag_images.appendChild(copy_img);
+    tag_images._visibleCount++;
+    
+    if (saveToDB) {
+        new Ajax.Request("taggr2.py", {
+            parameters: {
+                act: 'tag',
+                img: orig_img._key,
+                tag: tag,
+            },
+            method: 'get'
+
+        });
+    }
+}
+
+function tag_add_images (images, tag_name) {
+    var tag_images = $('tag_' + tag_name + '_images');
+    var copy_img, img_keys = new Array();
+
+    images.each(function(orig_img){
+        tag_add_image(orig_img, null, tag_name, false);
+        img_keys.push(orig_img._key);
+    });
+
+    new Ajax.Request("taggr2.py", {
+        parameters: {
+            act: 'tag',
+            img: img_keys,
+            tag: tag_name,
+        },
+        method: 'get'
+
+    });
+}
+
+function tag_remove_image (img_key, tag_name) {
+    var tag_images = $("tag_" + tag_name + "_images");
+
+    tag_images.removeChild($("tag_" + tag_name + "_img_" + img_key));
+    array_remove(tag_images._images, img_key);
+
+    if (tag_images._images.length == 0)
+        $("tag_" + tag_name).hide();
+
+    new Ajax.Request("taggr2.py", {
+        parameters: {
+            act: 'untag',
+            img: img_key,
+            tag: tag_name,
+        },
+        method: 'get',
+    });
+}
+
+function hide_tag_image (tag, img) {
+    $("tagimg_" + tag + "_" + img).hide();
+
+    if (--$("tag_" + tag + "_images")._visibleCount == 0)
+        $("tag_" + tag).hide();
+}
+
+function show_tag_image (tag, img) {
+    $("tagimg_" + tag + "_" + img).hide();
+
+    if ($("tag_" + tag + "_images")._visibleCount++ == 0)
+         $("tag_" + tag).show();
+}
+
+/*
+ * Frontend image drag code
+ */
+var g_ghost;
+function drag_start (e) {
+    if (e.hasClassName("image") || e.hasClassName("tag_image"))
+        g_ghost = image_copy(e);
+    else {
+        var name;
+
+        if (e.hasClassName("tag_name"))
+            name = e._tag;
+        else
+            name = $F($('new_tag'));
+
+        g_ghost = Builder.node("span", {className: 'dragged'}, [name]);
+    }
+
+    $('taggr').appendChild(g_ghost);
+
+    g_ghost.style.position = "absolute";
+}
+
+function drag_move (e, ev) {
+    g_ghost.style.left = ev.pointerX() - g_offset.left + "px";
+    g_ghost.style.top = ev.pointerY() - g_offset.top + "px";
+}
+
+function drag_end_drop (e, s, ev) {
+    g_ghost.style.position = "static";
+    $('taggr').removeChild(g_ghost);
+    
+    if (e.hasClassName("image") || e.hasClassName("tag_image")) {
+        var selection = $$(".selection");
+
+        if (selection.length)
+            tag_add_images($$(".selection"), s._tag);
+        else
+            tag_add_image(e, g_ghost, s._tag, true);
+            
+/*
+        if (!tag_add_image(e, g_ghost, s._tag, true))
+            // it was already in there
+            Effect.Shake("tagimg_" + s._tag + "_" + g_ghost._key);
+*/            
+    } else if (e.hasClassName("tag_name")) {
+        null;   // breakpoint
+    } else {
+        tag_add_tag($F($('new_tag')), s._tag);
+    }
+}
+
+function drag_end_fail (e, ev) {
+    if (e.hasClassName("tag_image")) {
+        tag_remove_image(e._key, e._tag);
+        $('taggr').removeChild(g_ghost);
+    } else if (e.hasClassName("tag_name")) {
+        tag_remove_tag(e._tag);
+    } else {
+        $('taggr').removeChild(g_ghost);
+    }
+}
+
+function drag_cleanup (e, ev) {
+    g_ghost = null;
+}
+
+function drag_hover (e, s, ev) {
+    s.addClassName("hover");
+}
+
+function drag_unhover (e, s, ev) {
+    s.removeClassName("hover");
+}
+
+/*
+ * Backend image click/drag code
+ */
+
 var g_drag, g_hover, g_start, g_offset, g_targets;
 function check_mouse_down (ev) {
     var e = ev.element();
+    
+    if (!ev.isLeftClick())
+        return true;
 
-    if (e.hasClassName("image")) {
+    if (e.hasClassName("image") || e.hasClassName("draggable") || e.hasClassName("tag_image")) {
         g_drag = e;
         g_start = false;
         
@@ -65,7 +337,7 @@
 
         g_hover = null;
 
-        g_targets = $$(".tag_images");
+        g_targets = $$(".tag_row");
 
     } else if (e.hasClassName("directory_link")) {
         toggle_dir(e);
@@ -88,8 +360,13 @@
                 drag_end_fail(g_drag, ev);
             
             drag_cleanup(g_drag);
-        } else
-            image_click(g_drag, ev);
+        } else if (g_drag.hasClassName("image") || g_drag.hasClassName("tag_image")) {
+            // in case image_click blocks and we get a mouse_move during it
+            var tmp = g_drag;
+            g_drag = null;
+
+            image_click(tmp, ev);
+        }
 
         g_start = null;
         g_drag = null;
@@ -124,6 +401,7 @@
                 if (Position.withinIncludingScrolloffsets(g_targets[i], px, py)) {
                     drag_hover(g_drag, g_targets[i], ev);
                     g_hover = g_targets[i];
+                    break;
                 }
         }
 
@@ -133,10 +411,10 @@
 }
 
 function check_drag_hover (ev) {
-        var e = ev.element();
     if (g_start) {
+        var e = ev.element();
 
-        if (e.hasClassName("tag_images")) {
+        if (e.hasClassName("tag_row")) {
             drag_hover(g_drag, e, ev);
             g_hover = e;
         }
@@ -144,8 +422,8 @@
 }
 
 function check_drag_nohover (ev) {
+    if (g_start) {
         var e = ev.element();
-    if (g_start) {
 
         if (g_hover && e == g_hover) {
             drag_unhover(g_drag, g_hover, ev);
@@ -154,64 +432,33 @@
     }
 }
 
-var g_ghost;
-function drag_start (e) {
-    g_ghost = Builder.node("img", {
-        src: e.src
+/* 
+ * utility code
+ */
+function image_copy (img) {
+    copy = Builder.node("img", {
+        src: img.src,
+        className: "tag_image",
     });
 
-    g_ghost._key = e.id;
-
-    $('taggr').appendChild(g_ghost);
-
-    g_ghost.style.position = "absolute";
-}
-
-function drag_move (e, ev) {
-    g_ghost.style.left = ev.pointerX() - g_offset.left + "px";
-    g_ghost.style.top = ev.pointerY() - g_offset.top + "px";
+    copy._key = img._key;
+    copy._tags = img._tags;
+    
+    return copy;
 }
 
-function drag_end_drop (e, s, ev) {
-    if (!s.images)
-        s.images = new Array();
-
-    if (s.images.indexOf(g_ghost._key) != -1)
-        return drag_end_fail();
-
-    g_ghost.id = s.id + "_" + g_ghost._key
-
-    s.images.push(g_ghost._key)
-
-    $('taggr').removeChild(g_ghost);
-    s.appendChild(g_ghost);
-
-    g_ghost.style.position = "static";
+function array_remove (a, i) {
+    a.splice(a.indexOf(i), 1);
 }
 
-function drag_end_fail (e, ev) {
-    $('taggr').removeChild(g_ghost);
-}
-
-function drag_cleanup (e, ev) {
-    g_ghost = null;
-}
-
-function drag_hover (e, s, ev) {
-    s.addClassName("hover");
-}
-
-function drag_unhover (e, s, ev) {
-    s.removeClassName("hover");
-}
-
-Event.observe(window, "load", init);
+/*
+ * events
+ */
 Event.observe(document, "mousedown", check_mouse_down);
 Event.observe(document, "mouseup", check_mouse_up);
 Event.observe(document, "mousemove", check_mouse_move);
 Event.observe(document, "mouseover", check_drag_hover);
 Event.observe(document, "mouseout", check_drag_nohover);
-Event.observe(document, "dragenter", check_drag_hover);
+Event.observe(document, "dragenter", check_drag_hover);     // worth a try...
 Event.observe(document, "dragexit", check_drag_nohover);
 
-
--- a/lib/db.py	Thu Jan 17 01:56:04 2008 +0000
+++ b/lib/db.py	Sun Jan 20 01:07:02 2008 +0000
@@ -20,7 +20,7 @@
 
 import sqlite3
 
-conn = sqlite3.connect("degal.db")
+conn = sqlite3.connect("db/degal.db")
 
 def execute (expr, *args) :
     c = conn.cursor()
@@ -28,10 +28,33 @@
 
     return c
 
-def insert (expr, *args) :
-    return execute(expr, *args).rowcount
+def execute_many (expr, iter) :
+    c = conn.cursor()
+    c.executemany(expr, iter)
+
+    return c
+
+def commit (cursor) :
+    try :
+        cursor.execute("COMMIT")
+    except sqlite3.OperationalError :
+        pass    # ffs. INSERT just doesn't do anything otherwise
+
+    return cursor.rowcount
+
+def execute_commit (expr, *args) :
+    return commit(execute(expr, *args))
+
+def execute_commit_many (expr, iter) :
+    return commit(execute_many(expr, iter))
 
 select = execute
 
+delete = execute_commit
+insert = execute_commit
+
+delete_many = execute_commit_many
+insert_many = execute_commit_many
+
 cursor = conn.cursor
 
--- a/lib/req.py	Thu Jan 17 01:56:04 2008 +0000
+++ b/lib/req.py	Sun Jan 20 01:07:02 2008 +0000
@@ -48,3 +48,10 @@
     else :
         return default
 
+def get_int_list (key, default=REQUIRED_PARAM) :
+    if key in vars :
+      return [int(val) for val in vars.getlist(key)]
+    elif default is REQUIRED_PARAM :
+        raise ValueError("Required param %s" % key)
+    else :
+        return default
--- a/templates/taggr.html	Thu Jan 17 01:56:04 2008 +0000
+++ b/templates/taggr.html	Sun Jan 20 01:07:02 2008 +0000
@@ -4,62 +4,14 @@
     <div id="taggr">
         <div id="tags">
             <table cellpadding="0" cellspacing="0" id="tag_table">
-                <tr>
-                    <td class="tag">
-                        otaniemi
-                    </td>
-                    <td class="tag_images" id="otaniemi">
-                        <img src="http://photos.marttila.de/otaniemi/2007-10-31/thumbs/PA315851.JPG" />
-                        <img src="http://photos.marttila.de/otaniemi/2007-10-31/thumbs/PA315859.JPG" />
-                    </td>
-                </tr>
-                <tr>
-                    <td class="tag">
-                        otaranta
-                    </td>
-                    <td class="tag_images" id="otaranta">
-                        <img src="http://photos.marttila.de/otaniemi/2007-10-31/thumbs/PA295785.JPG" />
-                        <img src="http://photos.marttila.de/otaniemi/2007-10-31/thumbs/PA295787.JPG" />
-                        <img src="http://photos.marttila.de/otaniemi/2007-10-31/thumbs/PA295806.JPG" />
-                    </td>
-                </tr>
-                <tr>
-                    <td class="tag">
-                        sik
-                    </td>
-                    <td class="tag_images" id="sik">
-                        <img src="http://photos.marttila.de/otaniemi/2007-10-31/thumbs/PA315851.JPG" />
-                        <img src="http://photos.marttila.de/otaniemi/2007-10-31/thumbs/PA315859.JPG" />
-                    </td>
-                </tr>   
-                <tr>
-                    <td class="tag">
-                        <ul>
-                            <li>sky</li>
-                            <li>clouds</li>
-                        </ul>
-                    </td>
-                    <td class="tag_images" id="sky/clouds">
-                        <img src="http://photos.marttila.de/otaniemi/2007-10-31/thumbs/PA295821.JPG" />
-                    </td>
-                </tr>   
-<!--                
-                <tr>
-                    <td class="tag">
-
-                    </td>
-                    <td class="tag_images">
-
-                    </td>
-                </tr>   
--->                
             </table>
-            <label for="tag_name">New tag</label>
-            <input type="text" name="new_tag" id="new_tag" />
-            <input type="button" value="Add" onclick="new_tag($F($('new_tag')))" />
-
         </div>
-
+        <div id="toolbar">
+            <ul>
+                <li><a href="#" onclick="selection_clear()">Clear selection</a></li>
+                <li><input type="text" name="new_tag" id="new_tag" /><a href="#" onclick="new_tag($F($('new_tag')))" class="draggable">Create Tag</a></li>
+            </ul>
+        </div>
         <div id="images">
             <%include file="taggr_dir.html" />
         </div>
--- a/templates/taggr_dir.html	Thu Jan 17 01:56:04 2008 +0000
+++ b/templates/taggr_dir.html	Sun Jan 20 01:07:02 2008 +0000
@@ -12,8 +12,8 @@
 % endif
 
 <div class="images">
-% for key, image in images :
-    <img src="../${image}" onload="image_loaded(this)" class="image" id="${key}" />
+% for i, (id, image, tags) in enumerate(images) :
+    <img src="../${image}" onload="image_info(this, ${i}, ${id}, [${",".join(("'%s'" % tag for tag in tags))}])" class="image" id="img_${id}" />
 % endfor
 </div>