Add photo albums and hook them up to the blog
|
@ -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.
|
|
@ -1,3 +1,5 @@
|
|||
flask
|
||||
redis
|
||||
bcrypt
|
||||
pillow
|
||||
requests
|
||||
|
|
|
@ -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
rophako/model/photo.py
Normal 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]
|
|
@ -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:
|
||||
|
|
|
@ -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
rophako/modules/photo.py
Normal 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")
|
|
@ -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)
|
|
@ -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
Normal file
After Width: | Height: | Size: 329 B |
165
rophako/www/css/jquery.Jcrop.css
Normal 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
rophako/www/css/jquery.Jcrop.min.css
vendored
Normal 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
Normal file
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 418 B |
After Width: | Height: | Size: 312 B |
After Width: | Height: | Size: 205 B |
After Width: | Height: | Size: 262 B |
After Width: | Height: | Size: 348 B |
After Width: | Height: | Size: 207 B |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 278 B |
After Width: | Height: | Size: 328 B |
BIN
rophako/www/css/ui-lightness/images/ui-icons_222222_256x240.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
rophako/www/css/ui-lightness/images/ui-icons_228ef1_256x240.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
rophako/www/css/ui-lightness/images/ui-icons_ef8c08_256x240.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
rophako/www/css/ui-lightness/images/ui-icons_ffd27a_256x240.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
rophako/www/css/ui-lightness/images/ui-icons_ffffff_256x240.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
485
rophako/www/css/ui-lightness/jquery-ui-1.10.4.custom.css
vendored
Normal 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
rophako/www/css/ui-lightness/jquery-ui-1.10.4.custom.min.css
vendored
Normal file
2747
rophako/www/js/jquery-ui-1.10.4.custom.js
vendored
Normal file
6
rophako/www/js/jquery-ui-1.10.4.custom.min.js
vendored
Normal file
1694
rophako/www/js/jquery.Jcrop.js
Normal file
22
rophako/www/js/jquery.Jcrop.min.js
vendored
Normal 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
rophako/www/js/jquery.color.js
Normal 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 );
|
||||
}
|
||||
});
|
||||
return used.pop();
|
||||
},
|
||||
transition: function( other, distance ) {
|
||||
var end = color( other ),
|
||||
spaceName = end._space(),
|
||||
space = spaces[ spaceName ],
|
||||
start = this[ space.cache ] || space.to( this._rgba ),
|
||||
result = start.slice();
|
||||
|
||||
end = end[ space.cache ];
|
||||
each( space.props, function( key, prop ) {
|
||||
var index = prop.idx,
|
||||
startValue = start[ index ],
|
||||
endValue = end[ index ],
|
||||
type = propTypes[ prop.type ] || {};
|
||||
|
||||
// if null, don't override start value
|
||||
if ( endValue === null ) {
|
||||
return;
|
||||
}
|
||||
// if null - use end
|
||||
if ( startValue === null ) {
|
||||
result[ index ] = endValue;
|
||||
} else {
|
||||
if ( type.mod ) {
|
||||
if ( endValue - startValue > type.mod / 2 ) {
|
||||
startValue += type.mod;
|
||||
} else if ( startValue - endValue > type.mod / 2 ) {
|
||||
startValue -= type.mod;
|
||||
}
|
||||
}
|
||||
result[ prop.idx ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
|
||||
}
|
||||
});
|
||||
return this[ spaceName ]( result );
|
||||
},
|
||||
blend: function( opaque ) {
|
||||
// if we are already opaque - return ourself
|
||||
if ( this._rgba[ 3 ] === 1 ) {
|
||||
return this;
|
||||
}
|
||||
|
||||
var rgb = this._rgba.slice(),
|
||||
a = rgb.pop(),
|
||||
blend = color( opaque )._rgba;
|
||||
|
||||
return color( jQuery.map( rgb, function( v, i ) {
|
||||
return ( 1 - a ) * blend[ i ] + a * v;
|
||||
}));
|
||||
},
|
||||
toRgbaString: function() {
|
||||
var prefix = "rgba(",
|
||||
rgba = jQuery.map( this._rgba, function( v, i ) {
|
||||
return v == null ? ( i > 2 ? 1 : 0 ) : v;
|
||||
});
|
||||
|
||||
if ( rgba[ 3 ] === 1 ) {
|
||||
rgba.pop();
|
||||
prefix = "rgb(";
|
||||
}
|
||||
|
||||
return prefix + rgba.join(",") + ")";
|
||||
},
|
||||
toHslaString: function() {
|
||||
var prefix = "hsla(",
|
||||
hsla = jQuery.map( this.hsla(), function( v, i ) {
|
||||
if ( v == null ) {
|
||||
v = i > 2 ? 1 : 0;
|
||||
}
|
||||
|
||||
// catch 1 and 2
|
||||
if ( i && i < 3 ) {
|
||||
v = Math.round( v * 100 ) + "%";
|
||||
}
|
||||
return v;
|
||||
});
|
||||
|
||||
if ( hsla[ 3 ] === 1 ) {
|
||||
hsla.pop();
|
||||
prefix = "hsl(";
|
||||
}
|
||||
return prefix + hsla.join(",") + ")";
|
||||
},
|
||||
toHexString: function( includeAlpha ) {
|
||||
var rgba = this._rgba.slice(),
|
||||
alpha = rgba.pop();
|
||||
|
||||
if ( includeAlpha ) {
|
||||
rgba.push( ~~( alpha * 255 ) );
|
||||
}
|
||||
|
||||
return "#" + jQuery.map( rgba, function( v, i ) {
|
||||
|
||||
// default to 0 when nulls exist
|
||||
v = ( v || 0 ).toString( 16 );
|
||||
return v.length === 1 ? "0" + v : v;
|
||||
}).join("");
|
||||
},
|
||||
toString: function() {
|
||||
return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
|
||||
}
|
||||
};
|
||||
color.fn.parse.prototype = color.fn;
|
||||
|
||||
// hsla conversions adapted from:
|
||||
// http://www.google.com/codesearch/p#OAMlx_jo-ck/src/third_party/WebKit/Source/WebCore/inspector/front-end/Color.js&d=7&l=193
|
||||
|
||||
function hue2rgb( p, q, h ) {
|
||||
h = ( h + 1 ) % 1;
|
||||
if ( h * 6 < 1 ) {
|
||||
return p + (q - p) * 6 * h;
|
||||
}
|
||||
if ( h * 2 < 1) {
|
||||
return q;
|
||||
}
|
||||
if ( h * 3 < 2 ) {
|
||||
return p + (q - p) * ((2/3) - h) * 6;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
spaces.hsla.to = function ( rgba ) {
|
||||
if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
|
||||
return [ null, null, null, rgba[ 3 ] ];
|
||||
}
|
||||
var r = rgba[ 0 ] / 255,
|
||||
g = rgba[ 1 ] / 255,
|
||||
b = rgba[ 2 ] / 255,
|
||||
a = rgba[ 3 ],
|
||||
max = Math.max( r, g, b ),
|
||||
min = Math.min( r, g, b ),
|
||||
diff = max - min,
|
||||
add = max + min,
|
||||
l = add * 0.5,
|
||||
h, s;
|
||||
|
||||
if ( min === max ) {
|
||||
h = 0;
|
||||
} else if ( r === max ) {
|
||||
h = ( 60 * ( g - b ) / diff ) + 360;
|
||||
} else if ( g === max ) {
|
||||
h = ( 60 * ( b - r ) / diff ) + 120;
|
||||
} else {
|
||||
h = ( 60 * ( r - g ) / diff ) + 240;
|
||||
}
|
||||
|
||||
if ( l === 0 || l === 1 ) {
|
||||
s = l;
|
||||
} else if ( l <= 0.5 ) {
|
||||
s = diff / add;
|
||||
} else {
|
||||
s = diff / ( 2 - add );
|
||||
}
|
||||
return [ Math.round(h) % 360, s, l, a == null ? 1 : a ];
|
||||
};
|
||||
|
||||
spaces.hsla.from = function ( hsla ) {
|
||||
if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
|
||||
return [ null, null, null, hsla[ 3 ] ];
|
||||
}
|
||||
var h = hsla[ 0 ] / 360,
|
||||
s = hsla[ 1 ],
|
||||
l = hsla[ 2 ],
|
||||
a = hsla[ 3 ],
|
||||
q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
|
||||
p = 2 * l - q,
|
||||
r, g, b;
|
||||
|
||||
return [
|
||||
Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
|
||||
Math.round( hue2rgb( p, q, h ) * 255 ),
|
||||
Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
|
||||
a
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
each( spaces, function( spaceName, space ) {
|
||||
var props = space.props,
|
||||
cache = space.cache,
|
||||
to = space.to,
|
||||
from = space.from;
|
||||
|
||||
// makes rgba() and hsla()
|
||||
color.fn[ spaceName ] = function( value ) {
|
||||
|
||||
// generate a cache for this space if it doesn't exist
|
||||
if ( to && !this[ cache ] ) {
|
||||
this[ cache ] = to( this._rgba );
|
||||
}
|
||||
if ( value === undefined ) {
|
||||
return this[ cache ].slice();
|
||||
}
|
||||
|
||||
var type = jQuery.type( value ),
|
||||
arr = ( type === "array" || type === "object" ) ? value : arguments,
|
||||
local = this[ cache ].slice(),
|
||||
ret;
|
||||
|
||||
each( props, function( key, prop ) {
|
||||
var val = arr[ type === "object" ? key : prop.idx ];
|
||||
if ( val == null ) {
|
||||
val = local[ prop.idx ];
|
||||
}
|
||||
local[ prop.idx ] = clamp( val, prop );
|
||||
});
|
||||
|
||||
if ( from ) {
|
||||
ret = color( from( local ) );
|
||||
ret[ cache ] = local;
|
||||
return ret;
|
||||
} else {
|
||||
return color( local );
|
||||
}
|
||||
};
|
||||
|
||||
// makes red() green() blue() alpha() hue() saturation() lightness()
|
||||
each( props, function( key, prop ) {
|
||||
// alpha is included in more than one space
|
||||
if ( color.fn[ key ] ) {
|
||||
return;
|
||||
}
|
||||
color.fn[ key ] = function( value ) {
|
||||
var vtype = jQuery.type( value ),
|
||||
fn = ( key === 'alpha' ? ( this._hsla ? 'hsla' : 'rgba' ) : spaceName ),
|
||||
local = this[ fn ](),
|
||||
cur = local[ prop.idx ],
|
||||
match;
|
||||
|
||||
if ( vtype === "undefined" ) {
|
||||
return cur;
|
||||
}
|
||||
|
||||
if ( vtype === "function" ) {
|
||||
value = value.call( this, cur );
|
||||
vtype = jQuery.type( value );
|
||||
}
|
||||
if ( value == null && prop.empty ) {
|
||||
return this;
|
||||
}
|
||||
if ( vtype === "string" ) {
|
||||
match = rplusequals.exec( value );
|
||||
if ( match ) {
|
||||
value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
|
||||
}
|
||||
}
|
||||
local[ prop.idx ] = value;
|
||||
return this[ fn ]( local );
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// add .fx.step functions
|
||||
each( stepHooks, function( i, hook ) {
|
||||
jQuery.cssHooks[ hook ] = {
|
||||
set: function( elem, value ) {
|
||||
var parsed, backgroundColor, curElem;
|
||||
|
||||
if ( jQuery.type( value ) !== 'string' || ( parsed = stringParse( value ) ) )
|
||||
{
|
||||
value = color( parsed || value );
|
||||
if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
|
||||
curElem = hook === "backgroundColor" ? elem.parentNode : elem;
|
||||
do {
|
||||
backgroundColor = jQuery.curCSS( curElem, "backgroundColor" );
|
||||
} while (
|
||||
( backgroundColor === "" || backgroundColor === "transparent" ) &&
|
||||
( curElem = curElem.parentNode ) &&
|
||||
curElem.style
|
||||
);
|
||||
|
||||
value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
|
||||
backgroundColor :
|
||||
"_default" );
|
||||
}
|
||||
|
||||
value = value.toRgbaString();
|
||||
}
|
||||
elem.style[ hook ] = value;
|
||||
}
|
||||
};
|
||||
jQuery.fx.step[ hook ] = function( fx ) {
|
||||
if ( !fx.colorInit ) {
|
||||
fx.start = color( fx.elem, hook );
|
||||
fx.end = color( fx.end );
|
||||
fx.colorInit = true;
|
||||
}
|
||||
jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
|
||||
};
|
||||
});
|
||||
|
||||
// detect rgba support
|
||||
jQuery(function() {
|
||||
var div = document.createElement( "div" ),
|
||||
div_style = div.style;
|
||||
|
||||
div_style.cssText = "background-color:rgba(1,1,1,.5)";
|
||||
support.rgba = div_style.backgroundColor.indexOf( "rgba" ) > -1;
|
||||
});
|
||||
|
||||
// Some named colors to work with
|
||||
// From Interface by Stefan Petre
|
||||
// http://interface.eyecon.ro/
|
||||
colors = jQuery.Color.names = {
|
||||
aqua: "#00ffff",
|
||||
azure: "#f0ffff",
|
||||
beige: "#f5f5dc",
|
||||
black: "#000000",
|
||||
blue: "#0000ff",
|
||||
brown: "#a52a2a",
|
||||
cyan: "#00ffff",
|
||||
darkblue: "#00008b",
|
||||
darkcyan: "#008b8b",
|
||||
darkgrey: "#a9a9a9",
|
||||
darkgreen: "#006400",
|
||||
darkkhaki: "#bdb76b",
|
||||
darkmagenta: "#8b008b",
|
||||
darkolivegreen: "#556b2f",
|
||||
darkorange: "#ff8c00",
|
||||
darkorchid: "#9932cc",
|
||||
darkred: "#8b0000",
|
||||
darksalmon: "#e9967a",
|
||||
darkviolet: "#9400d3",
|
||||
fuchsia: "#ff00ff",
|
||||
gold: "#ffd700",
|
||||
green: "#008000",
|
||||
indigo: "#4b0082",
|
||||
khaki: "#f0e68c",
|
||||
lightblue: "#add8e6",
|
||||
lightcyan: "#e0ffff",
|
||||
lightgreen: "#90ee90",
|
||||
lightgrey: "#d3d3d3",
|
||||
lightpink: "#ffb6c1",
|
||||
lightyellow: "#ffffe0",
|
||||
lime: "#00ff00",
|
||||
magenta: "#ff00ff",
|
||||
maroon: "#800000",
|
||||
navy: "#000080",
|
||||
olive: "#808000",
|
||||
orange: "#ffa500",
|
||||
pink: "#ffc0cb",
|
||||
purple: "#800080",
|
||||
violet: "#800080",
|
||||
red: "#ff0000",
|
||||
silver: "#c0c0c0",
|
||||
white: "#ffffff",
|
||||
yellow: "#ffff00",
|
||||
transparent: [ null, null, null, 0 ],
|
||||
_default: "#ffffff"
|
||||
};
|
||||
})( jQuery );
|
33
rophako/www/photos/album.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}{{ album }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Album: {{ album }}</h1>
|
||||
|
||||
<ul class="photo-grid">
|
||||
|
||||
{% for photo in photos %}
|
||||
<li class="portrait">
|
||||
<div class="dummy"></div>
|
||||
<div class="photo-grid-item">
|
||||
<a href="{{ url_for('photo.view_photo', key=photo['key']) }}">
|
||||
<img src="{{ app['photo_url'] }}/{{ photo['data']['thumb'] }}" width="100%" height="100%">
|
||||
<span class="name">{{ photo["data"]["caption"] }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
|
||||
{% if session["login"] %}
|
||||
<h1>Administrative Options</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="{{ url_for('photo.upload') }}">Upload a Photo</a></li>
|
||||
{% if photos|length > 0 %}<li><a href="{{ url_for('photo.arrange_photos', album=album) }}">Rearrange Photos</a></li>{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
37
rophako/www/photos/albums.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Photo Albums{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Photo Albums</h1>
|
||||
|
||||
{% if albums|length == 0 %}
|
||||
<em>There are no photo albums yet.</em>
|
||||
{% else %}
|
||||
<ul class="photo-grid">
|
||||
|
||||
{% for album in albums %}
|
||||
<li class="portrait">
|
||||
<div class="dummy"></div>
|
||||
<div class="photo-grid-item">
|
||||
<a href="{{ url_for('photo.album_index', name=album['name']) }}">
|
||||
<img src="{{ app['photo_url'] }}/{{ album['cover'] }}" width="100%" height="100%">
|
||||
<span class="name">{{ album["name"] }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
{% endif %}
|
||||
|
||||
{% if session["login"] %}
|
||||
<h1>Administrative Options</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="{{ url_for('photo.upload') }}">Upload a Photo</a></li>
|
||||
{% if albums|length > 0 %}<li><a href="{{ url_for('photo.arrange_albums') }}">Rearrange Albums</a></li>{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
60
rophako/www/photos/arrange_albums.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Arrange Albums{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Arrange Albums</h1>
|
||||
|
||||
Drag and drop your albums in the positions you want them in.<p>
|
||||
|
||||
<form name="arrange" id="arrange-form" action="{{ url_for('photo.arrange_albums') }}" method="POST">
|
||||
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="order" id="order" value="">
|
||||
<button type="submit">Save Changes</button>
|
||||
</form>
|
||||
<p>
|
||||
|
||||
<ul id="arrange-photos" class="photo-grid">
|
||||
|
||||
{% for album in albums %}
|
||||
<li data-name="{{ album['name'] }}" class="portrait">
|
||||
<div class="dummy"></div>
|
||||
<div class="photo-grid-item">
|
||||
<a href="{{ url_for('photo.album_index', name=album['name']) }}">
|
||||
<img src="{{ app['photo_url'] }}/{{ album['cover'] }}" width="100%" height="100%">
|
||||
<span class="name">{{ album["name"] }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<link rel="stylesheet" type="text/css" media="all" href="/css/ui-lightness/jquery-ui-1.10.4.custom.css">
|
||||
<script src="/js/jquery-ui-1.10.4.custom.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var $photos = $("#arrange-photos"),
|
||||
$form = $("#arrange-form"),
|
||||
$order = $("#order");
|
||||
var doArrangePhotos = function() {
|
||||
var order = [];
|
||||
|
||||
// Get the list of elements.
|
||||
var list = $photos.children().each(function() {
|
||||
var name = $(this).data("name");
|
||||
order.push(name);
|
||||
});
|
||||
|
||||
$order.val(order.join(";"));
|
||||
return true;
|
||||
};
|
||||
|
||||
$form.submit(doArrangePhotos);
|
||||
$photos.sortable();
|
||||
$photos.disableSelection();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
58
rophako/www/photos/arrange_photos.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Arrange Photos{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Arrange Photos</h1>
|
||||
|
||||
Drag and drop your photos in the positions you want them in.<p>
|
||||
|
||||
<form name="arrange" id="arrange-form" action="{{ url_for('photo.arrange_photos', album=album) }}" method="POST">
|
||||
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="order" id="order" value="">
|
||||
<button type="submit">Save Changes</button>
|
||||
</form>
|
||||
<p>
|
||||
|
||||
<ul id="arrange-photos" class="photo-grid">
|
||||
|
||||
{% for photo in photos %}
|
||||
<li data-name="{{ photo['key'] }}" class="portrait">
|
||||
<div class="dummy"></div>
|
||||
<div class="photo-grid-item">
|
||||
<img src="{{ app['photo_url'] }}/{{ photo['data']['thumb'] }}" width="100%" height="100%">
|
||||
<span class="name">{{ photo['data']['caption'] }}</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<link rel="stylesheet" type="text/css" media="all" href="/css/ui-lightness/jquery-ui-1.10.4.custom.css">
|
||||
<script src="/js/jquery-ui-1.10.4.custom.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var $photos = $("#arrange-photos"),
|
||||
$form = $("#arrange-form"),
|
||||
$order = $("#order");
|
||||
var doArrangePhotos = function() {
|
||||
var order = [];
|
||||
|
||||
// Get the list of elements.
|
||||
var list = $photos.children().each(function() {
|
||||
var name = $(this).data("name");
|
||||
order.push(name);
|
||||
});
|
||||
|
||||
$order.val(order.join(";"));
|
||||
return true;
|
||||
};
|
||||
|
||||
$form.submit(doArrangePhotos);
|
||||
$photos.sortable();
|
||||
$photos.disableSelection();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
107
rophako/www/photos/crop.html
Normal file
|
@ -0,0 +1,107 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Crop Photo{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Crop Photo</h1>
|
||||
|
||||
All versions of your photo except the largest one are cropped into a square
|
||||
shape. You can use this page to modify the region of the photo you want to
|
||||
crop.<p>
|
||||
|
||||
<table border="0" cellspacing="4" cellpadding="2">
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<img src="{{ app['photo_url'] }}/{{ preview }}" id="cropbox">
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<strong>Preview:</strong><br>
|
||||
<div style="width: 100px; height: 100px; overflow: hidden">
|
||||
<img src="{{ app['photo_url'] }}/{{ preview }}" id="preview" style="max-width: none">
|
||||
</div>
|
||||
<p>
|
||||
|
||||
<form name="crop" action="{{ url_for('photo.crop', photo=photo) }}" method="POST">
|
||||
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="x" id="x" value="0">
|
||||
<input type="hidden" name="y" id="y" value="0">
|
||||
<input type="hidden" name="length" id="length" value="0">
|
||||
|
||||
<button type="submit">Crop Photo!</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="/js/jquery.Jcrop.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/css/jquery.Jcrop.css">
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var $cropbox = $("#cropbox"),
|
||||
$preview = $("#preview"),
|
||||
$x = $("#x"),
|
||||
$y = $("#y"),
|
||||
$length = $("#length");
|
||||
|
||||
// Find the shortest side.
|
||||
var len;
|
||||
if ($cropbox.width() > $cropbox.height()) {
|
||||
len = $cropbox.height();
|
||||
}
|
||||
else {
|
||||
len = $cropbox.width();
|
||||
}
|
||||
|
||||
// Jcrop handler.
|
||||
var showPreview = function(coords) {
|
||||
if (parseInt(coords.w) > 0) {
|
||||
var rx = 100 / coords.w;
|
||||
var ry = 100 / coords.h;
|
||||
var ht = $cropbox.height();
|
||||
var wt = $cropbox.width();
|
||||
|
||||
// Make the coords into percentages, so it works on mobile.
|
||||
|
||||
// Get the true dimensions of the image from PIL.
|
||||
var trueW = {{ true_width }};
|
||||
var trueH = {{ true_height }};
|
||||
|
||||
// The actual (possibly scaled) image shown on the page is hereby called
|
||||
// the "display image"... turn our "display coords" into percentages
|
||||
// across the image.
|
||||
var percentX = coords.x / wt;
|
||||
var percentY = coords.y / ht;
|
||||
var percentLen = coords.w / wt;
|
||||
|
||||
// Now get our true coords by multiplying those percentages against the
|
||||
// true dimensions of the image from PIL.
|
||||
var trueX = trueW * percentX;
|
||||
var trueY = trueH * percentY;
|
||||
var trueLen = trueW * percentLen;
|
||||
|
||||
// Update the preview.
|
||||
$preview.css({
|
||||
width: Math.round(rx * wt) + "px",
|
||||
height: Math.round(ry * ht) + "px",
|
||||
marginLeft: "-" + Math.round(rx * coords.x) + "px",
|
||||
marginTop: "-" + Math.round(ry * coords.y) + "px"
|
||||
})
|
||||
|
||||
// Update the form.
|
||||
$x.val(parseInt(trueX));
|
||||
$y.val(parseInt(trueY));
|
||||
$length.val(parseInt(trueLen));
|
||||
}
|
||||
}
|
||||
|
||||
$cropbox.Jcrop({
|
||||
onChange: showPreview,
|
||||
onSelect: showPreview,
|
||||
aspectRatio: 1,
|
||||
setSelect: [ 0, 0, len, len ],
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
16
rophako/www/photos/delete.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Delete Photo{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Delete Photo</h1>
|
||||
|
||||
<img src="{{ app['photo_url'] }}/{{ photo['thumb'] }}" class="portrait"><p>
|
||||
|
||||
<form name="delete" action="{{ url_for('photo.delete', key=key) }}" method="POST">
|
||||
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||
Are you <strong>sure</strong> you want to delete this photo?<p>
|
||||
|
||||
<button type="submit">Yes, Delete This Photo</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
18
rophako/www/photos/edit.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Edit Photo{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Edit Photo</h1>
|
||||
|
||||
<img src="{{ app['photo_url'] }}/{{ photo['thumb'] }}" class="portrait"><p>
|
||||
|
||||
<form name="edit" action="{{ url_for('photo.edit', key=key) }}" method="POST">
|
||||
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||
|
||||
<strong>Photo Caption:</strong><br>
|
||||
<input type="text" size="40" name="caption" value="{{ photo['caption'] }}"><p>
|
||||
|
||||
<button type="submit">Save Changes</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
89
rophako/www/photos/upload.html
Normal file
|
@ -0,0 +1,89 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Upload a Photo{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>Upload a Photo</h1>
|
||||
|
||||
You can upload a photo from your computer or by pasting in the URL to a photo
|
||||
somewhere else on the Internet.
|
||||
|
||||
<form name="upload" action="{{ url_for('photo.upload') }}" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||
|
||||
<fieldset>
|
||||
<legend>Where is your picture located?</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="location" name="location" value="pc" checked> On my computer
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" class="location" name="location" value="www"> On the web
|
||||
</label><p>
|
||||
|
||||
<div id="pic-pc" class="location-div">
|
||||
<strong>Upload a picture from my computer</strong><br>
|
||||
<input type="file" size="30" name="file">
|
||||
</div>
|
||||
|
||||
<div id="pic-www" class="location-div">
|
||||
<strong>Upload a picture from the Internet</strong><br>
|
||||
<input type="text" size="40" name="url" placeholder="http://" autocomplete="off">
|
||||
</div>
|
||||
<p>
|
||||
|
||||
Only jpeg, gif and png images are supported. There is no maximum file size
|
||||
limit, but be reasonable.
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Photo Options</legend>
|
||||
|
||||
<strong>Photo album:</strong><br>
|
||||
<select id="album" name="album">
|
||||
<optgroup label="Albums">
|
||||
{% for album in album_list %}
|
||||
<option value="{{ album }}"{% if album == selected %} selected{% endif %}>{{ album }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
<option value="">Create a new album</option>
|
||||
</select><p>
|
||||
|
||||
<blockquote id="create-album">
|
||||
<strong>New album:</strong><br>
|
||||
<input type="text" size="20" id="new-album" name="new-album">
|
||||
</blockquote>
|
||||
|
||||
<strong>Caption:</strong><br>
|
||||
<input type="text" size="40" name="caption">
|
||||
</fieldset>
|
||||
<p>
|
||||
|
||||
<button type="submit">Upload Picture</button>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("#pic-www").hide();
|
||||
$("#create-album").hide();
|
||||
|
||||
$(".location").change(function() {
|
||||
$(".location-div").hide();
|
||||
$("#pic-" + $(this).val()).show();
|
||||
});
|
||||
|
||||
$("#album").change(function() {
|
||||
if ($(this).val() === "") {
|
||||
$("#create-album").show();
|
||||
}
|
||||
else {
|
||||
$("#new-album").val("");
|
||||
$("#create-album").hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
49
rophako/www/photos/view.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}{{ photo["caption"] or "Photo" }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
{% macro nav_links() %}
|
||||
<div class="right">
|
||||
Photo {{ photo["position"] }} of {{ photo["siblings"] }}
|
||||
{% if photo["siblings"] > 1 %}
|
||||
|
||||
[
|
||||
<a href="{{ url_for('photo.view_photo', key=photo['previous']) }}">< Previous</a>
|
||||
|
|
||||
<a href="{{ url_for('photo.view_photo', key=photo['next']) }}">Next ></a>
|
||||
]
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<h1>Photo</h1>
|
||||
|
||||
<a href="{{ url_for('photo.album_index', name=photo['album']) }}">< Back to Album</a><p>
|
||||
|
||||
{{ nav_links() }}
|
||||
|
||||
<div class="center">
|
||||
<a href="{{ url_for('photo.view_photo', key=photo['next']) }}">
|
||||
<img src="{{ app['photo_url'] }}/{{ photo['large'] }}" class="portrait">
|
||||
</a><br>
|
||||
<strong>{{ photo["caption"] }}</strong>
|
||||
</div>
|
||||
<p>
|
||||
|
||||
Uploaded by {{ author["name"] }} on {{ photo["pretty_time"] }}.
|
||||
|
||||
{{ nav_links() }}
|
||||
|
||||
{% if session["login"] %}
|
||||
<h1>Administrative Options</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="{{ url_for('photo.upload') }}">Upload a Photo</a></li>
|
||||
<li><a href="{{ url_for('photo.set_cover', album=photo['album'], key=photo['key']) }}">Set Album Cover</a></li>
|
||||
<li><a href="{{ url_for('photo.set_profile', key=photo['key']) }}">Set as my Profile Picture</a></li>
|
||||
<li><a href="{{ url_for('photo.edit', key=photo['key']) }}">Edit this photo</a></li>
|
||||
<li><a href="{{ url_for('photo.delete', key=photo['key']) }}">Delete this photo</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|