Browse Source

Add photo albums and hook them up to the blog

pull/2/head
Noah Petherbridge 5 years ago
parent
commit
91536e3d05
43 changed files with 7133 additions and 7 deletions
  1. +26
    -2
      config-sample.py
  2. +2
    -0
      requirements.txt
  3. +3
    -0
      rophako/__init__.py
  4. +542
    -0
      rophako/model/photo.py
  5. +13
    -0
      rophako/model/user.py
  6. +7
    -3
      rophako/modules/blog.py
  7. +246
    -0
      rophako/modules/photo.py
  8. +8
    -1
      rophako/utils.py
  9. +3
    -1
      rophako/www/blog/entry.inc.html
  10. BIN
      rophako/www/css/Jcrop.gif
  11. +165
    -0
      rophako/www/css/jquery.Jcrop.css
  12. +29
    -0
      rophako/www/css/jquery.Jcrop.min.css
  13. BIN
      rophako/www/css/ui-lightness/images/animated-overlay.gif
  14. BIN
      rophako/www/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png
  15. BIN
      rophako/www/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png
  16. BIN
      rophako/www/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png
  17. BIN
      rophako/www/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png
  18. BIN
      rophako/www/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png
  19. BIN
      rophako/www/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png
  20. BIN
      rophako/www/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png
  21. BIN
      rophako/www/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
  22. BIN
      rophako/www/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
  23. BIN
      rophako/www/css/ui-lightness/images/ui-icons_222222_256x240.png
  24. BIN
      rophako/www/css/ui-lightness/images/ui-icons_228ef1_256x240.png
  25. BIN
      rophako/www/css/ui-lightness/images/ui-icons_ef8c08_256x240.png
  26. BIN
      rophako/www/css/ui-lightness/images/ui-icons_ffd27a_256x240.png
  27. BIN
      rophako/www/css/ui-lightness/images/ui-icons_ffffff_256x240.png
  28. +485
    -0
      rophako/www/css/ui-lightness/jquery-ui-1.10.4.custom.css
  29. +7
    -0
      rophako/www/css/ui-lightness/jquery-ui-1.10.4.custom.min.css
  30. +2747
    -0
      rophako/www/js/jquery-ui-1.10.4.custom.js
  31. +6
    -0
      rophako/www/js/jquery-ui-1.10.4.custom.min.js
  32. +1694
    -0
      rophako/www/js/jquery.Jcrop.js
  33. +22
    -0
      rophako/www/js/jquery.Jcrop.min.js
  34. +661
    -0
      rophako/www/js/jquery.color.js
  35. +33
    -0
      rophako/www/photos/album.html
  36. +37
    -0
      rophako/www/photos/albums.html
  37. +60
    -0
      rophako/www/photos/arrange_albums.html
  38. +58
    -0
      rophako/www/photos/arrange_photos.html
  39. +107
    -0
      rophako/www/photos/crop.html
  40. +16
    -0
      rophako/www/photos/delete.html
  41. +18
    -0
      rophako/www/photos/edit.html
  42. +89
    -0
      rophako/www/photos/upload.html
  43. +49
    -0
      rophako/www/photos/view.html

+ 26
- 2
config-sample.py View File

@@ -25,6 +25,9 @@ SECRET_KEY = 'for the love of Arceus, change this key!'
# Password strength: number of iterations for bcrypt to hash passwords.
BCRYPT_ITERATIONS = 12

# Where to save temp files for photo uploads, etc.
TEMPDIR = "/tmp"

# Rophako uses a flat file JSON database system, and the Redis caching server
# sits between Ropahko and the filesystem.
DB_ROOT = "db"
@@ -33,10 +36,31 @@ REDIS_PORT = 6379
REDIS_DB = 0
REDIS_PREFIX = "rophako:"

# Blog settings
################################################################################
## Blog Settings ##
################################################################################

BLOG_ENTRIES_PER_PAGE = 5 # Number of entries to show per page
BLOG_ENTRIES_PER_RSS = 5 # The same, but for the RSS feed
BLOG_DEFAULT_CATEGORY = "Uncategorized"
BLOG_DEFAULT_PRIVACY = "public"
BLOG_TIME_FORMAT = "%A, %B %d %Y @ %I:%M:%S %p" # "Weekday, Month dd yyyy @ hh:mm:ss AM"
BLOG_ALLOW_COMMENTS = True
BLOG_ALLOW_COMMENTS = True

################################################################################
## Photo Settings ##
################################################################################

# The path to where uploaded photos will be stored.
# The PRIVATE path is from the perspective of the server file system.
# The PUBLIC path is from the perspective of the web browser via HTTP.
PHOTO_ROOT_PRIVATE = os.path.join(_basedir, "site", "www", "static", "photos")
PHOTO_ROOT_PUBLIC = "/static/photos"

PHOTO_DEFAULT_ALBUM = "My Photos" # Default/fallback album name.
PHOTO_TIME_FORMAT = BLOG_TIME_FORMAT

# Photo sizes.
PHOTO_WIDTH_LARGE = 800 # Max width of full size photos.
PHOTO_WIDTH_THUMB = 256 # Max square width of photo thumbnails.
PHOTO_WIDTH_AVATAR = 96 # Square width of photo avatars.

+ 2
- 0
requirements.txt View File

@@ -1,3 +1,5 @@
flask
redis
bcrypt
pillow
requests

+ 3
- 0
rophako/__init__.py View File

@@ -18,9 +18,11 @@ app.secret_key = config.SECRET_KEY
from rophako.modules.admin import mod as AdminModule
from rophako.modules.account import mod as AccountModule
from rophako.modules.blog import mod as BlogModule
from rophako.modules.photo import mod as PhotoModule
app.register_blueprint(AdminModule)
app.register_blueprint(AccountModule)
app.register_blueprint(BlogModule)
app.register_blueprint(PhotoModule)

# Custom Jinja handler to support custom- and default-template folders for
# rendering templates.
@@ -50,6 +52,7 @@ def before_request():
"name": "Rophako",
"version": __version__,
"author": "Noah Petherbridge",
"photo_url": config.PHOTO_ROOT_PUBLIC,
},
"uri": request.path.split("/")[1:],
"session": {


+ 542
- 0
rophako/model/photo.py View File

@@ -0,0 +1,542 @@
# -*- coding: utf-8 -*-

"""Photo album models."""

import os
from flask import g, request
import time
import requests
from PIL import Image
import hashlib
import random

import config
import rophako.jsondb as JsonDB
from rophako.utils import sanitize_name
from rophako.log import logger

# Maps the friendly names of photo sizes with their pixel values from config.
PHOTO_SCALES = dict(
large=config.PHOTO_WIDTH_LARGE,
thumb=config.PHOTO_WIDTH_THUMB,
avatar=config.PHOTO_WIDTH_AVATAR,
)


def list_albums():
"""Retrieve a sorted list of the photo albums."""
index = get_index()
result = []

for album in index["album-order"]:
cover = index["covers"][album]
pic = index["albums"][album][cover]["thumb"]
result.append(dict(
name=album,
cover=pic,
data=index["albums"][album],
))

return result


def list_photos(album):
"""List the photos in an album."""
album = sanitize_name(album)
index = get_index()

if not album in index["albums"]:
return None

result = []
for key in index["photo-order"][album]:
data = index["albums"][album][key]
result.append(dict(
key=key,
data=data,
))

return result


def photo_exists(key):
"""Query whether a photo exists in the album."""
index = get_index()
return key in index["map"]


def get_photo(key):
"""Look up a photo by key. Returns None if not found."""
index = get_index()
photo = None
if key in index["map"]:
album = index["map"][key]
if album in index["albums"] and key in index["albums"][album]:
photo = index["albums"][album][key]
else:
# The map is wrong!
logger.error("Photo album map is wrong; key {} not found in album {}!".format(key, album))
del index["map"][key]
write_index(index)

if not photo:
return None

# Inject additional information about the photo:
# What position it's at in the album, how many photos total, and the photo
# IDs of its siblings.
siblings = index["photo-order"][album]
i = 0
print siblings
for pid in siblings:
if pid == key:
# We found us! Find the siblings.
sprev, snext = None, None
if i == 0:
# We're the first photo. So previous is the last!
sprev = siblings[-1]
if len(siblings) > i+1:
snext = siblings[i+1]
else:
snext = key
elif i == len(siblings)-1:
# We're the last. So next is first!
sprev = siblings[i-1]
snext = siblings[0]
else:
# Right in the middle.
print "MIDDLE!"
sprev = siblings[i-1]
snext = siblings[i+1]

# Inject.
photo["album"] = album
photo["position"] = i+1
photo["siblings"] = len(siblings)
photo["previous"] = sprev
photo["next"] = snext
i += 1

return photo


def get_image_dimensions(pic):
"""Use PIL to get the image's true dimensions."""
filename = os.path.join(config.PHOTO_ROOT_PRIVATE, pic["large"])
img = Image.open(filename)
return img.size


def update_photo(album, key, data):
"""Update photo meta-data in the album."""
index = get_index()

if not album in index["albums"]:
index["albums"][album] = {}
if not key in index["albums"][album]:
index["albums"][album][key] = {}

# Update!
index["albums"][album][key].update(data)
write_index(index)


def crop_photo(key, x, y, length):
"""Change the crop coordinates of a photo and re-crop it."""
index = get_index()
if not key in index["map"]:
raise Exception("Can't crop photo: doesn't exist!")

album = index["map"][key]

# Sanity check.
if not album in index["albums"]:
raise Exception("Can't find photo in album!")

logger.debug("Recropping photo {}".format(key))

# Delete all the images except the large one.
for size in ["thumb", "avatar"]:
pic = index["albums"][album][key][size]
logger.debug("Delete {} size: {}".format(size, pic))
os.unlink(os.path.join(config.PHOTO_ROOT_PRIVATE, pic))

# Regenerate all the thumbnails.
large = index["albums"][album][key]["large"]
source = os.path.join(config.PHOTO_ROOT_PRIVATE, large)
for size in ["thumb", "avatar"]:
pic = resize_photo(source, size, crop=dict(
x=x,
y=y,
length=length,
))
index["albums"][album][key][size] = pic

# Save changes.
write_index(index)


def set_album_cover(album, key):
"""Change the album's cover photo."""
album = sanitize_name(album)
index = get_index()
logger.info("Changing album cover for {} to {}".format(album, key))
if album in index["albums"] and key in index["albums"][album]:
index["covers"][album] = key
write_index(index)
return
logger.error("Failed to change album index! Album or photo not found.")


def edit_photo(key, data):
"""Update a photo's data."""
index = get_index()
if not key in index["map"]:
logger.warning("Tried to delete photo {} but it wasn't found?".format(key))
return

album = index["map"][key]

logger.info("Completely deleting the photo {} from album {}".format(key, album))
index["albums"][album][key].update(data)

write_index(index)


def delete_photo(key):
"""Delete a photo."""
index = get_index()
if not key in index["map"]:
logger.warning("Tried to delete photo {} but it wasn't found?".format(key))
return

album = index["map"][key]

logger.info("Completely deleting the photo {} from album {}".format(key, album))
photo = index["albums"][album][key]

# Delete all the images.
for size in ["large", "thumb", "avatar"]:
logger.info("Delete: {}".format(photo[size]))
os.unlink(os.path.join(config.PHOTO_ROOT_PRIVATE, photo[size]))

# Delete it from the sort list.
index["photo-order"][album].remove(key)
del index["map"][key]
del index["albums"][album][key]

# Was this the album cover?
if index["covers"][album] == key:
# Try to pick a new one.
if len(index["photo-order"][album]) > 0:
index["covers"][album] = index["photo-order"][album][0]
else:
index["covers"][album] = ""

# If the album is empty now too, delete it as well.
if len(index["albums"][album].keys()) == 0:
del index["albums"][album]
del index["photo-order"][album]
del index["covers"][album]
index["album-order"].remove(album)

write_index(index)


def order_albums(order):
"""Reorder the albums according to the new order list."""
index = get_index()

# Sanity check, make sure all albums are included.
if len(order) != len(index["album-order"]):
logger.warning("Can't reorganize albums because the order lists don't match!")
return None

for album in index["album-order"]:
if album not in order:
logger.warning("Tried reorganizing albums, but {} was missing!".format(album))
return None

index["album-order"] = order
write_index(index)


def order_photos(album, order):
"""Reorder the photos according to the new order list."""
index = get_index()

if not album in index["albums"]:
logger.warning("Album not found: {}".format(album))
return None

# Sanity check, make sure all albums are included.
if len(order) != len(index["photo-order"][album]):
logger.warning("Can't reorganize photos because the order lists don't match!")
return None

for key in index["photo-order"][album]:
if key not in order:
logger.warning("Tried reorganizing photos, but {} was missing!".format(key))
return None

index["photo-order"][album] = order
write_index(index)


def upload_from_pc(request):
"""Upload a photo from the user's filesystem.

This requires the Flask `request` object. Returns a dict with the following
keys:

* success: True || False
* error: if unsuccessful
* photo: if successful
"""

form = request.form
upload = request.files["file"]

# Make a temp filename for it.
filetype = upload.filename.rsplit(".", 1)[1]
tempfile = "{}/rophako-photo-{}.{}".format(config.TEMPDIR, int(time.time()), filetype)
logger.debug("Save incoming photo to: {}".format(tempfile))
upload.save(tempfile)

# All good so far. Process the photo.
return process_photo(form, tempfile)


def upload_from_www(form):
"""Upload a photo from the Internet.

This requires the `form` object, but not necessarily Flask's. It just has to
be a dict with the form keys for the upload.

Returns the same structure as `upload_from_pc()`.
"""

url = form.get("url")
if not url or not allowed_filetype(url):
return dict(success=False, error="Invalid file extension.")

# Make a temp filename for it.
filetype = url.rsplit(".", 1)[1]
tempfile = "{}/rophako-photo-{}.{}".format(config.TEMPDIR, int(time.time()), filetype)
logger.debug("Save incoming photo to: {}".format(tempfile))

# Grab the file.
try:
data = requests.get(url).content
except:
return dict(success=False, error="Failed to get that URL.")

fh = open(tempfile, "wb")
fh.write(data)
fh.close()

# All good so far. Process the photo.
return process_photo(form, tempfile)


def process_photo(form, filename):
"""Formats an incoming photo."""

# Resize the photo to each of the various sizes and collect their names.
sizes = dict()
for size in PHOTO_SCALES.keys():
sizes[size] = resize_photo(filename, size)

# Remove the temp file.
os.unlink(filename)

# What album are the photos going to?
album = form.get("album", "")
new_album = form.get("new-album", None)
if album == "" and new_album:
album = new_album

# Sanitize the name.
album = sanitize_name(album)
if album == "":
logger.warning("Album name didn't pass sanitization! Fall back to default album name.")
album = config.PHOTO_DEFAULT_ALBUM

# Make up a unique public key for this set of photos.
key = random_hash()
while photo_exists(key):
key = random_hash()
logger.debug("Photo set public key: {}".format(key))

# Get the album index to manipulate ordering.
index = get_index()

# Update the photo data.
if not album in index["albums"]:
index["albums"][album] = {}

index["albums"][album][key] = dict(
ip=request.remote_addr,
author=g.info["session"]["uid"],
uploaded=int(time.time()),
caption=form.get("caption", ""),
**sizes
)

# Maintain a photo map to album.
index["map"][key] = album

# Add this pic to the front of the album.
if not album in index["photo-order"]:
index["photo-order"][album] = []
index["photo-order"][album].insert(0, key)

# If this is a new album, add it to the front of the album ordering.
if not album in index["album-order"]:
index["album-order"].insert(0, album)

# Set the album cover for a new album.
if not album in index["covers"] or len(index["covers"][album]) == 0:
index["covers"][album] = key

# Save changes to the index.
write_index(index)

return dict(success=True, photo=key)


def allowed_filetype(filename):
"""Query whether the file extension is allowed."""
return "." in filename and \
filename.rsplit(".", 1)[1].lower() in ['jpeg', 'jpe', 'jpg', 'gif', 'png']


def resize_photo(filename, size, crop=None):
"""Resize a photo from the target filename into the requested size.

Optionally the photo can be cropped with custom parameters.
"""

# Find the file type.
filetype = filename.rsplit(".", 1)[1]
if filetype == "jpeg": filetype = "jpg"

# Open the image.
img = Image.open(filename)

# Make up a unique filename.
outfile = random_name(filetype)
target = os.path.join(config.PHOTO_ROOT_PRIVATE, outfile)
logger.debug("Output file for {} scale: {}".format(size, target))

# Get the image's dimensions.
orig_width, orig_height = img.size
new_width = PHOTO_SCALES[size]
logger.debug("Original photo dimensions: {}x{}".format(orig_width, orig_height))

# For the large version, only scale it, don't crop it.
if size == "large":
# Do we NEED to scale it?
if orig_width <= new_width:
logger.debug("Don't need to scale down the large image!")
img.save(target)
return outfile

# Scale it down.
ratio = float(new_width) / float(orig_width)
new_height = int(float(orig_height) * float(ratio))
logger.debug("New image dimensions: {}x{}".format(new_width, new_height))
img = img.resize((new_width, new_height), Image.ANTIALIAS)
img.save(target)
return outfile

# For all other versions, crop them into a square.
x, y, length = 0, 0, 0

# Use 0,0 and find the shortest dimension for the length.
if orig_width > orig_height:
length = orig_height
else:
length = orig_width

# Did they give us crop coordinates?
if crop is not None:
x = crop["x"]
y = crop["y"]
if crop["length"] > 0:
length = crop["length"]

# Adjust the coords if they're impossible.
if x < 0:
logger.warning("X-Coord is less than 0; fixing!")
x = 0
if y < 0:
logger.warning("Y-Coord is less than 0; fixing!")
y = 0
if x > orig_width:
logger.warning("X-Coord is greater than image width; fixing!")
x = orig_width - length
if x < 0: x = 0
if y > orig_height:
logger.warning("Y-Coord is greater than image height; fixing!")
y = orig_height - length
if y < 0: y = 0

# Make sure the crop box fits.
if x + length > orig_width:
diff = x + length - orig_width
logger.warning("Crop box is outside the right edge of the image by {}px; fixing!".format(diff))
length -= diff
if y + length > orig_height:
diff = y + length - orig_height
logger.warning("Crop box is outside the bottom edge of the image by {}px; fixing!".format(diff))
length -= diff

# Do we need to scale?
if new_width == length:
logger.debug("Image doesn't need to be cropped or scaled!")
img.save(target)
return outfile

# Crop to the requested box.
logger.debug("Cropping the photo")
img = img.crop((x, y, x+length, y+length))

# Scale it to the proper dimensions.
img = img.resize((new_width, new_width), Image.ANTIALIAS)
img.save(target)
return outfile


def get_index():
"""Get the photo album index, or a new empty DB if it doesn't exist."""
if JsonDB.exists("photos/index"):
return JsonDB.get("photos/index")

return {
"albums": {}, # Album data
"map": {}, # Map photo keys to albums
"covers": {}, # Album cover photos
"photo-order": {}, # Ordering of photos in albums
"album-order": [], # Ordering of albums themselves
}


def write_index(index):
"""Save the index back to the DB."""
return JsonDB.commit("photos/index", index)


def random_name(filetype):
"""Get a random available file name to save a new photo."""
outfile = random_hash() + "." + filetype
while os.path.isfile(os.path.join(config.PHOTO_ROOT_PRIVATE, outfile)):
outfile = random_hash() + "." + filetype
return outfile


def random_hash():
"""Get a short random hash to use as the base name for a photo."""
md5 = hashlib.md5()
md5.update(str(random.randint(0, 1000000)))
return md5.hexdigest()[:8]

+ 13
- 0
rophako/model/user.py View File

@@ -7,6 +7,7 @@ import time

import config
import rophako.jsondb as JsonDB
import rophako.model.photo as Photo
from rophako.log import logger

def create(username, password, name=None, uid=None, role="user"):
@@ -47,6 +48,7 @@ def create(username, password, name=None, uid=None, role="user"):
uid=uid,
username=username,
name=name,
picture="",
role=role,
password=hashedpass,
created=time.time(),
@@ -125,6 +127,17 @@ def get_user(uid=None, username=None):
return JsonDB.get("users/by-id/{}".format(uid))


def get_picture(uid):
"""Get the chosen profile photo for the user."""
data = get_user(uid=uid)
pic = data["picture"]
if len(pic):
photo = Photo.get_photo(pic)
if photo:
return photo["avatar"]
return None


def exists(uid=None, username=None):
"""Query whether a user ID or name exists."""
if uid:


+ 7
- 3
rophako/modules/blog.py View File

@@ -9,7 +9,7 @@ import calendar

import rophako.model.user as User
import rophako.model.blog as Blog
from rophako.utils import template, pretty_time, admin_required
from rophako.utils import template, pretty_time, login_required
from rophako.log import logger
from config import *

@@ -42,6 +42,8 @@ def entry(fid):

# Get the author's information.
post["profile"] = User.get_user(uid=post["author"])
post["photo"] = User.get_picture(uid=post["author"])
post["photo_url"] = PHOTO_ROOT_PUBLIC

# Pretty-print the time.
post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
@@ -60,7 +62,7 @@ def dummy():


@mod.route("/update", methods=["GET", "POST"])
@admin_required
@login_required
def update():
"""Post/edit a blog entry."""

@@ -192,7 +194,7 @@ def update():


@mod.route("/delete", methods=["GET", "POST"])
@admin_required
@login_required
def delete():
"""Delete a blog post."""
post_id = request.args.get("id")
@@ -286,6 +288,8 @@ def partial_index():

# Get the author's information.
post["profile"] = User.get_user(uid=post["author"])
post["photo"] = User.get_picture(uid=post["author"])
post["photo_url"] = PHOTO_ROOT_PUBLIC

post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])



+ 246
- 0
rophako/modules/photo.py View File

@@ -0,0 +1,246 @@
# -*- coding: utf-8 -*-

"""Endpoints for the photo albums."""

from flask import Blueprint, g, request, redirect, url_for, session, flash

import rophako.model.user as User
import rophako.model.photo as Photo
from rophako.utils import template, pretty_time, login_required
from rophako.log import logger
from config import *

mod = Blueprint("photo", __name__, url_prefix="/photos")

@mod.route("/")
def index():
return redirect(url_for(".albums"))


@mod.route("/albums")
def albums():
"""View the index of the photo albums."""
albums = Photo.list_albums()

# If there's only one album, jump directly to that one.
if len(albums) == 1:
return redirect(url_for(".album_index", name=albums[0]["name"]))

g.info["albums"] = albums
return template("photos/albums.html")


@mod.route("/album/<name>")
def album_index(name):
"""View the photos inside an album."""
photos = Photo.list_photos(name)
if photos is None:
flash("That album doesn't exist.")
return redirect(url_for(".albums"))

g.info["album"] = name
g.info["photos"] = photos
return template("photos/album.html")


@mod.route("/view/<key>")
def view_photo(key):
"""View a specific photo."""
photo = Photo.get_photo(key)
if photo is None:
flash("That photo wasn't found!")
return redirect(url_for(".albums"))

from pprint import pprint
pprint(photo)

# Get the author info.
author = User.get_user(uid=photo["author"])
if author:
g.info["author"] = author

g.info["photo"] = photo
g.info["photo"]["key"] = key
g.info["photo"]["pretty_time"] = pretty_time(PHOTO_TIME_FORMAT, photo["uploaded"])
return template("photos/view.html")


@mod.route("/upload", methods=["GET", "POST"])
@login_required
def upload():
"""Upload a photo."""

if request.method == "POST":
# We're posting the upload. What source is the pic from?
result = None
location = request.form.get("location")
if location == "pc":
# An upload from the PC.
result = Photo.upload_from_pc(request)
elif location == "www":
# An upload from the Internet.
result = Photo.upload_from_www(request.form)
else:
flash("Stop messing around.")
return redirect(url_for(".upload"))

# How'd it go?
if result["success"] is not True:
flash("The upload has failed: {}".format(result["error"]))
return redirect(url_for(".upload"))
return redirect(url_for(".crop", photo=result["photo"]))

# Get the list of available albums.
g.info["album_list"] = [
"My Photos", # the default
]
g.info["selected"] = PHOTO_DEFAULT_ALBUM
albums = Photo.list_albums()
if len(albums):
g.info["album_list"] = [ album["name"] for album in albums ]
g.info["selected"] = albums[0]

return template("photos/upload.html")


@mod.route("/crop/<photo>", methods=["GET", "POST"])
@login_required
def crop(photo):
pic = Photo.get_photo(photo)
if not pic:
flash("The photo you want to crop wasn't found!")
return redirect(url_for(".albums"))

# Saving?
if request.method == "POST":
try:
x = int(request.form.get("x", 0))
y = int(request.form.get("y", 0))
length = int(request.form.get("length", 0))
except:
flash("Error with form inputs.")
return redirect(url_for(".crop", photo=photo))

# Re-crop the photo!
Photo.crop_photo(photo, x, y, length)
flash("The photo has been cropped!")
return redirect(url_for(".albums")) # TODO go to photo

# Get the photo's true size.
true_width, true_height = Photo.get_image_dimensions(pic)
g.info["true_width"] = true_width
g.info["true_height"] = true_height
g.info["photo"] = photo
g.info["preview"] = pic["large"]
return template("photos/crop.html")


@mod.route("/set_cover/<album>/<key>")
@login_required
def set_cover(album, key):
"""Set the pic as the album cover."""
pic = Photo.get_photo(key)
if not pic:
flash("The photo you want to crop wasn't found!")
return redirect(url_for(".albums"))

Photo.set_album_cover(album, key)
flash("Album cover has been set.")
return redirect(url_for(".albums"))


@mod.route("/set_profile/<key>")
@login_required
def set_profile(key):
"""Set the pic as your profile picture."""
pic = Photo.get_photo(key)
if not pic:
flash("The photo wasn't found!")
return redirect(url_for(".albums"))

uid = g.info["session"]["uid"]
User.update_user(uid, dict(picture=key))
flash("Your profile picture has been updated.")
return redirect(url_for(".view_photo", key=key))


@mod.route("/edit/<key>", methods=["GET", "POST"])
@login_required
def edit(key):
"""Edit a photo."""
pic = Photo.get_photo(key)
if not pic:
flash("The photo wasn't found!")
return redirect(url_for(".albums"))

if request.method == "POST":
caption = request.form.get("caption", "")
Photo.edit_photo(key, dict(caption=caption))
flash("The photo has been updated.")
return redirect(url_for(".view_photo", key=key))

g.info["key"] = key
g.info["photo"] = pic

return template("photos/edit.html")


@mod.route("/delete/<key>", methods=["GET", "POST"])
@login_required
def delete(key):
"""Delete a photo."""
pic = Photo.get_photo(key)
if not pic:
flash("The photo wasn't found!")
return redirect(url_for(".albums"))

if request.method == "POST":
# Do it.
Photo.delete_photo(key)
flash("The photo has been deleted.")
return redirect(url_for(".albums"))

g.info["key"] = key
g.info["photo"] = pic

return template("photos/delete.html")


@mod.route("/arrange_albums", methods=["GET", "POST"])
@login_required
def arrange_albums():
"""Rearrange the photo album order."""
albums = Photo.list_albums()
if len(albums) == 0:
flash("There are no albums yet.")
return redirect(url_for(".albums"))

if request.method == "POST":
order = request.form.get("order", "").split(";")
print order
Photo.order_albums(order)
flash("The albums have been rearranged!")
return redirect(url_for(".albums"))

g.info["albums"] = albums
return template("photos/arrange_albums.html")


@mod.route("/arrange_photos/<album>", methods=["GET", "POST"])
@login_required
def arrange_photos(album):
"""Rearrange the photos in an album."""
photos = Photo.list_photos(album)
if photos is None:
flash("That album doesn't exist.")
return redirect(url_for(".albums"))

if request.method == "POST":
order = request.form.get("order", "").split(";")
Photo.order_photos(album, order)
flash("The albums have been rearranged!")
return redirect(url_for(".album_index", name=album))

g.info["album"] = album
g.info["photos"] = photos
return template("photos/arrange_photos.html")

+ 8
- 1
rophako/utils.py View File

@@ -75,4 +75,11 @@ def include(endpoint, *args, **kwargs):
def pretty_time(time_format, unix):
"""Pretty-print a time stamp."""
date = datetime.datetime.fromtimestamp(unix)
return date.strftime(time_format)
return date.strftime(time_format)


def sanitize_name(name):
"""Sanitize a name that may be used in the filesystem.

Only allows numbers, letters, and some symbols."""
return re.sub(r'[^A-Za-z0-9 .-_]+', '', name)

+ 3
- 1
rophako/www/blog/entry.inc.html View File

@@ -10,7 +10,9 @@

<div class="blog-author">
<a href="#">{# TODO #}
{% if post["avatar"] %}
{% if post["photo"] %}
<img src="{{ post['photo_url'] }}/{{ post['photo'] }}">
{% elif post["avatar"] %}
<img src="/static/avatars/{{ post['avatar'] }}">
{% else %}
<img src="/static/avatars/default.png">


BIN
rophako/www/css/Jcrop.gif View File

Before After
Width: 8  |  Height: 8  |  Size: 329B

+ 165
- 0
rophako/www/css/jquery.Jcrop.css View File

@@ -0,0 +1,165 @@
/* jquery.Jcrop.css v0.9.12 - MIT License */
/*
The outer-most container in a typical Jcrop instance
If you are having difficulty with formatting related to styles
on a parent element, place any fixes here or in a like selector

You can also style this element if you want to add a border, etc
A better method for styling can be seen below with .jcrop-light
(Add a class to the holder and style elements for that extended class)
*/
.jcrop-holder {
direction: ltr;
text-align: left;
}
/* Selection Border */
.jcrop-vline,
.jcrop-hline {
background: #ffffff url("Jcrop.gif");
font-size: 0;
position: absolute;
}
.jcrop-vline {
height: 100%;
width: 1px !important;
}
.jcrop-vline.right {
right: 0;
}
.jcrop-hline {
height: 1px !important;
width: 100%;
}
.jcrop-hline.bottom {
bottom: 0;
}
/* Invisible click targets */
.jcrop-tracker {
height: 100%;
width: 100%;
/* "turn off" link highlight */
-webkit-tap-highlight-color: transparent;
/* disable callout, image save panel */
-webkit-touch-callout: none;
/* disable cut copy paste */
-webkit-user-select: none;
}
/* Selection Handles */
.jcrop-handle {
background-color: #333333;
border: 1px #eeeeee solid;
width: 7px;
height: 7px;
font-size: 1px;
}
.jcrop-handle.ord-n {
left: 50%;
margin-left: -4px;
margin-top: -4px;
top: 0;
}
.jcrop-handle.ord-s {
bottom: 0;
left: 50%;
margin-bottom: -4px;
margin-left: -4px;
}
.jcrop-handle.ord-e {
margin-right: -4px;
margin-top: -4px;
right: 0;
top: 50%;
}
.jcrop-handle.ord-w {
left: 0;
margin-left: -4px;
margin-top: -4px;
top: 50%;
}
.jcrop-handle.ord-nw {
left: 0;
margin-left: -4px;
margin-top: -4px;
top: 0;
}
.jcrop-handle.ord-ne {
margin-right: -4px;
margin-top: -4px;
right: 0;
top: 0;
}
.jcrop-handle.ord-se {
bottom: 0;
margin-bottom: -4px;
margin-right: -4px;
right: 0;
}
.jcrop-handle.ord-sw {
bottom: 0;
left: 0;
margin-bottom: -4px;
margin-left: -4px;
}
/* Dragbars */
.jcrop-dragbar.ord-n,
.jcrop-dragbar.ord-s {
height: 7px;
width: 100%;
}
.jcrop-dragbar.ord-e,
.jcrop-dragbar.ord-w {
height: 100%;
width: 7px;
}
.jcrop-dragbar.ord-n {
margin-top: -4px;
}
.jcrop-dragbar.ord-s {
bottom: 0;
margin-bottom: -4px;
}
.jcrop-dragbar.ord-e {
margin-right: -4px;
right: 0;
}
.jcrop-dragbar.ord-w {
margin-left: -4px;
}
/* The "jcrop-light" class/extension */
.jcrop-light .jcrop-vline,
.jcrop-light .jcrop-hline {
background: #ffffff;
filter: alpha(opacity=70) !important;
opacity: .70!important;
}
.jcrop-light .jcrop-handle {
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: #000000;
border-color: #ffffff;
border-radius: 3px;
}
/* The "jcrop-dark" class/extension */
.jcrop-dark .jcrop-vline,
.jcrop-dark .jcrop-hline {
background: #000000;
filter: alpha(opacity=70) !important;
opacity: 0.7 !important;
}
.jcrop-dark .jcrop-handle {
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
background-color: #ffffff;
border-color: #000000;
border-radius: 3px;
}
/* Simple macro to turn off the antlines */
.solid-line .jcrop-vline,
.solid-line .jcrop-hline {
background: #ffffff;
}
/* Fix for twitter bootstrap et al. */
.jcrop-holder img,
img.jcrop-preview {
max-width: none;
}

+ 29
- 0
rophako/www/css/jquery.Jcrop.min.css View File

@@ -0,0 +1,29 @@
/* jquery.Jcrop.min.css v0.9.12 (build:20130126) */
.jcrop-holder{direction:ltr;text-align:left;}
.jcrop-vline,.jcrop-hline{background:#FFF url(Jcrop.gif);font-size:0;position:absolute;}
.jcrop-vline{height:100%;width:1px!important;}
.jcrop-vline.right{right:0;}
.jcrop-hline{height:1px!important;width:100%;}
.jcrop-hline.bottom{bottom:0;}
.jcrop-tracker{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;height:100%;width:100%;}
.jcrop-handle{background-color:#333;border:1px #EEE solid;font-size:1px;height:7px;width:7px;}
.jcrop-handle.ord-n{left:50%;margin-left:-4px;margin-top:-4px;top:0;}
.jcrop-handle.ord-s{bottom:0;left:50%;margin-bottom:-4px;margin-left:-4px;}
.jcrop-handle.ord-e{margin-right:-4px;margin-top:-4px;right:0;top:50%;}
.jcrop-handle.ord-w{left:0;margin-left:-4px;margin-top:-4px;top:50%;}
.jcrop-handle.ord-nw{left:0;margin-left:-4px;margin-top:-4px;top:0;}
.jcrop-handle.ord-ne{margin-right:-4px;margin-top:-4px;right:0;top:0;}
.jcrop-handle.ord-se{bottom:0;margin-bottom:-4px;margin-right:-4px;right:0;}
.jcrop-handle.ord-sw{bottom:0;left:0;margin-bottom:-4px;margin-left:-4px;}
.jcrop-dragbar.ord-n,.jcrop-dragbar.ord-s{height:7px;width:100%;}
.jcrop-dragbar.ord-e,.jcrop-dragbar.ord-w{height:100%;width:7px;}
.jcrop-dragbar.ord-n{margin-top:-4px;}
.jcrop-dragbar.ord-s{bottom:0;margin-bottom:-4px;}
.jcrop-dragbar.ord-e{margin-right:-4px;right:0;}
.jcrop-dragbar.ord-w{margin-left:-4px;}
.jcrop-light .jcrop-vline,.jcrop-light .jcrop-hline{background:#FFF;filter:alpha(opacity=70)!important;opacity:.70!important;}
.jcrop-light .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#000;border-color:#FFF;border-radius:3px;}
.jcrop-dark .jcrop-vline,.jcrop-dark .jcrop-hline{background:#000;filter:alpha(opacity=70)!important;opacity:.7!important;}
.jcrop-dark .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#FFF;border-color:#000;border-radius:3px;}
.solid-line .jcrop-vline,.solid-line .jcrop-hline{background:#FFF;}
.jcrop-holder img,img.jcrop-preview{max-width:none;}

BIN
rophako/www/css/ui-lightness/images/animated-overlay.gif View File

Before After
Width: 40  |  Height: 40  |  Size: 1.7KB

BIN
rophako/www/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png View File

Before After
Width: 40  |  Height: 40  |  Size: 418B

BIN
rophako/www/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png View File

Before After
Width: 40  |  Height: 40  |  Size: 312B

BIN
rophako/www/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png View File

Before After
Width: 40  |  Height: 100  |  Size: 205B

BIN
rophako/www/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png View File

Before After
Width: 1  |  Height: 400  |  Size: 262B

BIN
rophako/www/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png View File

Before After
Width: 1  |  Height: 400  |  Size: 348B

BIN
rophako/www/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png View File

Before After
Width: 1  |  Height: 400  |  Size: 207B

BIN
rophako/www/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png View File

Before After
Width: 500  |  Height: 100  |  Size: 5.7KB

BIN
rophako/www/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png View File

Before After
Width: 1  |  Height: 100  |  Size: 278B

BIN
rophako/www/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png View File

Before After
Width: 1  |  Height: 100  |  Size: 328B

BIN
rophako/www/css/ui-lightness/images/ui-icons_222222_256x240.png View File

Before After
Width: 256  |  Height: 240  |  Size: 6.8KB

BIN
rophako/www/css/ui-lightness/images/ui-icons_228ef1_256x240.png View File

Before After
Width: 256  |  Height: 240  |  Size: 4.4KB

BIN
rophako/www/css/ui-lightness/images/ui-icons_ef8c08_256x240.png View File

Before After
Width: 256  |  Height: 240  |  Size: 4.4KB

BIN
rophako/www/css/ui-lightness/images/ui-icons_ffd27a_256x240.png View File

Before After
Width: 256  |  Height: 240  |  Size: 4.4KB

BIN
rophako/www/css/ui-lightness/images/ui-icons_ffffff_256x240.png View File

Before After
Width: 256  |  Height: 240  |  Size: 6.2KB

+ 485
- 0
rophako/www/css/ui-lightness/jquery-ui-1.10.4.custom.css View File

@@ -0,0 +1,485 @@
/*! jQuery UI - v1.10.4 - 2014-04-05
* http://jqueryui.com
* Includes: jquery.ui.core.css, jquery.ui.theme.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */

/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
display: none;
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.ui-helper-reset {
margin: 0;
padding: 0;
border: 0;
outline: 0;
line-height: 1.3;
text-decoration: none;
font-size: 100%;
list-style: none;
}
.ui-helper-clearfix:before,
.ui-helper-clearfix:after {
content: "";
display: table;
border-collapse: collapse;
}
.ui-helper-clearfix:after {
clear: both;
}
.ui-helper-clearfix {
min-height: 0; /* support: IE7 */
}
.ui-helper-zfix {
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
opacity: 0;
filter:Alpha(Opacity=0);
}

.ui-front {
z-index: 100;
}


/* Interaction Cues
----------------------------------*/
.ui-state-disabled {
cursor: default !important;
}


/* Icons
----------------------------------*/

/* states and images */
.ui-icon {
display: block;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
}


/* Misc visuals
----------------------------------*/

/* Overlays */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

/* Component containers
----------------------------------*/
.ui-widget {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1.1em;
}
.ui-widget .ui-widget {
font-size: 1em;
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1em;
}
.ui-widget-content {
border: 1px solid #dddddd;
background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x;
color: #333333;
}
.ui-widget-content a {
color: #333333;
}
.ui-widget-header {
border: 1px solid #e78f08;
background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x;
color: #ffffff;
font-weight: bold;
}
.ui-widget-header a {
color: #ffffff;
}

/* Interaction states
----------------------------------*/
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default {
border: 1px solid #cccccc;
background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;
font-weight: bold;
color: #1c94c4;
}
.ui-state-default a,
.ui-state-default a:link,
.ui-state-default a:visited {
color: #1c94c4;
text-decoration: none;
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus {
border: 1px solid #fbcb09;
background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x;
font-weight: bold;
color: #c77405;
}
.ui-state-hover a,
.ui-state-hover a:hover,
.ui-state-hover a:link,
.ui-state-hover a:visited,
.ui-state-focus a,
.ui-state-focus a:hover,
.ui-state-focus a:link,
.ui-state-focus a:visited {
color: #c77405;
text-decoration: none;
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active {
border: 1px solid #fbd850;
background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;
font-weight: bold;
color: #eb8f00;
}
.ui-state-active a,
.ui-state-active a:link,
.ui-state-active a:visited {
color: #eb8f00;
text-decoration: none;
}

/* Interaction Cues
----------------------------------*/
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-widget-header .ui-state-highlight {
border: 1px solid #fed22f;
background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x;
color: #363636;
}
.ui-state-highlight a,
.ui-widget-content .ui-state-highlight a,
.ui-widget-header .ui-state-highlight a {
color: #363636;
}
.ui-state-error,
.ui-widget-content .ui-state-error,
.ui-widget-header .ui-state-error {
border: 1px solid #cd0a0a;
background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat;
color: #ffffff;
}
.ui-state-error a,
.ui-widget-content .ui-state-error a,
.ui-widget-header .ui-state-error a {
color: #ffffff;
}
.ui-state-error-text,
.ui-widget-content .ui-state-error-text,
.ui-widget-header .ui-state-error-text {
color: #ffffff;
}
.ui-priority-primary,
.ui-widget-content .ui-priority-primary,
.ui-widget-header .ui-priority-primary {
font-weight: bold;
}
.ui-priority-secondary,
.ui-widget-content .ui-priority-secondary,
.ui-widget-header .ui-priority-secondary {
opacity: .7;
filter:Alpha(Opacity=70);
font-weight: normal;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
opacity: .35;
filter:Alpha(Opacity=35);
background-image: none;
}
.ui-state-disabled .ui-icon {
filter:Alpha(Opacity=35); /* For IE8 - See #6059 */
}

/* Icons
----------------------------------*/

/* states and images */
.ui-icon {
width: 16px;
height: 16px;
}
.ui-icon,
.ui-widget-content .ui-icon {
background-image: url(images/ui-icons_222222_256x240.png);
}
.ui-widget-header .ui-icon {
background-image: url(images/ui-icons_ffffff_256x240.png);
}
.ui-state-default .ui-icon {
background-image: url(images/ui-icons_ef8c08_256x240.png);
}
.ui-state-hover .ui-icon,
.ui-state-focus .ui-icon {
background-image: url(images/ui-icons_ef8c08_256x240.png);
}
.ui-state-active .ui-icon {
background-image: url(images/ui-icons_ef8c08_256x240.png);
}
.ui-state-highlight .ui-icon {
background-image: url(images/ui-icons_228ef1_256x240.png);
}
.ui-state-error .ui-icon,
.ui-state-error-text .ui-icon {
background-image: url(images/ui-icons_ffd27a_256x240.png);
}

/* positioning */
.ui-icon-blank { background-position: 16px 16px; }
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-on { background-position: -96px -144px; }
.ui-icon-radio-off { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }


/* Misc visuals
----------------------------------*/

/* Corner radius */
.ui-corner-all,
.ui-corner-top,
.ui-corner-left,
.ui-corner-tl {
border-top-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-top,
.ui-corner-right,
.ui-corner-tr {
border-top-right-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-left,
.ui-corner-bl {
border-bottom-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-right,
.ui-corner-br {
border-bottom-right-radius: 4px;
}

/* Overlays */
.ui-widget-overlay {
background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat;
opacity: .5;
filter: Alpha(Opacity=50);
}
.ui-widget-shadow {
margin: -5px 0 0 -5px;
padding: 5px;
background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x;
opacity: .2;
filter: Alpha(Opacity=20);
border-radius: 5px;
}

+ 7
- 0
rophako/www/css/ui-lightness/jquery-ui-1.10.4.custom.min.css
File diff suppressed because it is too large
View File


+ 2747
- 0
rophako/www/js/jquery-ui-1.10.4.custom.js
File diff suppressed because it is too large
View File


+ 6
- 0
rophako/www/js/jquery-ui-1.10.4.custom.min.js
File diff suppressed because it is too large
View File


+ 1694
- 0
rophako/www/js/jquery.Jcrop.js
File diff suppressed because it is too large
View File


+ 22
- 0
rophako/www/js/jquery.Jcrop.min.js View File

@@ -0,0 +1,22 @@
/**
* jquery.Jcrop.min.js v0.9.12 (build:20130202)
* jQuery Image Cropping Plugin - released under MIT License
* Copyright (c) 2008-2013 Tapmodo Interactive LLC
* https://github.com/tapmodo/Jcrop
*/
(function(a){a.Jcrop=function(b,c){function i(a){return Math.round(a)+"px"}function j(a){return d.baseClass+"-"+a}function k(){return a.fx.step.hasOwnProperty("backgroundColor")}function l(b){var c=a(b).offset();return[c.left,c.top]}function m(a){return[a.pageX-e[0],a.pageY-e[1]]}function n(b){typeof b!="object"&&(b={}),d=a.extend(d,b),a.each(["onChange","onSelect","onRelease","onDblClick"],function(a,b){typeof d[b]!="function"&&(d[b]=function(){})})}function o(a,b,c){e=l(D),bc.setCursor(a==="move"?a:a+"-resize");if(a==="move")return bc.activateHandlers(q(b),v,c);var d=_.getFixed(),f=r(a),g=_.getCorner(r(f));_.setPressed(_.getCorner(f)),_.setCurrent(g),bc.activateHandlers(p(a,d),v,c)}function p(a,b){return function(c){if(!d.aspectRatio)switch(a){case"e":c[1]=b.y2;break;case"w":c[1]=b.y2;break;case"n":c[0]=b.x2;break;case"s":c[0]=b.x2}else switch(a){case"e":c[1]=b.y+1;break;case"w":c[1]=b.y+1;break;case"n":c[0]=b.x+1;break;case"s":c[0]=b.x+1}_.setCurrent(c),bb.update()}}function q(a){var b=a;return bd.watchKeys
(),function(a){_.moveOffset([a[0]-b[0],a[1]-b[1]]),b=a,bb.update()}}function r(a){switch(a){case"n":return"sw";case"s":return"nw";case"e":return"nw";case"w":return"ne";case"ne":return"sw";case"nw":return"se";case"se":return"nw";case"sw":return"ne"}}function s(a){return function(b){return d.disabled?!1:a==="move"&&!d.allowMove?!1:(e=l(D),W=!0,o(a,m(b)),b.stopPropagation(),b.preventDefault(),!1)}}function t(a,b,c){var d=a.width(),e=a.height();d>b&&b>0&&(d=b,e=b/a.width()*a.height()),e>c&&c>0&&(e=c,d=c/a.height()*a.width()),T=a.width()/d,U=a.height()/e,a.width(d).height(e)}function u(a){return{x:a.x*T,y:a.y*U,x2:a.x2*T,y2:a.y2*U,w:a.w*T,h:a.h*U}}function v(a){var b=_.getFixed();b.w>d.minSelect[0]&&b.h>d.minSelect[1]?(bb.enableHandles(),bb.done()):bb.release(),bc.setCursor(d.allowSelect?"crosshair":"default")}function w(a){if(d.disabled)return!1;if(!d.allowSelect)return!1;W=!0,e=l(D),bb.disableHandles(),bc.setCursor("crosshair");var b=m(a);return _.setPressed(b),bb.update(),bc.activateHandlers(x,v,a.type.substring
(0,5)==="touch"),bd.watchKeys(),a.stopPropagation(),a.preventDefault(),!1}function x(a){_.setCurrent(a),bb.update()}function y(){var b=a("<div></div>").addClass(j("tracker"));return g&&b.css({opacity:0,backgroundColor:"white"}),b}function be(a){G.removeClass().addClass(j("holder")).addClass(a)}function bf(a,b){function t(){window.setTimeout(u,l)}var c=a[0]/T,e=a[1]/U,f=a[2]/T,g=a[3]/U;if(X)return;var h=_.flipCoords(c,e,f,g),i=_.getFixed(),j=[i.x,i.y,i.x2,i.y2],k=j,l=d.animationDelay,m=h[0]-j[0],n=h[1]-j[1],o=h[2]-j[2],p=h[3]-j[3],q=0,r=d.swingSpeed;c=k[0],e=k[1],f=k[2],g=k[3],bb.animMode(!0);var s,u=function(){return function(){q+=(100-q)/r,k[0]=Math.round(c+q/100*m),k[1]=Math.round(e+q/100*n),k[2]=Math.round(f+q/100*o),k[3]=Math.round(g+q/100*p),q>=99.8&&(q=100),q<100?(bh(k),t()):(bb.done(),bb.animMode(!1),typeof b=="function"&&b.call(bs))}}();t()}function bg(a){bh([a[0]/T,a[1]/U,a[2]/T,a[3]/U]),d.onSelect.call(bs,u(_.getFixed())),bb.enableHandles()}function bh(a){_.setPressed([a[0],a[1]]),_.setCurrent([a[2],
a[3]]),bb.update()}function bi(){return u(_.getFixed())}function bj(){return _.getFixed()}function bk(a){n(a),br()}function bl(){d.disabled=!0,bb.disableHandles(),bb.setCursor("default"),bc.setCursor("default")}function bm(){d.disabled=!1,br()}function bn(){bb.done(),bc.activateHandlers(null,null)}function bo(){G.remove(),A.show(),A.css("visibility","visible"),a(b).removeData("Jcrop")}function bp(a,b){bb.release(),bl();var c=new Image;c.onload=function(){var e=c.width,f=c.height,g=d.boxWidth,h=d.boxHeight;D.width(e).height(f),D.attr("src",a),H.attr("src",a),t(D,g,h),E=D.width(),F=D.height(),H.width(E).height(F),M.width(E+L*2).height(F+L*2),G.width(E).height(F),ba.resize(E,F),bm(),typeof b=="function"&&b.call(bs)},c.src=a}function bq(a,b,c){var e=b||d.bgColor;d.bgFade&&k()&&d.fadeTime&&!c?a.animate({backgroundColor:e},{queue:!1,duration:d.fadeTime}):a.css("backgroundColor",e)}function br(a){d.allowResize?a?bb.enableOnly():bb.enableHandles():bb.disableHandles(),bc.setCursor(d.allowSelect?"crosshair":"default"),bb
.setCursor(d.allowMove?"move":"default"),d.hasOwnProperty("trueSize")&&(T=d.trueSize[0]/E,U=d.trueSize[1]/F),d.hasOwnProperty("setSelect")&&(bg(d.setSelect),bb.done(),delete d.setSelect),ba.refresh(),d.bgColor!=N&&(bq(d.shade?ba.getShades():G,d.shade?d.shadeColor||d.bgColor:d.bgColor),N=d.bgColor),O!=d.bgOpacity&&(O=d.bgOpacity,d.shade?ba.refresh():bb.setBgOpacity(O)),P=d.maxSize[0]||0,Q=d.maxSize[1]||0,R=d.minSize[0]||0,S=d.minSize[1]||0,d.hasOwnProperty("outerImage")&&(D.attr("src",d.outerImage),delete d.outerImage),bb.refresh()}var d=a.extend({},a.Jcrop.defaults),e,f=navigator.userAgent.toLowerCase(),g=/msie/.test(f),h=/msie [1-6]\./.test(f);typeof b!="object"&&(b=a(b)[0]),typeof c!="object"&&(c={}),n(c);var z={border:"none",visibility:"visible",margin:0,padding:0,position:"absolute",top:0,left:0},A=a(b),B=!0;if(b.tagName=="IMG"){if(A[0].width!=0&&A[0].height!=0)A.width(A[0].width),A.height(A[0].height);else{var C=new Image;C.src=A[0].src,A.width(C.width),A.height(C.height)}var D=A.clone().removeAttr("id").
css(z).show();D.width(A.width()),D.height(A.height()),A.after(D).hide()}else D=A.css(z).show(),B=!1,d.shade===null&&(d.shade=!0);t(D,d.boxWidth,d.boxHeight);var E=D.width(),F=D.height(),G=a("<div />").width(E).height(F).addClass(j("holder")).css({position:"relative",backgroundColor:d.bgColor}).insertAfter(A).append(D);d.addClass&&G.addClass(d.addClass);var H=a("<div />"),I=a("<div />").width("100%").height("100%").css({zIndex:310,position:"absolute",overflow:"hidden"}),J=a("<div />").width("100%").height("100%").css("zIndex",320),K=a("<div />").css({position:"absolute",zIndex:600}).dblclick(function(){var a=_.getFixed();d.onDblClick.call(bs,a)}).insertBefore(D).append(I,J);B&&(H=a("<img />").attr("src",D.attr("src")).css(z).width(E).height(F),I.append(H)),h&&K.css({overflowY:"hidden"});var L=d.boundary,M=y().width(E+L*2).height(F+L*2).css({position:"absolute",top:i(-L),left:i(-L),zIndex:290}).mousedown(w),N=d.bgColor,O=d.bgOpacity,P,Q,R,S,T,U,V=!0,W,X,Y;e=l(D);var Z=function(){function a(){var a={},b=["touchstart"
,"touchmove","touchend"],c=document.createElement("div"),d;try{for(d=0;d<b.length;d++){var e=b[d];e="on"+e;var f=e in c;f||(c.setAttribute(e,"return;"),f=typeof c[e]=="function"),a[b[d]]=f}return a.touchstart&&a.touchend&&a.touchmove}catch(g){return!1}}function b(){return d.touchSupport===!0||d.touchSupport===!1?d.touchSupport:a()}return{createDragger:function(a){return function(b){return d.disabled?!1:a==="move"&&!d.allowMove?!1:(e=l(D),W=!0,o(a,m(Z.cfilter(b)),!0),b.stopPropagation(),b.preventDefault(),!1)}},newSelection:function(a){return w(Z.cfilter(a))},cfilter:function(a){return a.pageX=a.originalEvent.changedTouches[0].pageX,a.pageY=a.originalEvent.changedTouches[0].pageY,a},isSupported:a,support:b()}}(),_=function(){function h(d){d=n(d),c=a=d[0],e=b=d[1]}function i(a){a=n(a),f=a[0]-c,g=a[1]-e,c=a[0],e=a[1]}function j(){return[f,g]}function k(d){var f=d[0],g=d[1];0>a+f&&(f-=f+a),0>b+g&&(g-=g+b),F<e+g&&(g+=F-(e+g)),E<c+f&&(f+=E-(c+f)),a+=f,c+=f,b+=g,e+=g}function l(a){var b=m();switch(a){case"ne":return[
b.x2,b.y];case"nw":return[b.x,b.y];case"se":return[b.x2,b.y2];case"sw":return[b.x,b.y2]}}function m(){if(!d.aspectRatio)return p();var f=d.aspectRatio,g=d.minSize[0]/T,h=d.maxSize[0]/T,i=d.maxSize[1]/U,j=c-a,k=e-b,l=Math.abs(j),m=Math.abs(k),n=l/m,r,s,t,u;return h===0&&(h=E*10),i===0&&(i=F*10),n<f?(s=e,t=m*f,r=j<0?a-t:t+a,r<0?(r=0,u=Math.abs((r-a)/f),s=k<0?b-u:u+b):r>E&&(r=E,u=Math.abs((r-a)/f),s=k<0?b-u:u+b)):(r=c,u=l/f,s=k<0?b-u:b+u,s<0?(s=0,t=Math.abs((s-b)*f),r=j<0?a-t:t+a):s>F&&(s=F,t=Math.abs(s-b)*f,r=j<0?a-t:t+a)),r>a?(r-a<g?r=a+g:r-a>h&&(r=a+h),s>b?s=b+(r-a)/f:s=b-(r-a)/f):r<a&&(a-r<g?r=a-g:a-r>h&&(r=a-h),s>b?s=b+(a-r)/f:s=b-(a-r)/f),r<0?(a-=r,r=0):r>E&&(a-=r-E,r=E),s<0?(b-=s,s=0):s>F&&(b-=s-F,s=F),q(o(a,b,r,s))}function n(a){return a[0]<0&&(a[0]=0),a[1]<0&&(a[1]=0),a[0]>E&&(a[0]=E),a[1]>F&&(a[1]=F),[Math.round(a[0]),Math.round(a[1])]}function o(a,b,c,d){var e=a,f=c,g=b,h=d;return c<a&&(e=c,f=a),d<b&&(g=d,h=b),[e,g,f,h]}function p(){var d=c-a,f=e-b,g;return P&&Math.abs(d)>P&&(c=d>0?a+P:a-P),Q&&Math.abs
(f)>Q&&(e=f>0?b+Q:b-Q),S/U&&Math.abs(f)<S/U&&(e=f>0?b+S/U:b-S/U),R/T&&Math.abs(d)<R/T&&(c=d>0?a+R/T:a-R/T),a<0&&(c-=a,a-=a),b<0&&(e-=b,b-=b),c<0&&(a-=c,c-=c),e<0&&(b-=e,e-=e),c>E&&(g=c-E,a-=g,c-=g),e>F&&(g=e-F,b-=g,e-=g),a>E&&(g=a-F,e-=g,b-=g),b>F&&(g=b-F,e-=g,b-=g),q(o(a,b,c,e))}function q(a){return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]}}var a=0,b=0,c=0,e=0,f,g;return{flipCoords:o,setPressed:h,setCurrent:i,getOffset:j,moveOffset:k,getCorner:l,getFixed:m}}(),ba=function(){function f(a,b){e.left.css({height:i(b)}),e.right.css({height:i(b)})}function g(){return h(_.getFixed())}function h(a){e.top.css({left:i(a.x),width:i(a.w),height:i(a.y)}),e.bottom.css({top:i(a.y2),left:i(a.x),width:i(a.w),height:i(F-a.y2)}),e.right.css({left:i(a.x2),width:i(E-a.x2)}),e.left.css({width:i(a.x)})}function j(){return a("<div />").css({position:"absolute",backgroundColor:d.shadeColor||d.bgColor}).appendTo(c)}function k(){b||(b=!0,c.insertBefore(D),g(),bb.setBgOpacity(1,0,1),H.hide(),l(d.shadeColor||d.bgColor,1),bb.
isAwake()?n(d.bgOpacity,1):n(1,1))}function l(a,b){bq(p(),a,b)}function m(){b&&(c.remove(),H.show(),b=!1,bb.isAwake()?bb.setBgOpacity(d.bgOpacity,1,1):(bb.setBgOpacity(1,1,1),bb.disableHandles()),bq(G,0,1))}function n(a,e){b&&(d.bgFade&&!e?c.animate({opacity:1-a},{queue:!1,duration:d.fadeTime}):c.css({opacity:1-a}))}function o(){d.shade?k():m(),bb.isAwake()&&n(d.bgOpacity)}function p(){return c.children()}var b=!1,c=a("<div />").css({position:"absolute",zIndex:240,opacity:0}),e={top:j(),left:j().height(F),right:j().height(F),bottom:j()};return{update:g,updateRaw:h,getShades:p,setBgColor:l,enable:k,disable:m,resize:f,refresh:o,opacity:n}}(),bb=function(){function k(b){var c=a("<div />").css({position:"absolute",opacity:d.borderOpacity}).addClass(j(b));return I.append(c),c}function l(b,c){var d=a("<div />").mousedown(s(b)).css({cursor:b+"-resize",position:"absolute",zIndex:c}).addClass("ord-"+b);return Z.support&&d.bind("touchstart.jcrop",Z.createDragger(b)),J.append(d),d}function m(a){var b=d.handleSize,e=l(a,c++
).css({opacity:d.handleOpacity}).addClass(j("handle"));return b&&e.width(b).height(b),e}function n(a){return l(a,c++).addClass("jcrop-dragbar")}function o(a){var b;for(b=0;b<a.length;b++)g[a[b]]=n(a[b])}function p(a){var b,c;for(c=0;c<a.length;c++){switch(a[c]){case"n":b="hline";break;case"s":b="hline bottom";break;case"e":b="vline right";break;case"w":b="vline"}e[a[c]]=k(b)}}function q(a){var b;for(b=0;b<a.length;b++)f[a[b]]=m(a[b])}function r(a,b){d.shade||H.css({top:i(-b),left:i(-a)}),K.css({top:i(b),left:i(a)})}function t(a,b){K.width(Math.round(a)).height(Math.round(b))}function v(){var a=_.getFixed();_.setPressed([a.x,a.y]),_.setCurrent([a.x2,a.y2]),w()}function w(a){if(b)return x(a)}function x(a){var c=_.getFixed();t(c.w,c.h),r(c.x,c.y),d.shade&&ba.updateRaw(c),b||A(),a?d.onSelect.call(bs,u(c)):d.onChange.call(bs,u(c))}function z(a,c,e){if(!b&&!c)return;d.bgFade&&!e?D.animate({opacity:a},{queue:!1,duration:d.fadeTime}):D.css("opacity",a)}function A(){K.show(),d.shade?ba.opacity(O):z(O,!0),b=!0}function B
(){F(),K.hide(),d.shade?ba.opacity(1):z(1),b=!1,d.onRelease.call(bs)}function C(){h&&J.show()}function E(){h=!0;if(d.allowResize)return J.show(),!0}function F(){h=!1,J.hide()}function G(a){a?(X=!0,F()):(X=!1,E())}function L(){G(!1),v()}var b,c=370,e={},f={},g={},h=!1;d.dragEdges&&a.isArray(d.createDragbars)&&o(d.createDragbars),a.isArray(d.createHandles)&&q(d.createHandles),d.drawBorders&&a.isArray(d.createBorders)&&p(d.createBorders),a(document).bind("touchstart.jcrop-ios",function(b){a(b.currentTarget).hasClass("jcrop-tracker")&&b.stopPropagation()});var M=y().mousedown(s("move")).css({cursor:"move",position:"absolute",zIndex:360});return Z.support&&M.bind("touchstart.jcrop",Z.createDragger("move")),I.append(M),F(),{updateVisible:w,update:x,release:B,refresh:v,isAwake:function(){return b},setCursor:function(a){M.css("cursor",a)},enableHandles:E,enableOnly:function(){h=!0},showHandles:C,disableHandles:F,animMode:G,setBgOpacity:z,done:L}}(),bc=function(){function f(b){M.css({zIndex:450}),b?a(document).bind("touchmove.jcrop"
,k).bind("touchend.jcrop",l):e&&a(document).bind("mousemove.jcrop",h).bind("mouseup.jcrop",i)}function g(){M.css({zIndex:290}),a(document).unbind(".jcrop")}function h(a){return b(m(a)),!1}function i(a){return a.preventDefault(),a.stopPropagation(),W&&(W=!1,c(m(a)),bb.isAwake()&&d.onSelect.call(bs,u(_.getFixed())),g(),b=function(){},c=function(){}),!1}function j(a,d,e){return W=!0,b=a,c=d,f(e),!1}function k(a){return b(m(Z.cfilter(a))),!1}function l(a){return i(Z.cfilter(a))}function n(a){M.css("cursor",a)}var b=function(){},c=function(){},e=d.trackDocument;return e||M.mousemove(h).mouseup(i).mouseout(i),D.before(M),{activateHandlers:j,setCursor:n}}(),bd=function(){function e(){d.keySupport&&(b.show(),b.focus())}function f(a){b.hide()}function g(a,b,c){d.allowMove&&(_.moveOffset([b,c]),bb.updateVisible(!0)),a.preventDefault(),a.stopPropagation()}function i(a){if(a.ctrlKey||a.metaKey)return!0;Y=a.shiftKey?!0:!1;var b=Y?10:1;switch(a.keyCode){case 37:g(a,-b,0);break;case 39:g(a,b,0);break;case 38:g(a,0,-b);break;
case 40:g(a,0,b);break;case 27:d.allowSelect&&bb.release();break;case 9:return!0}return!1}var b=a('<input type="radio" />').css({position:"fixed",left:"-120px",width:"12px"}).addClass("jcrop-keymgr"),c=a("<div />").css({position:"absolute",overflow:"hidden"}).append(b);return d.keySupport&&(b.keydown(i).blur(f),h||!d.fixedSupport?(b.css({position:"absolute",left:"-20px"}),c.append(b).insertBefore(D)):b.insertBefore(D)),{watchKeys:e}}();Z.support&&M.bind("touchstart.jcrop",Z.newSelection),J.hide(),br(!0);var bs={setImage:bp,animateTo:bf,setSelect:bg,setOptions:bk,tellSelect:bi,tellScaled:bj,setClass:be,disable:bl,enable:bm,cancel:bn,release:bb.release,destroy:bo,focus:bd.watchKeys,getBounds:function(){return[E*T,F*U]},getWidgetSize:function(){return[E,F]},getScaleFactor:function(){return[T,U]},getOptions:function(){return d},ui:{holder:G,selection:K}};return g&&G.bind("selectstart",function(){return!1}),A.data("Jcrop",bs),bs},a.fn.Jcrop=function(b,c){var d;return this.each(function(){if(a(this).data("Jcrop")){if(
b==="api")return a(this).data("Jcrop");a(this).data("Jcrop").setOptions(b)}else this.tagName=="IMG"?a.Jcrop.Loader(this,function(){a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d)}):(a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d))}),this},a.Jcrop.Loader=function(b,c,d){function g(){f.complete?(e.unbind(".jcloader"),a.isFunction(c)&&c.call(f)):window.setTimeout(g,50)}var e=a(b),f=e[0];e.bind("load.jcloader",g).bind("error.jcloader",function(b){e.unbind(".jcloader"),a.isFunction(d)&&d.call(f)}),f.complete&&a.isFunction(c)&&(e.unbind(".jcloader"),c.call(f))},a.Jcrop.defaults={allowSelect:!0,allowMove:!0,allowResize:!0,trackDocument:!0,baseClass:"jcrop",addClass:null,bgColor:"black",bgOpacity:.6,bgFade:!1,borderOpacity:.4,handleOpacity:.5,handleSize:null,aspectRatio:0,keySupport:!0,createHandles:["n","s","e","w","nw","ne","se","sw"],createDragbars:["n","s","e","w"],createBorders:["n","s","e","w"],drawBorders:!0,dragEdges
:!0,fixedSupport:!0,touchSupport:null,shade:null,boxWidth:0,boxHeight:0,boundary:2,fadeTime:400,animationDelay:20,swingSpeed:3,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){},onDblClick:function(){},onRelease:function(){}}})(jQuery);

+ 661
- 0
rophako/www/js/jquery.color.js View File

@@ -0,0 +1,661 @@
/*!
* jQuery Color Animations v2.0pre
* http://jquery.org/
*
* Copyright 2011 John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/

(function( jQuery, undefined ){
var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color outlineColor".split(" "),

// plusequals test for += 100 -= 100
rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
// a set of RE's that can match strings and generate color tuples.
stringParsers = [{
re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
parse: function( execResult ) {
return [
execResult[ 1 ],
execResult[ 2 ],
execResult[ 3 ],
execResult[ 4 ]
];
}
}, {
re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
parse: function( execResult ) {
return [
2.55 * execResult[1],
2.55 * execResult[2],
2.55 * execResult[3],
execResult[ 4 ]
];
}
}, {
re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,
parse: function( execResult ) {
return [
parseInt( execResult[ 1 ], 16 ),
parseInt( execResult[ 2 ], 16 ),
parseInt( execResult[ 3 ], 16 )
];
}
}, {
re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,
parse: function( execResult ) {
return [
parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
];
}
}, {
re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
space: "hsla",
parse: function( execResult ) {
return [
execResult[1],
execResult[2] / 100,
execResult[3] / 100,
execResult[4]
];
}
}],

// jQuery.Color( )
color = jQuery.Color = function( color, green, blue, alpha ) {
return new jQuery.Color.fn.parse( color, green, blue, alpha );
},
spaces = {
rgba: {
cache: "_rgba",
props: {
red: {
idx: 0,
type: "byte",
empty: true
},
green: {
idx: 1,
type: "byte",
empty: true
},
blue: {
idx: 2,
type: "byte",
empty: true
},
alpha: {
idx: 3,
type: "percent",
def: 1
}
}
},
hsla: {
cache: "_hsla",
props: {
hue: {
idx: 0,
type: "degrees",
empty: true
},
saturation: {
idx: 1,
type: "percent",
empty: true
},
lightness: {
idx: 2,
type: "percent",
empty: true
}
}
}
},
propTypes = {
"byte": {
floor: true,
min: 0,
max: 255
},
"percent": {
min: 0,
max: 1
},
"degrees": {
mod: 360,
floor: true
}
},
rgbaspace = spaces.rgba.props,
support = color.support = {},

// colors = jQuery.Color.names
colors,

// local aliases of functions called often
each = jQuery.each;

spaces.hsla.props.alpha = rgbaspace.alpha;

function clamp( value, prop, alwaysAllowEmpty ) {
var type = propTypes[ prop.type ] || {},
allowEmpty = prop.empty || alwaysAllowEmpty;

if ( allowEmpty && value == null ) {
return null;
}
if ( prop.def && value == null ) {
return prop.def;
}
if ( type.floor ) {
value = ~~value;
} else {
value = parseFloat( value );
}
if ( value == null || isNaN( value ) ) {
return prop.def;
}
if ( type.mod ) {
value = value % type.mod;
// -10 -> 350
return value < 0 ? type.mod + value : value;
}

// for now all property types without mod have min and max
return type.min > value ? type.min : type.max < value ? type.max : value;
}

function stringParse( string ) {
var inst = color(),
rgba = inst._rgba = [];

string = string.toLowerCase();

each( stringParsers, function( i, parser ) {
var match = parser.re.exec( string ),
values = match && parser.parse( match ),
parsed,
spaceName = parser.space || "rgba",
cache = spaces[ spaceName ].cache;


if ( values ) {
parsed = inst[ spaceName ]( values );

// if this was an rgba parse the assignment might happen twice
// oh well....
inst[ cache ] = parsed[ cache ];
rgba = inst._rgba = parsed._rgba;

// exit each( stringParsers ) here because we matched
return false;
}
});

// Found a stringParser that handled it
if ( rgba.length !== 0 ) {

// if this came from a parsed string, force "transparent" when alpha is 0
// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
if ( Math.max.apply( Math, rgba ) === 0 ) {
jQuery.extend( rgba, colors.transparent );
}
return inst;
}

// named colors / default - filter back through parse function
if ( string = colors[ string ] ) {
return string;
}
}

color.fn = color.prototype = {
constructor: color,
parse: function( red, green, blue, alpha ) {
if ( red === undefined ) {
this._rgba = [ null, null, null, null ];
return this;
}
if ( red instanceof jQuery || red.nodeType ) {
red = red instanceof jQuery ? red.css( green ) : jQuery( red ).css( green );
green = undefined;
}

var inst = this,
type = jQuery.type( red ),
rgba = this._rgba = [],
source;

// more than 1 argument specified - assume ( red, green, blue, alpha )
if ( green !== undefined ) {
red = [ red, green, blue, alpha ];
type = "array";
}

if ( type === "string" ) {
return this.parse( stringParse( red ) || colors._default );
}

if ( type === "array" ) {
each( rgbaspace, function( key, prop ) {
rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
});
return this;
}

if ( type === "object" ) {
if ( red instanceof color ) {
each( spaces, function( spaceName, space ) {
if ( red[ space.cache ] ) {
inst[ space.cache ] = red[ space.cache ].slice();
}
});
} else {
each( spaces, function( spaceName, space ) {
each( space.props, function( key, prop ) {
var cache = space.cache;

// if the cache doesn't exist, and we know how to convert
if ( !inst[ cache ] && space.to ) {

// if the value was null, we don't need to copy it
// if the key was alpha, we don't need to copy it either
if ( red[ key ] == null || key === "alpha") {
return;
}
inst[ cache ] = space.to( inst._rgba );
}

// this is the only case where we allow nulls for ALL properties.
// call clamp with alwaysAllowEmpty
inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
});
});
}
return this;
}
},
is: function( compare ) {
var is = color( compare ),
same = true,
myself = this;

each( spaces, function( _, space ) {
var isCache = is[ space.cache ],
localCache;
if (isCache) {
localCache = myself[ space.cache ] || space.to && space.to( myself._rgba ) || [];
each( space.props, function( _, prop ) {
if ( isCache[ prop.idx ] != null ) {
same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
return same;
}
});
}
return same;
});
return same;
},
_space: function() {
var used = [],
inst = this;
each( spaces, function( spaceName, space ) {
if ( inst[ space.cache ] ) {
used.push( spaceName );
}
});