ImageGrid/ui/index.html

404 lines
7.6 KiB
HTML
Raw Normal View History

<html>
<head>
<title>ImageGrid.Viewer</title>
<style>
.viewer {
position: relative;
width: 800px;
height: 600px;
overflow: hidden;
border: solid blue 1px;
}
.ribbon-set {
position: absolute;
}
.ribbon-set:empty:after {
display: block;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
content: "Empty";
text-align: center;
}
.ribbon {
position: relative;
display: block;
height: auto;
min-width: 0px;
overflow: visible;
white-space: nowrap;
font-size: 0;
/*
margin-top: 20px;
margin-bottom: 20px;
*/
}
.ribbon:first-child {
margin-top: 0px;
}
.ribbon:last-child {
margin-bottom: 0px;
}
/* XXX do we actually need this? */
.current.ribbon {
}
.image {
position: relative;
display: inline-block;
vertical-align: middle;
text-align;left;
width: 300px;
height: 300px;
font-size: 12pt;
background: black;
box-sizing: border-box;
border: solid gray 1px;
color: white;
}
.current.image {
background: red;
}
/* XXX this misbehaves... (happens with page zoom) */
.marked.image:after {
display: block;
position: absolute;
content: "";
font-size: 0pt;
border: none;
/* XXX this is not uniform circle for some reason... (connected with page zoom) */
width: 5px;
height: 5px;
bottom: 5px;
right: 5px;
border-radius: 50%;
background: blue;
}
</style>
<script src="jquery.js"></script>
<script src="lib/jli.js"></script>
<script src="lib/keyboard.js"></script>
<script>
/*
Viewer Generation III
Split the API into the following sections:
- main control actions
do main domain tasks like image and ribbon manipulation.
- serialization and deserialization
load and save data
- UI
basic align, animation and modes
*/
var toggleImageMark = createCSSClassToggler('.current.image', 'marked')
function createImage(n){
if(n == null){
if(window._n == null){
window._n = 0
}
n = _n
_n += 1
}
return $('<div order="'+n+'" class="image"/>')
}
function createRibbon(){
return $('<div class="ribbon"/>')
}
// NOTE: if this returns null, it means that the element is smallest in
// target ribbon -- first position.
function getImageBefore(image, ribbon){
image = $(image)
var images = $(ribbon).find('.image')
var order = image.attr('order')
var prev = null
images.each(function(){
if(order < $(this).attr('order')){
return false
}
prev = this
})
return prev
}
function shiftTo(image, ribbon){
var target = getImageBefore(image, ribbon)
var cur_ribbon = image.closest('.ribbon')
// insert before the first image if nothing is before the target...
if(target == null){
image.insertBefore($(ribbon).find('.image').first())
} else {
image.insertAfter(target)
}
// if removing last image out of a ribbon, remove the ribbon....
if(cur_ribbon.find('.image').length == 0){
cur_ribbon.remove()
}
return image
}
function shiftImage(direction, image, force_create_ribbon){
if(image == null){
// XXX need to make this context specific...
image = $('.current.image')
} else {
image = $(image)
}
var ribbon = image.closest('.ribbon')[direction]('.ribbon')
// need to create a new ribbon...
if(ribbon.length == 0 || force_create_ribbon == true){
ribbon = createRibbon()['insert' + (direction == 'prev'
? 'Before'
: 'After')](image.closest('.ribbon'))
ribbon.append(image)
} else {
shiftTo(image, ribbon)
}
return image
}
// short-hand methods...
function shiftImageUp(image){
return shiftImage('prev', image)
}
function shiftImageDown(image){
return shiftImage('next', image)
}
function shiftImageUpNewRibbon(image){
return shiftImage('prev', image, true)
}
function shiftImageDownNewRibbon(image){
return shiftImage('next', image, true)
}
// TODO manual image ordering (shiftLeft/shiftRight functions)
// XXX
function focusImage(image){
image.closest('.viewer').find('.current.image').removeClass('current')
return image.addClass('current')
}
// Alignment API...
// ...tried to make this as brain-dead-stupidly-simple as possible...
function relativeVisualPosition(outer, inner){
outer = $(outer).offset()
inner = $(inner).offset()
return {
top: inner.top - outer.top,
left: inner.left - outer.left
}
}
// This appears to work well with scaling...
// XXX make this more configurable...
// XXX this only works for square images...
function centerImage(image, mode){
if(mode == null){
//mode = 'css'
mode = 'animate'
}
if(image == null){
image = $('.current.image')
}
var viewer = $('.viewer')
// XXX should these be "inner"???
var W = viewer.innerWidth()
var H = viewer.innerHeight()
var ribbons = $('.ribbon-set')
var scale = getElementScale(ribbons)
// NOTE: these are scalable, this needs to get normalized...
var w = image.outerWidth()*scale
var h = image.outerHeight()*scale
var pos = relativeVisualPosition(viewer, image)
// zero out top/left if set to anything other than a specific number...
var t = parseFloat(ribbons.css('top'))
t = t ? t : 0
var l = parseFloat(ribbons.css('left'))
l = l ? l : 0
// do the actual work...
return ribbons[mode]({
'top': t - pos.top + (H - h)/2,
'left': l - pos.left + (W - w)/2
})
}
function fitNImages(n){
var image = $('.current.image')
var size = image.outerHeight(true)
var viewer = $('.viewer')
var W = viewer.innerWidth()
var H = viewer.innerHeight()
var scale = Math.min(W / (size * n), H / size)
// XXX if animating, the next two likes must be animated together...
setElementScale($('.ribbon-set'), scale)
centerImage(image, 'css')
}
// XXX use CSS toggler...
// XXX revise: does extra stuff...
function toggleImageProportions(mode){
var image = $('.image')
var h = image.outerHeight(true)
var w = image.outerWidth(true)
if(mode == '?'){
return h != w ? 'viewer' : 'square'
// square...
} else if(h != w || mode == 'square'){
var size = Math.min(w, h)
image.css({
width: size,
height: size
})
centerImage(null, 'css')
return 'square'
// viewer size...
} else {
var viewer = $('.viewer')
var W = viewer.innerWidth()
var H = viewer.innerHeight()
if(W > H){
image.css('width', W * h/H)
} else {
image.css('height', H * w/W)
}
centerImage(null, 'css')
return 'viewer'
}
}
// NOTE: this is on purpose done relative...
function clickHandler(evt){
var img = $(evt.target).closest('.image')
centerImage(
focusImage(img))
}
// setup...
$(function(){
// populate the viewer...
var r = createRibbon()
var images = []
for(var i=0; i < 40; i++){
images.push(createImage().text(i)[0])
}
r.append($(images))
var rr = r.clone()
var rrr = r.clone()
$('.ribbon-set')
.append(r)
.append(rr)
.append(rrr)
// NOTE: this is global so as to not to add any extra complexity to
// the internal workings...
$('.viewer').click(clickHandler)
})
</script>
</head>
<body>
<!-- This is the basic viewer structure...
Unpopulated
NOTE: there can be only .ribbon-set element.
<div class="viewer">
<div class="ribbon-set"></div>
</div>
Populated
<div class="viewer">
<div class="ribbon-set">
<div class="ribbon">
<div class="image"></div>
<div class="image"></div>
</div>
<div class="ribbon">
<div class="image"></div>
<div class="current image"></div>
<div class="image"></div>
<div class="image"></div>
</div>
</div>
</div>
-->
<div class="viewer">
<div class="ribbon-set"></div>
</div>
<!-- vim:set ts=4 sw=4 spell : -->
</body>
</html>