mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
Scroll error feedback into center of view for better UX
This commit is contained in:
parent
450c179183
commit
6b89f6f6c9
@ -1,26 +0,0 @@
|
||||
package io.onedev.server.web.asset.scrollintoview;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.wicket.Application;
|
||||
import org.apache.wicket.markup.head.HeaderItem;
|
||||
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
|
||||
import org.apache.wicket.request.resource.JavaScriptResourceReference;
|
||||
|
||||
public class ScrollIntoViewResourceReference extends JavaScriptResourceReference {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ScrollIntoViewResourceReference() {
|
||||
super(ScrollIntoViewResourceReference.class, "jquery.scrollintoview.js");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HeaderItem> getDependencies() {
|
||||
List<HeaderItem> dependencies = new ArrayList<>();
|
||||
dependencies.add(JavaScriptHeaderItem.forReference(Application.get().getJavaScriptLibrarySettings().getJQueryReference()));
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,208 +0,0 @@
|
||||
/*!
|
||||
* jQuery scrollintoview() plugin and :scrollable selector filter
|
||||
*
|
||||
* Version 1.8 (14 Jul 2011)
|
||||
* Requires jQuery 1.4 or newer
|
||||
*
|
||||
* Copyright (c) 2011 Robert Koritnik
|
||||
* Licensed under the terms of the MIT license
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var converter = {
|
||||
vertical: { x: false, y: true },
|
||||
horizontal: { x: true, y: false },
|
||||
both: { x: true, y: true },
|
||||
x: { x: true, y: false },
|
||||
y: { x: false, y: true }
|
||||
};
|
||||
|
||||
var settings = {
|
||||
duration: "fast",
|
||||
direction: "both"
|
||||
};
|
||||
|
||||
var rootrx = /^(?:html)$/i;
|
||||
|
||||
// gets border dimensions
|
||||
var borders = function (domElement, styles) {
|
||||
styles = styles || (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(domElement, null) : domElement.currentStyle);
|
||||
var px = document.defaultView && document.defaultView.getComputedStyle ? true : false;
|
||||
var b = {
|
||||
top: (parseFloat(px ? styles.borderTopWidth : $.css(domElement, "borderTopWidth")) || 0),
|
||||
left: (parseFloat(px ? styles.borderLeftWidth : $.css(domElement, "borderLeftWidth")) || 0),
|
||||
bottom: (parseFloat(px ? styles.borderBottomWidth : $.css(domElement, "borderBottomWidth")) || 0),
|
||||
right: (parseFloat(px ? styles.borderRightWidth : $.css(domElement, "borderRightWidth")) || 0)
|
||||
};
|
||||
return {
|
||||
top: b.top,
|
||||
left: b.left,
|
||||
bottom: b.bottom,
|
||||
right: b.right,
|
||||
vertical: b.top + b.bottom,
|
||||
horizontal: b.left + b.right
|
||||
};
|
||||
};
|
||||
|
||||
var dimensions = function ($element) {
|
||||
var win = $(window);
|
||||
var isRoot = rootrx.test($element[0].nodeName);
|
||||
return {
|
||||
border: isRoot ? { top: 0, left: 0, bottom: 0, right: 0} : borders($element[0]),
|
||||
scroll: {
|
||||
top: (isRoot ? win : $element).scrollTop(),
|
||||
left: (isRoot ? win : $element).scrollLeft()
|
||||
},
|
||||
scrollbar: {
|
||||
right: isRoot ? 0 : $element.innerWidth() - $element[0].clientWidth,
|
||||
bottom: isRoot ? 0 : $element.innerHeight() - $element[0].clientHeight
|
||||
},
|
||||
rect: (function () {
|
||||
var r = $element[0].getBoundingClientRect();
|
||||
return {
|
||||
top: isRoot ? 0 : r.top,
|
||||
left: isRoot ? 0 : r.left,
|
||||
bottom: isRoot ? $element[0].clientHeight : r.bottom,
|
||||
right: isRoot ? $element[0].clientWidth : r.right
|
||||
};
|
||||
})()
|
||||
};
|
||||
};
|
||||
|
||||
$.fn.extend({
|
||||
scrollIntoView: function (options) {
|
||||
/// <summary>Scrolls the first element in the set into view by scrolling its closest scrollable parent.</summary>
|
||||
/// <param name="options" type="Object">Additional options that can configure scrolling:
|
||||
/// duration (default: "fast") - jQuery animation speed (can be a duration string or number of milliseconds)
|
||||
/// direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both")
|
||||
/// complete (default: none) - a function to call when scrolling completes (called in context of the DOM element being scrolled)
|
||||
/// </param>
|
||||
/// <return type="jQuery">Returns the same jQuery set that this function was run on.</return>
|
||||
|
||||
options = $.extend({}, settings, options);
|
||||
options.direction = converter[typeof (options.direction) === "string" && options.direction.toLowerCase()] || converter.both;
|
||||
|
||||
var dirStr = "";
|
||||
if (options.direction.x === true) dirStr = "horizontal";
|
||||
if (options.direction.y === true) dirStr = dirStr ? "both" : "vertical";
|
||||
|
||||
var el = this.eq(0);
|
||||
var scroller = el.closest(":scrollable(" + dirStr + ")");
|
||||
|
||||
// check if there's anything to scroll in the first place
|
||||
if (scroller.length > 0)
|
||||
{
|
||||
scroller = scroller.eq(0);
|
||||
|
||||
var dim = {
|
||||
e: dimensions(el),
|
||||
s: dimensions(scroller)
|
||||
};
|
||||
|
||||
var rel = {
|
||||
top: dim.e.rect.top - (dim.s.rect.top + dim.s.border.top),
|
||||
bottom: dim.s.rect.bottom - dim.s.border.bottom - dim.s.scrollbar.bottom - dim.e.rect.bottom,
|
||||
left: dim.e.rect.left - (dim.s.rect.left + dim.s.border.left),
|
||||
right: dim.s.rect.right - dim.s.border.right - dim.s.scrollbar.right - dim.e.rect.right
|
||||
};
|
||||
|
||||
var animOptions = {};
|
||||
|
||||
// vertical scroll
|
||||
if (options.direction.y === true)
|
||||
{
|
||||
if (rel.top < 0)
|
||||
{
|
||||
animOptions.scrollTop = dim.s.scroll.top + rel.top;
|
||||
}
|
||||
else if (rel.top > 0 && rel.bottom < 0)
|
||||
{
|
||||
animOptions.scrollTop = dim.s.scroll.top + Math.min(rel.top, -rel.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
// horizontal scroll
|
||||
if (options.direction.x === true)
|
||||
{
|
||||
if (rel.left < 0)
|
||||
{
|
||||
animOptions.scrollLeft = dim.s.scroll.left + rel.left;
|
||||
}
|
||||
else if (rel.left > 0 && rel.right < 0)
|
||||
{
|
||||
animOptions.scrollLeft = dim.s.scroll.left + Math.min(rel.left, -rel.right);
|
||||
}
|
||||
}
|
||||
|
||||
// scroll if needed
|
||||
if (!$.isEmptyObject(animOptions))
|
||||
{
|
||||
if (rootrx.test(scroller[0].nodeName))
|
||||
{
|
||||
scroller = $("html,body");
|
||||
}
|
||||
scroller
|
||||
.animate(animOptions, options.duration)
|
||||
.eq(0) // we want function to be called just once (ref. "html,body")
|
||||
.queue(function (next) {
|
||||
$.isFunction(options.complete) && options.complete.call(scroller[0]);
|
||||
next();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// when there's nothing to scroll, just call the "complete" function
|
||||
$.isFunction(options.complete) && options.complete.call(scroller[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// return set back
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
var scrollValue = {
|
||||
auto: true,
|
||||
scroll: true,
|
||||
visible: false,
|
||||
hidden: false
|
||||
};
|
||||
|
||||
$.extend($.expr[":"], {
|
||||
scrollable: function (element, index, meta, stack) {
|
||||
var direction = converter[typeof (meta[3]) === "string" && meta[3].toLowerCase()] || converter.both;
|
||||
var styles = (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(element, null) : element.currentStyle);
|
||||
var overflow = {
|
||||
x: scrollValue[styles.overflowX.toLowerCase()] || false,
|
||||
y: scrollValue[styles.overflowY.toLowerCase()] || false,
|
||||
isRoot: rootrx.test(element.nodeName)
|
||||
};
|
||||
|
||||
// check if completely unscrollable (exclude HTML element because it's special)
|
||||
if (!overflow.x && !overflow.y && !overflow.isRoot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var size = {
|
||||
height: {
|
||||
scroll: element.scrollHeight,
|
||||
client: element.clientHeight
|
||||
},
|
||||
width: {
|
||||
scroll: element.scrollWidth,
|
||||
client: element.clientWidth
|
||||
},
|
||||
// check overflow.x/y because iPad (and possibly other tablets) don't dislay scrollbars
|
||||
scrollableX: function () {
|
||||
return (overflow.x || overflow.isRoot) && this.width.scroll > this.width.client;
|
||||
},
|
||||
scrollableY: function () {
|
||||
return (overflow.y || overflow.isRoot) && this.height.scroll > this.height.client;
|
||||
}
|
||||
};
|
||||
return direction.y && size.scrollableY() || direction.x && size.scrollableX();
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
@ -13,7 +13,6 @@ import io.onedev.server.web.asset.align.AlignResourceReference;
|
||||
import io.onedev.server.web.asset.autosize.AutoSizeResourceReference;
|
||||
import io.onedev.server.web.asset.cookies.CookiesResourceReference;
|
||||
import io.onedev.server.web.asset.perfectscrollbar.PerfectScrollbarResourceReference;
|
||||
import io.onedev.server.web.asset.scrollintoview.ScrollIntoViewResourceReference;
|
||||
|
||||
public class BaseResourceReference extends JavaScriptResourceReference {
|
||||
|
||||
@ -33,7 +32,6 @@ public class BaseResourceReference extends JavaScriptResourceReference {
|
||||
dependencies.add(JavaScriptHeaderItem.forReference(new AutoSizeResourceReference()));
|
||||
dependencies.add(JavaScriptHeaderItem.forReference(new PerfectScrollbarResourceReference()));
|
||||
dependencies.add(JavaScriptHeaderItem.forReference(new CookiesResourceReference()));
|
||||
dependencies.add(JavaScriptHeaderItem.forReference(new ScrollIntoViewResourceReference()));
|
||||
|
||||
dependencies.add(CssHeaderItem.forReference(new BaseCssResourceReference()));
|
||||
|
||||
|
||||
@ -290,18 +290,22 @@ onedev.server = {
|
||||
setTimeout(function() {
|
||||
var focusibleSelector = "input[type=text]:visible, input:not([type]):visible, textarea:visible, .CodeMirror:visible";
|
||||
|
||||
var inErrorSelector = ".feedbackPanelERROR:visible:first";
|
||||
var inErrorSelector = ".has-error:visible:first";
|
||||
var $inError = $containers.find(inErrorSelector).addBack(inErrorSelector);
|
||||
if ($inError.length == 0) {
|
||||
inErrorSelector = ".feedbackPanelERROR:visible:first";
|
||||
$inError = $containers.find(inErrorSelector).addBack(inErrorSelector);
|
||||
}
|
||||
if ($inError.length != 0) {
|
||||
var $focusable = $inError.find(focusibleSelector).addBack(focusibleSelector);
|
||||
if ($focusable.hasClass("CodeMirror") && $focusable[0].CodeMirror.options.readOnly == false) {
|
||||
if ($focusable.hasClass("CodeMirror") && $this[0].CodeMirror.options.readOnly == false) {
|
||||
$focusable[0].CodeMirror.focus();
|
||||
} else if ($focusable.length != 0
|
||||
&& $focusable.closest(".select2-container").length == 0
|
||||
&& !$focusable.hasClass("input-assist")) {
|
||||
$focusable.focus();
|
||||
} else {
|
||||
$inError.scrollIntoView();
|
||||
$inError[0].scrollIntoView({behavior: "smooth", block: "center"});
|
||||
}
|
||||
} else {
|
||||
$containers.find(focusibleSelector).addBack(focusibleSelector).each(function() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user