Rewrite/refactor of UI.

Use percents instead of pixels when dragging windows. (#16)
Now supports touch and multi-touch for dragging windows. (#484)

Boom. Boom.
Close #16.
Close #484.
This commit is contained in:
Nicolas Stepien 2012-09-16 15:42:31 +02:00
parent ee841f5b52
commit 6b0f739237
3 changed files with 286 additions and 167 deletions

View File

@ -222,68 +222,157 @@
posts: {} posts: {}
}; };
UI = { UI = (function() {
dialog: function(id, position, html) { var dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchmove;
var el; dialog = function(id, position, html) {
var el, move;
el = d.createElement('div'); el = d.createElement('div');
el.className = 'reply dialog'; el.className = 'reply dialog';
el.innerHTML = html; el.innerHTML = html;
el.id = id; el.id = id;
el.style.cssText = localStorage.getItem("" + g.NAMESPACE + id + ".position") || position; el.style.cssText = localStorage.getItem("" + g.NAMESPACE + id + ".position") || position;
el.querySelector('.move').addEventListener('mousedown', UI.dragstart, false); move = el.querySelector('.move');
move.addEventListener('touchstart', dragstart, false);
move.addEventListener('mousedown', dragstart, false);
return el; return el;
}, };
dragstart: function(e) { dragstart = function(e) {
var el, rect; var el, isTouching, o, rect, screenHeight, screenWidth;
e.preventDefault(); e.preventDefault();
UI.el = el = this.parentNode; el = this.parentNode;
d.addEventListener('mousemove', UI.drag, false); if (isTouching = e.type === 'touchstart') {
d.addEventListener('mouseup', UI.dragend, false); e = e.changedTouches[e.changedTouches.length - 1];
rect = el.getBoundingClientRect();
UI.dx = e.clientX - rect.left;
UI.dy = e.clientY - rect.top;
UI.width = d.documentElement.clientWidth - rect.width;
return UI.height = d.documentElement.clientHeight - rect.height;
},
drag: function(e) {
var left, style, top;
left = e.clientX - UI.dx;
top = e.clientY - UI.dy;
left = left < 10 ? '0px' : UI.width - left < 10 ? null : left + 'px';
top = top < 10 ? '0px' : UI.height - top < 10 ? null : top + 'px';
style = UI.el.style;
style.left = left;
style.top = top;
style.right = left ? null : '0px';
return style.bottom = top ? null : '0px';
},
dragend: function() {
localStorage.setItem("" + g.NAMESPACE + UI.el.id + ".position", UI.el.style.cssText);
d.removeEventListener('mousemove', UI.drag, false);
d.removeEventListener('mouseup', UI.dragend, false);
return delete UI.el;
},
hover: function(e) {
var clientHeight, clientWidth, clientX, clientY, height, style, top, _ref;
clientX = e.clientX, clientY = e.clientY;
style = UI.el.style;
_ref = d.documentElement, clientHeight = _ref.clientHeight, clientWidth = _ref.clientWidth;
height = UI.el.offsetHeight;
top = clientY - 120;
style.top = clientHeight <= height || top <= 0 ? '0px' : top + height >= clientHeight ? clientHeight - height + 'px' : top + 'px';
if (clientX <= clientWidth - 400) {
style.left = clientX + 45 + 'px';
return style.right = null;
} else {
style.left = null;
return style.right = clientWidth - clientX + 45 + 'px';
} }
}, rect = el.getBoundingClientRect();
hoverend: function() { screenHeight = d.documentElement.clientHeight;
$.rm(UI.el); screenWidth = d.documentElement.clientWidth;
return delete UI.el; o = {
} id: el.id,
}; style: el.style,
dx: e.clientX - rect.left,
dy: e.clientY - rect.top,
height: screenHeight - rect.height,
width: screenWidth - rect.width,
screenHeight: screenHeight,
screenWidth: screenWidth
};
if (isTouching) {
o.identifier = e.identifier;
o.move = touchmove.bind(o);
o.up = dragend.bind(o);
d.addEventListener('touchmove', o.move, false);
d.addEventListener('touchend', o.up, false);
return d.addEventListener('touchcancel', o.up, false);
} else {
o.move = drag.bind(o);
o.up = dragend.bind(o);
d.addEventListener('mousemove', o.move, false);
return d.addEventListener('mouseup', o.up, false);
}
};
touchmove = function(e) {
var touch, _i, _len, _ref;
_ref = e.changedTouches;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
touch = _ref[_i];
if (touch.identifier === this.identifier) {
drag.call(this, touch);
return;
}
}
};
drag = function(e) {
var left, top;
left = e.clientX - this.dx;
top = e.clientY - this.dy;
if (left < 10) {
left = 0;
} else if (this.width - left < 10) {
left = null;
}
if (top < 10) {
top = 0;
} else if (this.height - top < 10) {
top = null;
}
if (left === null) {
this.style.left = null;
this.style.right = '0%';
} else {
this.style.left = left / this.screenWidth * 100 + '%';
this.style.right = null;
}
if (top === null) {
this.style.top = null;
return this.style.bottom = '0%';
} else {
this.style.top = top / this.screenHeight * 100 + '%';
return this.style.bottom = null;
}
};
dragend = function(e) {
if (e.type === 'mouseup') {
d.removeEventListener('mousemove', this.move, false);
d.removeEventListener('mouseup', this.up, false);
} else {
d.removeEventListener('touchmove', this.move, false);
d.removeEventListener('touchend', this.up, false);
d.removeEventListener('touchcancel', this.up, false);
}
return localStorage.setItem("" + g.NAMESPACE + this.id + ".position", this.style.cssText);
};
hoverstart = function(root, el, events, cb) {
var event, o, _i, _len, _ref;
o = {
root: root,
el: el,
style: el.style,
cb: cb,
events: events.split(' '),
clientHeight: d.documentElement.clientHeight,
clientWidth: d.documentElement.clientWidth
};
o.hover = hover.bind(o);
o.hoverend = hoverend.bind(o);
_ref = o.events;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
event = _ref[_i];
root.addEventListener(event, o.hoverend, false);
}
return root.addEventListener('mousemove', o.hover, false);
};
hover = function(e) {
var clientX, height, top;
height = this.el.offsetHeight;
top = e.clientY - height / 2;
this.style.top = this.clientHeight <= height || top <= 0 ? '0px' : top + height >= this.clientHeight ? this.clientHeight - height + 'px' : top + 'px';
clientX = e.clientX;
if (clientX <= this.clientWidth - 400) {
this.style.left = clientX + 45 + 'px';
return this.style.right = null;
} else {
this.style.left = null;
return this.style.right = this.clientWidth - clientX + 45 + 'px';
}
};
hoverend = function() {
var event, _i, _len, _ref;
this.el.parentNode.removeChild(this.el);
_ref = this.events;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
event = _ref[_i];
this.root.removeEventListener(event, this.hoverend, false);
}
this.root.removeEventListener('mousemove', this.hover, false);
if (this.cb) {
return this.cb.call(this);
}
};
return {
dialog: dialog,
hover: hoverstart
};
})();
/* /*
loosely follows the jquery api: loosely follows the jquery api:
@ -1830,20 +1919,15 @@
if ($.hasClass(this, 'inlined')) { if ($.hasClass(this, 'inlined')) {
return; return;
} }
if (UI.el) {
return;
}
_ref = Get.postDataFromLink(this), board = _ref.board, threadID = _ref.threadID, postID = _ref.postID; _ref = Get.postDataFromLink(this), board = _ref.board, threadID = _ref.threadID, postID = _ref.postID;
qp = UI.el = $.el('div', { qp = $.el('div', {
id: 'qp', id: 'qp',
className: 'reply dialog' className: 'reply dialog'
}); });
UI.hover(e);
context = Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', this)); context = Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', this));
$.add(d.body, qp); $.add(d.body, qp);
Get.postClone(board, threadID, postID, qp, context); Get.postClone(board, threadID, postID, qp, context);
$.on(this, 'mousemove', UI.hover); UI.hover(this, qp, 'mouseout click', QuotePreview.mouseout);
$.on(this, 'mouseout click', QuotePreview.mouseout);
if (!(origin = g.posts["" + board + "." + postID])) { if (!(origin = g.posts["" + board + "." + postID])) {
return; return;
} }
@ -1872,12 +1956,9 @@
} }
} }
}, },
mouseout: function(e) { mouseout: function() {
var clone, post, root, _i, _len, _ref; var clone, post, root, _i, _len, _ref;
root = UI.el.firstElementChild; root = this.el.firstElementChild;
UI.hoverend();
$.off(this, 'mousemove', UI.hover);
$.off(this, 'mouseout click', QuotePreview.mouseout);
if (!root) { if (!root) {
return; return;
} }
@ -2357,30 +2438,29 @@
return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover); return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover);
}, },
mouseover: function() { mouseover: function() {
var el; var el,
if (UI.el) { _this = this;
return; el = $.el('img', {
}
el = UI.el = $.el('img', {
id: 'ihover', id: 'ihover',
src: this.parentNode.href src: this.parentNode.href
}); });
$.add(d.body, el); $.add(d.body, el);
$.on(el, 'load', ImageHover.load); $.on(el, 'load', function() {
return ImageHover.load(_this, el);
});
$.on(el, 'error', ImageHover.error); $.on(el, 'error', ImageHover.error);
$.on(this, 'mousemove', UI.hover); return UI.hover(this, el, 'mouseout');
return $.on(this, 'mouseout', ImageHover.mouseout);
}, },
load: function() { load: function(root, el) {
var style; var e, style;
if (!this.parentNode) { if (!el.parentNode) {
return; return;
} }
style = this.style; style = el.style;
return UI.hover({ e = new Event('mousemove');
clientX: -45 + parseInt(style.left), e.clientX = -45 + parseInt(style.left);
clientY: 120 + parseInt(style.top) e.clientY = 120 + parseInt(style.top);
}); return root.dispatchEvent(e);
}, },
error: function() { error: function() {
var src, timeoutID, url, var src, timeoutID, url,
@ -2413,11 +2493,6 @@
}, { }, {
type: 'head' type: 'head'
}); });
},
mouseout: function() {
UI.hoverend();
$.off(this, 'mousemove', UI.hover);
return $.off(this, 'mouseout', ImageHover.mouseout);
} }
}; };

View File

@ -1,5 +1,6 @@
alpha alpha
- Mayhem - Mayhem
Added touch and multi-touch support for dragging windows.
The Thread Updater will pause when offline, and resume when online. The Thread Updater will pause when offline, and resume when online.
Fix Chrome's install warning that 4chan X would execute on all domains. Fix Chrome's install warning that 4chan X would execute on all domains.
Fix Quote Backlinks not affecting inlined quotes. Fix Quote Backlinks not affecting inlined quotes.

View File

@ -175,72 +175,129 @@ g =
threads: {} threads: {}
posts: {} posts: {}
UI = UI = (->
dialog: (id, position, html) -> dialog = (id, position, html) ->
el = d.createElement 'div' el = d.createElement 'div'
el.className = 'reply dialog' el.className = 'reply dialog'
el.innerHTML = html el.innerHTML = html
el.id = id el.id = id
el.style.cssText = localStorage.getItem("#{g.NAMESPACE}#{id}.position") or position el.style.cssText = localStorage.getItem("#{g.NAMESPACE}#{id}.position") or position
el.querySelector('.move').addEventListener 'mousedown', UI.dragstart, false move = el.querySelector '.move'
move.addEventListener 'touchstart', dragstart, false
move.addEventListener 'mousedown', dragstart, false
el el
dragstart: (e) ->
dragstart = (e) ->
# prevent text selection # prevent text selection
e.preventDefault() e.preventDefault()
UI.el = el = @parentNode el = @parentNode
d.addEventListener 'mousemove', UI.drag, false if isTouching = e.type is 'touchstart'
d.addEventListener 'mouseup', UI.dragend, false e = e.changedTouches[e.changedTouches.length - 1]
# distance from pointer to el edge is constant; calculate it here. # distance from pointer to el edge is constant; calculate it here.
rect = el.getBoundingClientRect() rect = el.getBoundingClientRect()
UI.dx = e.clientX - rect.left screenHeight = d.documentElement.clientHeight
UI.dy = e.clientY - rect.top screenWidth = d.documentElement.clientWidth
UI.width = d.documentElement.clientWidth - rect.width o = {
UI.height = d.documentElement.clientHeight - rect.height id: el.id
drag: (e) -> style: el.style
left = e.clientX - UI.dx dx: e.clientX - rect.left
top = e.clientY - UI.dy dy: e.clientY - rect.top
left = height: screenHeight - rect.height
if left < 10 then '0px' width: screenWidth - rect.width
else if UI.width - left < 10 then null screenHeight: screenHeight
else left + 'px' screenWidth: screenWidth
top = }
if top < 10 then '0px' if isTouching
else if UI.height - top < 10 then null o.identifier = e.identifier
else top + 'px' o.move = touchmove.bind o
{style} = UI.el o.up = dragend.bind o
style.left = left d.addEventListener 'touchmove', o.move, false
style.top = top d.addEventListener 'touchend', o.up, false
style.right = if left then null else '0px' d.addEventListener 'touchcancel', o.up, false
style.bottom = if top then null else '0px' else # mousedown
dragend: -> o.move = drag.bind o
localStorage.setItem "#{g.NAMESPACE}#{UI.el.id}.position", UI.el.style.cssText o.up = dragend.bind o
d.removeEventListener 'mousemove', UI.drag, false d.addEventListener 'mousemove', o.move, false
d.removeEventListener 'mouseup', UI.dragend, false d.addEventListener 'mouseup', o.up, false
delete UI.el touchmove = (e) ->
hover: (e) -> for touch in e.changedTouches
{clientX, clientY} = e if touch.identifier is @identifier
{style} = UI.el drag.call @, touch
{clientHeight, clientWidth} = d.documentElement return
height = UI.el.offsetHeight drag = (e) ->
left = e.clientX - @dx
top = e.clientY - @dy
if left < 10 then left = 0
else if @width - left < 10 then left = null
if top < 10 then top = 0
else if @height - top < 10 then top = null
if left is null
@style.left = null
@style.right = '0%'
else
@style.left = left / @screenWidth * 100 + '%'
@style.right = null
if top is null
@style.top = null
@style.bottom = '0%'
else
@style.top = top / @screenHeight * 100 + '%'
@style.bottom = null
dragend = (e) ->
if e.type is 'mouseup'
d.removeEventListener 'mousemove', @move, false
d.removeEventListener 'mouseup', @up, false
else # touchend or touchcancel
d.removeEventListener 'touchmove', @move, false
d.removeEventListener 'touchend', @up, false
d.removeEventListener 'touchcancel', @up, false
localStorage.setItem "#{g.NAMESPACE}#{@id}.position", @style.cssText
top = clientY - 120 hoverstart = (root, el, events, cb) ->
style.top = o = {
if clientHeight <= height or top <= 0 root: root
el: el
style: el.style
cb: cb
events: events.split ' '
clientHeight: d.documentElement.clientHeight
clientWidth: d.documentElement.clientWidth
}
o.hover = hover.bind o
o.hoverend = hoverend.bind o
for event in o.events
root.addEventListener event, o.hoverend, false
root.addEventListener 'mousemove', o.hover, false
hover = (e) ->
height = @el.offsetHeight
top = e.clientY - height / 2
@style.top =
if @clientHeight <= height or top <= 0
'0px' '0px'
else if top + height >= clientHeight else if top + height >= @clientHeight
clientHeight - height + 'px' @clientHeight - height + 'px'
else else
top + 'px' top + 'px'
if clientX <= clientWidth - 400 {clientX} = e
style.left = clientX + 45 + 'px' if clientX <= @clientWidth - 400
style.right = null @style.left = clientX + 45 + 'px'
@style.right = null
else else
style.left = null @style.left = null
style.right = clientWidth - clientX + 45 + 'px' @style.right = @clientWidth - clientX + 45 + 'px'
hoverend: -> hoverend = ->
$.rm UI.el @el.parentNode.removeChild @el
delete UI.el for event in @events
@root.removeEventListener event, @hoverend, false
@root.removeEventListener 'mousemove', @hover, false
@cb.call @ if @cb
return {
dialog: dialog
hover: hoverstart
}
)()
### ###
loosely follows the jquery api: loosely follows the jquery api:
@ -1790,21 +1847,16 @@ QuotePreview =
mouseover: (e) -> mouseover: (e) ->
return if $.hasClass @, 'inlined' return if $.hasClass @, 'inlined'
# Don't stop other elements from dragging
return if UI.el
{board, threadID, postID} = Get.postDataFromLink @ {board, threadID, postID} = Get.postDataFromLink @
qp = UI.el = $.el 'div', qp = $.el 'div',
id: 'qp' id: 'qp'
className: 'reply dialog' className: 'reply dialog'
UI.hover e
context = Get.postFromRoot $.x 'ancestor::div[parent::div[@class="thread"]][1]', @ context = Get.postFromRoot $.x 'ancestor::div[parent::div[@class="thread"]][1]', @
$.add d.body, qp $.add d.body, qp
Get.postClone board, threadID, postID, qp, context Get.postClone board, threadID, postID, qp, context
$.on @, 'mousemove', UI.hover UI.hover @, qp, 'mouseout click', QuotePreview.mouseout
$.on @, 'mouseout click', QuotePreview.mouseout
return unless origin = g.posts["#{board}.#{postID}"] return unless origin = g.posts["#{board}.#{postID}"]
@ -1824,11 +1876,8 @@ QuotePreview =
if quote.hash[2..] is quoterID if quote.hash[2..] is quoterID
$.addClass quote, 'forwardlink' $.addClass quote, 'forwardlink'
return return
mouseout: (e) -> mouseout: ->
root = UI.el.firstElementChild root = @el.firstElementChild
UI.hoverend()
$.off @, 'mousemove', UI.hover
$.off @, 'mouseout click', QuotePreview.mouseout
# Stop if it only contains text. # Stop if it only contains text.
return unless root return unless root
@ -2141,23 +2190,21 @@ ImageHover =
return unless @file?.isImage return unless @file?.isImage
$.on @file.thumb, 'mouseover', ImageHover.mouseover $.on @file.thumb, 'mouseover', ImageHover.mouseover
mouseover: -> mouseover: ->
# Don't stop other elements from dragging el = $.el 'img'
return if UI.el
el = UI.el = $.el 'img'
id: 'ihover' id: 'ihover'
src: @parentNode.href src: @parentNode.href
$.add d.body, el $.add d.body, el
$.on el, 'load', ImageHover.load $.on el, 'load', => ImageHover.load @, el
$.on el, 'error', ImageHover.error $.on el, 'error', ImageHover.error
$.on @, 'mousemove', UI.hover UI.hover @, el, 'mouseout'
$.on @, 'mouseout', ImageHover.mouseout load: (root, el) ->
load: -> return unless el.parentNode
return unless @parentNode
# 'Fake' mousemove event by giving required values. # 'Fake' mousemove event by giving required values.
{style} = @ {style} = el
UI.hover e = new Event 'mousemove'
clientX: - 45 + parseInt style.left e.clientX = - 45 + parseInt style.left
clientY: 120 + parseInt style.top e.clientY = 120 + parseInt style.top
root.dispatchEvent e
error: -> error: ->
return unless @parentNode return unless @parentNode
src = @src.split '/' src = @src.split '/'
@ -2171,10 +2218,6 @@ ImageHover =
return if $.engine isnt 'webkit' or url.split('/')[2] isnt 'images.4chan.org' return if $.engine isnt 'webkit' or url.split('/')[2] isnt 'images.4chan.org'
$.ajax url, onreadystatechange: (-> clearTimeout timeoutID if @status is 404), $.ajax url, onreadystatechange: (-> clearTimeout timeoutID if @status is 404),
type: 'head' type: 'head'
mouseout: ->
UI.hoverend()
$.off @, 'mousemove', UI.hover
$.off @, 'mouseout', ImageHover.mouseout
ThreadUpdater = ThreadUpdater =
init: -> init: ->