diff --git a/octoprint/static/css/bootstrap-modal.css b/octoprint/static/css/bootstrap-modal.css new file mode 100644 index 0000000..76e3be2 --- /dev/null +++ b/octoprint/static/css/bootstrap-modal.css @@ -0,0 +1,214 @@ +/*! + * Bootstrap Modal + * + * Copyright Jordan Schroter + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */ + +.modal-open { + overflow: hidden; +} + + +/* add a scroll bar to stop page from jerking around */ +.modal-open.page-overflow .page-container, +.modal-open.page-overflow .page-container .navbar-fixed-top, +.modal-open.page-overflow .page-container .navbar-fixed-bottom, +.modal-open.page-overflow .modal-scrollable { + overflow-y: scroll; +} + +@media (max-width: 979px) { + .modal-open.page-overflow .page-container .navbar-fixed-top, + .modal-open.page-overflow .page-container .navbar-fixed-bottom { + overflow-y: visible; + } +} + + +.modal-scrollable { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow: auto; +} + +.modal { + outline: none; + position: absolute; + margin-top: 0; + top: 50%; + overflow: visible; /* allow content to popup out (i.e tooltips) */ +} + +.modal.fade { + top: -100%; + -webkit-transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out; + -moz-transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out; + -o-transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out; + transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out; +} + +.modal.fade.in { + top: 50%; +} + +.modal-body { + max-height: none; + overflow: visible; +} + +.modal.modal-absolute { + position: absolute; + z-index: 950; +} + +.modal .loading-mask { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: #fff; + border-radius: 6px; +} + +.modal-backdrop.modal-absolute{ + position: absolute; + z-index: 940; +} + +.modal-backdrop, +.modal-backdrop.fade.in{ + opacity: 0.7; + filter: alpha(opacity=70); + background: #fff; +} + +.modal.container { + width: 940px; + margin-left: -470px; +} + +/* Modal Overflow */ + +.modal-overflow.modal { + top: 1%; +} + +.modal-overflow.modal.fade { + top: -100%; +} + +.modal-overflow.modal.fade.in { + top: 1%; +} + +.modal-overflow .modal-body { + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +/* Responsive */ + +@media (min-width: 1200px) { + .modal.container { + width: 1170px; + margin-left: -585px; + } +} + +@media (max-width: 979px) { + .modal, + .modal.container, + .modal.modal-overflow { + top: 1%; + right: 1%; + left: 1%; + bottom: auto; + width: auto !important; + height: auto !important; + margin: 0 !important; + padding: 0 !important; + } + + .modal.fade.in, + .modal.container.fade.in, + .modal.modal-overflow.fade.in { + top: 1%; + bottom: auto; + } + + .modal-body, + .modal-overflow .modal-body { + position: static; + margin: 0; + height: auto !important; + max-height: none !important; + overflow: visible !important; + } + + .modal-footer, + .modal-overflow .modal-footer { + position: static; + } +} + +.loading-spinner { + position: absolute; + top: 50%; + left: 50%; + margin: -12px 0 0 -12px; +} + +/* +Animate.css - http://daneden.me/animate +Licensed under the ☺ license (http://licence.visualidiot.com/) + +Copyright (c) 2012 Dan Eden*/ + +.animated { + -webkit-animation-duration: 1s; + -moz-animation-duration: 1s; + -o-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + -moz-animation-fill-mode: both; + -o-animation-fill-mode: both; + animation-fill-mode: both; +} + +@-webkit-keyframes shake { + 0%, 100% {-webkit-transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {-webkit-transform: translateX(-10px);} + 20%, 40%, 60%, 80% {-webkit-transform: translateX(10px);} +} + +@-moz-keyframes shake { + 0%, 100% {-moz-transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {-moz-transform: translateX(-10px);} + 20%, 40%, 60%, 80% {-moz-transform: translateX(10px);} +} + +@-o-keyframes shake { + 0%, 100% {-o-transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {-o-transform: translateX(-10px);} + 20%, 40%, 60%, 80% {-o-transform: translateX(10px);} +} + +@keyframes shake { + 0%, 100% {transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {transform: translateX(-10px);} + 20%, 40%, 60%, 80% {transform: translateX(10px);} +} + +.shake { + -webkit-animation-name: shake; + -moz-animation-name: shake; + -o-animation-name: shake; + animation-name: shake; +} diff --git a/octoprint/static/js/bootstrap/bootstrap-modal.js b/octoprint/static/js/bootstrap/bootstrap-modal.js new file mode 100644 index 0000000..c125bd5 --- /dev/null +++ b/octoprint/static/js/bootstrap/bootstrap-modal.js @@ -0,0 +1,374 @@ +/* =========================================================== + * bootstrap-modal.js v2.1 + * =========================================================== + * Copyright 2012 Jordan Schroter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + /* MODAL CLASS DEFINITION + * ====================== */ + + var Modal = function (element, options) { + this.init(element, options); + }; + + Modal.prototype = { + + constructor: Modal, + + init: function (element, options) { + this.options = options; + + this.$element = $(element) + .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)); + + this.options.remote && this.$element.find('.modal-body').load(this.options.remote); + + var manager = typeof this.options.manager === 'function' ? + this.options.manager.call(this) : this.options.manager; + + manager = manager.appendModal ? + manager : $(manager).modalmanager().data('modalmanager'); + + manager.appendModal(this); + }, + + toggle: function () { + return this[!this.isShown ? 'show' : 'hide'](); + }, + + show: function () { + var e = $.Event('show'); + + if (this.isShown) return; + + this.$element.trigger(e); + + if (e.isDefaultPrevented()) return; + + this.escape(); + + this.tab(); + + this.options.loading && this.loading(); + }, + + hide: function (e) { + e && e.preventDefault(); + + e = $.Event('hide'); + + this.$element.trigger(e); + + if (!this.isShown || e.isDefaultPrevented()) return (this.isShown = false); + + this.isShown = false; + + this.escape(); + + this.tab(); + + this.isLoading && this.loading(); + + $(document).off('focusin.modal'); + + this.$element + .removeClass('in') + .removeClass('animated') + .removeClass(this.options.attentionAnimation) + .removeClass('modal-overflow') + .attr('aria-hidden', true); + + $.support.transition && this.$element.hasClass('fade') ? + this.hideWithTransition() : + this.hideModal(); + }, + + layout: function () { + var prop = this.options.height ? 'height' : 'max-height', + value = this.options.height || this.options.maxHeight; + + if (this.options.width){ + this.$element.css('width', this.options.width); + + var that = this; + this.$element.css('margin-left', function () { + if (/%/ig.test(that.options.width)){ + return -(parseInt(that.options.width) / 2) + '%'; + } else { + return -($(this).width() / 2) + 'px'; + } + }); + } else { + this.$element.css('width', ''); + this.$element.css('margin-left', ''); + } + + this.$element.find('.modal-body') + .css('overflow', '') + .css(prop, ''); + + var modalOverflow = $(window).height() - 10 < this.$element.height(); + + if (value){ + this.$element.find('.modal-body') + .css('overflow', 'auto') + .css(prop, value); + } + + if (modalOverflow || this.options.modalOverflow) { + this.$element + .css('margin-top', 0) + .addClass('modal-overflow'); + } else { + this.$element + .css('margin-top', 0 - this.$element.height() / 2) + .removeClass('modal-overflow'); + } + }, + + tab: function () { + var that = this; + + if (this.isShown && this.options.consumeTab) { + this.$element.on('keydown.tabindex.modal', '[data-tabindex]', function (e) { + if (e.keyCode && e.keyCode == 9){ + var $next = $(this), + $rollover = $(this); + + that.$element.find('[data-tabindex]:enabled:not([readonly])').each(function (e) { + if (!e.shiftKey){ + $next = $next.data('tabindex') < $(this).data('tabindex') ? + $next = $(this) : + $rollover = $(this); + } else { + $next = $next.data('tabindex') > $(this).data('tabindex') ? + $next = $(this) : + $rollover = $(this); + } + }); + + $next[0] !== $(this)[0] ? + $next.focus() : $rollover.focus(); + + e.preventDefault(); + } + }); + } else if (!this.isShown) { + this.$element.off('keydown.tabindex.modal'); + } + }, + + escape: function () { + var that = this; + if (this.isShown && this.options.keyboard) { + if (!this.$element.attr('tabindex')) this.$element.attr('tabindex', -1); + + this.$element.on('keyup.dismiss.modal', function (e) { + e.which == 27 && that.hide(); + }); + } else if (!this.isShown) { + this.$element.off('keyup.dismiss.modal') + } + }, + + hideWithTransition: function () { + var that = this + , timeout = setTimeout(function () { + that.$element.off($.support.transition.end); + that.hideModal(); + }, 500); + + this.$element.one($.support.transition.end, function () { + clearTimeout(timeout); + that.hideModal(); + }); + }, + + hideModal: function () { + this.$element + .hide() + .trigger('hidden'); + + var prop = this.options.height ? 'height' : 'max-height'; + var value = this.options.height || this.options.maxHeight; + + if (value){ + this.$element.find('.modal-body') + .css('overflow', '') + .css(prop, ''); + } + + }, + + removeLoading: function () { + this.$loading.remove(); + this.$loading = null; + this.isLoading = false; + }, + + loading: function (callback) { + callback = callback || function () {}; + + var animate = this.$element.hasClass('fade') ? 'fade' : ''; + + if (!this.isLoading) { + var doAnimate = $.support.transition && animate; + + this.$loading = $('
') + .append(this.options.spinner) + .appendTo(this.$element); + + if (doAnimate) this.$loading[0].offsetWidth; // force reflow + + this.$loading.addClass('in'); + + this.isLoading = true; + + doAnimate ? + this.$loading.one($.support.transition.end, callback) : + callback(); + + } else if (this.isLoading && this.$loading) { + this.$loading.removeClass('in'); + + var that = this; + $.support.transition && this.$element.hasClass('fade')? + this.$loading.one($.support.transition.end, function () { that.removeLoading() }) : + that.removeLoading(); + + } else if (callback) { + callback(this.isLoading); + } + }, + + focus: function () { + var $focusElem = this.$element.find(this.options.focusOn); + + $focusElem = $focusElem.length ? $focusElem : this.$element; + + $focusElem.focus(); + }, + + attention: function (){ + // NOTE: transitionEnd with keyframes causes odd behaviour + + if (this.options.attentionAnimation){ + this.$element + .removeClass('animated') + .removeClass(this.options.attentionAnimation); + + var that = this; + + setTimeout(function () { + that.$element + .addClass('animated') + .addClass(that.options.attentionAnimation); + }, 0); + } + + + this.focus(); + }, + + + destroy: function () { + var e = $.Event('destroy'); + this.$element.trigger(e); + if (e.isDefaultPrevented()) return; + + this.teardown(); + }, + + teardown: function () { + if (!this.$parent.length){ + this.$element.remove(); + this.$element = null; + return; + } + + if (this.$parent !== this.$element.parent()){ + this.$element.appendTo(this.$parent); + } + + this.$element.off('.modal'); + this.$element.removeData('modal'); + this.$element + .removeClass('in') + .attr('aria-hidden', true); + } + }; + + + /* MODAL PLUGIN DEFINITION + * ======================= */ + + $.fn.modal = function (option, args) { + return this.each(function () { + var $this = $(this), + data = $this.data('modal'), + options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option); + + if (!data) $this.data('modal', (data = new Modal(this, options))); + if (typeof option == 'string') data[option].apply(data, [].concat(args)); + else if (options.show) data.show() + }) + }; + + $.fn.modal.defaults = { + keyboard: true, + backdrop: true, + loading: false, + show: true, + width: null, + height: null, + maxHeight: null, + modalOverflow: false, + consumeTab: true, + focusOn: null, + replace: false, + resize: false, + attentionAnimation: 'shake', + manager: 'body', + spinner: '
' + }; + + $.fn.modal.Constructor = Modal; + + + /* MODAL DATA-API + * ============== */ + + $(function () { + $(document).off('click.modal').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) { + var $this = $(this), + href = $this.attr('href'), + $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))), //strip for ie7 + option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()); + + e.preventDefault(); + $target + .modal(option) + .one('hide', function () { + $this.focus(); + }) + }); + }); + +}(window.jQuery); diff --git a/octoprint/static/js/bootstrap/bootstrap-modalmanager.js b/octoprint/static/js/bootstrap/bootstrap-modalmanager.js new file mode 100644 index 0000000..1982975 --- /dev/null +++ b/octoprint/static/js/bootstrap/bootstrap-modalmanager.js @@ -0,0 +1,412 @@ +/* =========================================================== + * bootstrap-modalmanager.js v2.1 + * =========================================================== + * Copyright 2012 Jordan Schroter. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + +!function ($) { + + "use strict"; // jshint ;_; + + /* MODAL MANAGER CLASS DEFINITION + * ====================== */ + + var ModalManager = function (element, options) { + this.init(element, options); + }; + + ModalManager.prototype = { + + constructor: ModalManager, + + init: function (element, options) { + this.$element = $(element); + this.options = $.extend({}, $.fn.modalmanager.defaults, this.$element.data(), typeof options == 'object' && options); + this.stack = []; + this.backdropCount = 0; + + if (this.options.resize) { + var resizeTimeout, + that = this; + + $(window).on('resize.modal', function(){ + resizeTimeout && clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(function(){ + for (var i = 0; i < that.stack.length; i++){ + that.stack[i].isShown && that.stack[i].layout(); + } + }, 10); + }); + } + }, + + createModal: function (element, options) { + $(element).modal($.extend({ manager: this }, options)); + }, + + appendModal: function (modal) { + this.stack.push(modal); + + var that = this; + + modal.$element.on('show.modalmanager', targetIsSelf(function (e) { + + var showModal = function(){ + modal.isShown = true; + + var transition = $.support.transition && modal.$element.hasClass('fade'); + + that.$element + .toggleClass('modal-open', that.hasOpenModal()) + .toggleClass('page-overflow', $(window).height() < that.$element.height()); + + modal.$parent = modal.$element.parent(); + + modal.$container = that.createContainer(modal); + + modal.$element.appendTo(modal.$container); + + that.backdrop(modal, function () { + + modal.$element.show(); + + if (transition) { + //modal.$element[0].style.display = 'run-in'; + modal.$element[0].offsetWidth; + //modal.$element.one($.support.transition.end, function () { modal.$element[0].style.display = 'block' }); + } + + modal.layout(); + + modal.$element + .addClass('in') + .attr('aria-hidden', false); + + var complete = function () { + that.setFocus(); + modal.$element.trigger('shown'); + }; + + transition ? + modal.$element.one($.support.transition.end, complete) : + complete(); + }); + }; + + modal.options.replace ? + that.replace(showModal) : + showModal(); + })); + + modal.$element.on('hidden.modalmanager', targetIsSelf(function (e) { + + that.backdrop(modal); + + if (modal.$backdrop){ + $.support.transition && modal.$element.hasClass('fade') ? + modal.$backdrop.one($.support.transition.end, function () { that.destroyModal(modal) }) : + that.destroyModal(modal); + } else { + that.destroyModal(modal); + } + + })); + + modal.$element.on('destroy.modalmanager', targetIsSelf(function (e) { + that.removeModal(modal); + })); + + }, + + destroyModal: function (modal) { + + modal.destroy(); + + var hasOpenModal = this.hasOpenModal(); + + this.$element.toggleClass('modal-open', hasOpenModal); + + if (!hasOpenModal){ + this.$element.removeClass('page-overflow'); + } + + this.removeContainer(modal); + + this.setFocus(); + }, + + hasOpenModal: function () { + for (var i = 0; i < this.stack.length; i++){ + if (this.stack[i].isShown) return true; + } + + return false; + }, + + setFocus: function () { + var topModal; + + for (var i = 0; i < this.stack.length; i++){ + if (this.stack[i].isShown) topModal = this.stack[i]; + } + + if (!topModal) return; + + topModal.focus(); + + }, + + removeModal: function (modal) { + modal.$element.off('.modalmanager'); + if (modal.$backdrop) this.removeBackdrop.call(modal); + this.stack.splice(this.getIndexOfModal(modal), 1); + }, + + getModalAt: function (index) { + return this.stack[index]; + }, + + getIndexOfModal: function (modal) { + for (var i = 0; i < this.stack.length; i++){ + if (modal === this.stack[i]) return i; + } + }, + + replace: function (callback) { + var topModal; + + for (var i = 0; i < this.stack.length; i++){ + if (this.stack[i].isShown) topModal = this.stack[i]; + } + + if (topModal) { + this.$backdropHandle = topModal.$backdrop; + topModal.$backdrop = null; + + callback && topModal.$element.one('hidden', + targetIsSelf( $.proxy(callback, this) )); + + topModal.hide(); + } else if (callback) { + callback(); + } + }, + + removeBackdrop: function (modal) { + modal.$backdrop.remove(); + modal.$backdrop = null; + }, + + createBackdrop: function (animate) { + var $backdrop; + + if (!this.$backdropHandle) { + $backdrop = $('