define([ 'jquery', '/customize/messages.js', '/common/common-util.js', '/customize/application_config.js', '/bower_components/alertifyjs/dist/js/alertify.js', '/common/notify.js', '/common/visible.js', '/common/tippy.min.js', '/customize/pages.js', '/common/hyperscript.js', '/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js', 'css!/common/tippy.css', ], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible, Tippy, Pages, h) { var UI = {}; /* * Alertifyjs */ UI.Alertify = Alertify; // set notification timeout Alertify._$$alertify.delay = AppConfig.notificationTimeout || 5000; var findCancelButton = UI.findCancelButton = function (root) { if (root) { return $(root).find('button.cancel').last(); } return $('button.cancel').last(); }; var findOKButton = UI.findOKButton = function (root) { if (root) { return $(root).find('button.ok').last(); } return $('button.ok').last(); }; var listenForKeys = UI.listenForKeys = function (yes, no) { var handler = function (e) { switch (e.which) { case 27: // cancel if (typeof(no) === 'function') { no(e); } break; case 13: // enter if (typeof(yes) === 'function') { yes(e); } break; } }; $(window).keyup(handler); return handler; }; var stopListening = UI.stopListening = function (handler) { $(window).off('keyup', handler); }; var dialog = UI.dialog = {}; var merge = function (a, b) { var c = {}; if (a) { Object.keys(a).forEach(function (k) { c[k] = a[k]; }); } if (b) { Object.keys(b).forEach(function (k) { c[k] = b[k]; }); } return c; }; dialog.selectable = function (value, opt) { var attrs = merge({ type: 'text', readonly: 'readonly', }, opt); var input = h('input', attrs); $(input).val(value).click(function () { input.select(); }); return input; }; dialog.okButton = function (content) { return h('button.ok', { tabindex: '2', }, content || Messages.okButton); }; dialog.cancelButton = function (content) { return h('button.cancel', { tabindex: '1'}, content || Messages.cancelButton); }; dialog.message = function (text) { return h('p.msg', text); }; dialog.textInput = function (opt) { var attrs = merge({ type: 'text', 'class': 'cp-text-input', }, opt); return h('input', attrs); }; dialog.nav = function (content) { return h('nav', content || [ dialog.cancelButton(), dialog.okButton(), ]); }; dialog.frame = function (content) { return h('div.alertify', [ h('div.dialog', [ h('div', content), ]) ]); }; UI.tokenField = function (target) { var t = { element: target || h('input'), }; var $t = t.tokenfield = $(t.element).tokenfield(); t.getTokens = function () { return $t.tokenfield('getTokens').map(function (token) { return token.value; }); }; t.preventDuplicates = function (cb) { $t.on('tokenfield:createtoken', function (ev) { var val; if (t.getTokens().some(function (t) { if (t === ev.attrs.value) { return ((val = t)); } })) { ev.preventDefault(); if (typeof(cb) === 'function') { cb(val); } } }); return t; }; t.setTokens = function (tokens) { $t.tokenfield('setTokens', tokens.map(function (token) { return { value: token, label: token, }; })); }; t.focus = function () { var $temp = $t.closest('.tokenfield').find('.token-input'); $temp.css('width', '20%'); $t.tokenfield('focusInput', $temp[0]); }; return t; }; dialog.tagPrompt = function (tags, cb) { var input = dialog.textInput(); var tagger = dialog.frame([ dialog.message('make some tags'), // TODO translate input, dialog.nav(), ]); var field = UI.tokenField(input).preventDuplicates(function (val) { UI.warn('Duplicate tag: ' + val); // TODO translate }); var close = Util.once(function () { var $t = $(tagger).fadeOut(150, function () { $t.remove(); }); }); var listener = listenForKeys(function () {}, function () { close(); stopListening(listener); }); var CB = Util.once(cb); findOKButton(tagger).click(function () { var tokens = field.getTokens(); close(); CB(tokens); }); findCancelButton(tagger).click(function () { close(); CB(null); }); // :( setTimeout(function () { field.setTokens(tags); field.focus(); }); return tagger; }; UI.alert = function (msg, cb, force) { cb = cb || function () {}; var message; if (typeof(msg) === 'string') { // sanitize if (!force) { msg = Util.fixHTML(msg); } message = dialog.message(); message.innerHTML = msg; } else { message = dialog.message(msg); } var ok = dialog.okButton(); var frame = dialog.frame([ message, dialog.nav(ok), ]); var listener; var close = Util.once(function () { $(frame).fadeOut(150, function () { $(this).remove(); }); stopListening(listener); }); listener = listenForKeys(close, close); var $ok = $(ok).click(close); document.body.appendChild(frame); setTimeout(function () { $ok.focus(); UI.notify(); }); }; UI.prompt = function (msg, def, cb, opt, force) { cb = cb || function () {}; opt = opt || {}; var input = dialog.textInput(); input.value = typeof(def) === 'string'? def: ''; var message; if (typeof(msg) === 'string') { if (!force) { msg = Util.fixHTML(msg); } message = dialog.message(); message.innerHTML = msg; } else { message = dialog.message(msg); } var ok = dialog.okButton(opt.ok); var cancel = dialog.cancelButton(opt.cancel); var frame = dialog.frame([ message, input, dialog.nav([ cancel, ok, ]), ]); var listener; var close = Util.once(function () { $(frame).fadeOut(150, function () { $(this).remove(); }); stopListening(listener); }); var $ok = $(ok).click(function (ev) { cb(input.value, ev); }); var $cancel = $(cancel).click(function (ev) { cb(null, ev); }); listener = listenForKeys(function () { // yes close(); $ok.click(); }, function () { // no close(); $cancel.click(); }); document.body.appendChild(frame); setTimeout(function () { input.select().focus(); UI.notify(); }); }; UI.confirm = function (msg, cb, opt, force, styleCB) { cb = cb || function () {}; opt = opt || {}; var message; if (typeof(msg) === 'string') { if (!force) { msg = Util.fixHTML(msg); } message = dialog.message(); message.innerHTML = msg; } else { message = dialog.message(msg); } var ok = dialog.okButton(opt.ok); var cancel = dialog.cancelButton(opt.cancel); var frame = dialog.frame([ message, dialog.nav(opt.reverseOrder? [ok, cancel]: [cancel, ok]), ]); var listener; var close = Util.once(function () { $(frame).fadeOut(150, function () { $(this).remove(); }); stopListening(listener); }); var $ok = $(ok).click(function (ev) { close(); cb(true, ev); }); var $cancel = $(cancel).click(function (ev) { close(); cb(false, ev); }); if (opt.cancelClass) { $cancel.addClass(opt.cancelClass); } if (opt.okClass) { $ok.addClass(opt.okClass); } listener = listenForKeys(function () { $ok.click(); }, function () { $cancel.click(); }); document.body.appendChild(frame); setTimeout(function () { UI.notify(); if (typeof(styleCB) === 'function') { styleCB($ok.closest('.dialog')); } }); }; UI.log = function (msg) { Alertify.success(Util.fixHTML(msg)); }; UI.warn = function (msg) { Alertify.error(Util.fixHTML(msg)); }; /* * spinner */ UI.spinner = function (parent) { var $target = $('', { 'class': 'fa fa-circle-o-notch fa-spin fa-4x fa-fw', }).hide(); $(parent).append($target); return { show: function () { $target.css('display', 'inline'); return this; }, hide: function () { $target.hide(); return this; }, get: function () { return $target; }, }; }; var LOADING = 'loading'; var getRandomTip = function () { if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; } var keys = Object.keys(Messages.tips); var rdm = Math.floor(Math.random() * keys.length); return Messages.tips[keys[rdm]]; }; UI.addLoadingScreen = function (config) { config = config || {}; var loadingText = config.loadingText; var hideTips = config.hideTips; var hideLogo = config.hideLogo; var $loading, $container; if ($('#' + LOADING).length) { $loading = $('#' + LOADING).show(); if (loadingText) { $('#' + LOADING).find('p').text(loadingText); } $container = $loading.find('.loadingContainer'); } else { $loading = $(Pages.loadingScreen()); $container = $loading.find('.loadingContainer'); if (hideLogo) { $loading.find('img').hide(); } else { $loading.find('img').show(); } var $spinner = $loading.find('.spinnerContainer'); $spinner.show(); $('body').append($loading); } if (Messages.tips && !hideTips) { var $loadingTip = $('
', {'id': 'loadingTip'}); $('', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip); $loadingTip.css({ 'bottom': $('body').height()/2 - $container.height()/2 + 20 + 'px' }); $('body').append($loadingTip); } }; UI.removeLoadingScreen = function (cb) { $('#' + LOADING).fadeOut(750, cb); var $tip = $('#loadingTip').css('top', '') // loading.less sets transition-delay: $wait-time // and transition: opacity $fadeout-time .css({ 'opacity': 0, 'pointer-events': 'none', }); setTimeout(function () { $tip.remove(); }, 3750); // jquery.fadeout can get stuck }; UI.errorLoadingScreen = function (error, transparent) { if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen({hideTips: true}); } $('.spinnerContainer').hide(); if (transparent) { $('#' + LOADING).css('opacity', 0.8); } $('#' + LOADING).find('p').html(error || Messages.error); }; // Notify var notify = {}; UI.unnotify = function () { if (notify.tabNotification && typeof(notify.tabNotification.cancel) === 'function') { notify.tabNotification.cancel(); } }; UI.notify = function () { if (Visible.isSupported() && !Visible.currently()) { UI.unnotify(); notify.tabNotification = Notify.tab(1000, 10); } }; if (Visible.isSupported()) { Visible.onChange(function (yes) { if (yes) { UI.unnotify(); } }); } UI.importContent = function (type, f, cfg) { return function () { var $files = $('', {type:"file"}); if (cfg && cfg.accept) { $files.attr('accept', cfg.accept); } $files.click(); $files.on('change', function (e) { var file = e.target.files[0]; var reader = new FileReader(); reader.onload = function (e) { f(e.target.result, file); }; reader.readAsText(file, type); }); }; }; var $defaultIcon = $('', {"class": "fa fa-file-text-o"}); UI.getIcon = function (type) { var $icon = $defaultIcon.clone(); if (AppConfig.applicationsIcon && AppConfig.applicationsIcon[type]) { var appClass = ' cp-icon-color-'+type; $icon = $('', {'class': 'fa ' + AppConfig.applicationsIcon[type] + appClass}); } return $icon; }; // Tooltips UI.clearTooltips = function () { // If an element is removed from the UI while a tooltip is applied on that element, the tooltip will get hung // forever, this is a solution which just searches for tooltips which have no corrisponding element and removes // them. var win; $('.tippy-popper').each(function (i, el) { win = win || $('#pad-iframe')[0].contentWindow; if (win.$('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) { el.remove(); } }); }; UI.addTooltips = function () { var MutationObserver = window.MutationObserver; var addTippy = function (el) { if (el.nodeName === 'IFRAME') { return; } var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500; Tippy(el, { position: 'bottom', distance: 0, performance: true, dynamicTitle: true, delay: [delay, 0] }); }; var $body = $('body'); var $padIframe = $('#pad-iframe').contents().find('body'); $('[title]').each(function (i, el) { addTippy(el); }); $('#pad-iframe').contents().find('[title]').each(function (i, el) { addTippy(el); }); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList' && mutation.addedNodes.length) { $body.find('[title]').each(function (i, el) { addTippy(el); }); if (!$padIframe.length) { return; } $padIframe.find('[title]').each(function (i, el) { addTippy(el); }); } }); }); observer.observe($('body')[0], { attributes: false, childList: true, characterData: false, subtree: true }); if ($('#pad-iframe').length) { observer.observe($('#pad-iframe').contents().find('body')[0], { attributes: false, childList: true, characterData: false, subtree: true }); } }; return UI; });