641 lines
26 KiB
JavaScript
641 lines
26 KiB
JavaScript
$(function() {
|
|
//~~ Initialize i18n
|
|
var catalog = window["BABEL_TO_LOAD_" + LOCALE];
|
|
if (catalog === undefined) {
|
|
catalog = {messages: undefined, plural_expr: undefined, locale: undefined, domain: undefined}
|
|
}
|
|
babel.Translations.load(catalog).install();
|
|
|
|
moment.locale(LOCALE);
|
|
|
|
// Dummy translation requests for dynamic strings supplied by the backend
|
|
var dummyTranslations = [
|
|
// printer states
|
|
gettext("Offline"),
|
|
gettext("Opening serial port"),
|
|
gettext("Detecting serial port"),
|
|
gettext("Detecting baudrate"),
|
|
gettext("Connecting"),
|
|
gettext("Operational"),
|
|
gettext("Printing from SD"),
|
|
gettext("Sending file to SD"),
|
|
gettext("Printing"),
|
|
gettext("Paused"),
|
|
gettext("Closed"),
|
|
gettext("Transfering file to SD")
|
|
];
|
|
|
|
PNotify.prototype.options.styling = "bootstrap2";
|
|
|
|
// work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at
|
|
// http://stackoverflow.com/questions/12506897/is-safari-on-ios-6-caching-ajax-results
|
|
$.ajaxSetup({
|
|
type: 'POST',
|
|
headers: { "cache-control": "no-cache" }
|
|
});
|
|
|
|
// send the current UI API key with any request
|
|
$.ajaxSetup({
|
|
headers: {"X-Api-Key": UI_API_KEY}
|
|
});
|
|
|
|
//~~ Show settings - to ensure centered
|
|
var settingsDialog = $('#settings_dialog');
|
|
settingsDialog.on('show', function() {
|
|
_.each(allViewModels, function(viewModel) {
|
|
if (viewModel.hasOwnProperty("onSettingsShown")) {
|
|
viewModel.onSettingsShown();
|
|
}
|
|
});
|
|
});
|
|
settingsDialog.on('hidden', function() {
|
|
_.each(allViewModels, function(viewModel) {
|
|
if (viewModel.hasOwnProperty("onSettingsHidden")) {
|
|
viewModel.onSettingsHidden();
|
|
}
|
|
});
|
|
});
|
|
|
|
//~~ Initialize view models
|
|
var loginStateViewModel = new LoginStateViewModel();
|
|
var printerProfilesViewModel = new PrinterProfilesViewModel();
|
|
var usersViewModel = new UsersViewModel(loginStateViewModel);
|
|
var timelapseViewModel = new TimelapseViewModel(loginStateViewModel);
|
|
|
|
var printerStateViewModel = new PrinterStateViewModel(loginStateViewModel, timelapseViewModel);
|
|
var settingsViewModel = new SettingsViewModel(loginStateViewModel, usersViewModel, printerProfilesViewModel);
|
|
var gcodeViewModel = new GcodeViewModel(loginStateViewModel, settingsViewModel);
|
|
var connectionViewModel = new ConnectionViewModel(loginStateViewModel, settingsViewModel, printerProfilesViewModel);
|
|
var appearanceViewModel = new AppearanceViewModel(settingsViewModel, printerStateViewModel);
|
|
var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel);
|
|
var terminalViewModel = new TerminalViewModel(loginStateViewModel, settingsViewModel);
|
|
|
|
var slicingViewModel = new SlicingViewModel(loginStateViewModel, printerProfilesViewModel);
|
|
var gcodeFilesViewModel = new GcodeFilesViewModel(printerStateViewModel, loginStateViewModel, slicingViewModel);
|
|
var controlViewModel = new ControlViewModel(loginStateViewModel, settingsViewModel, printerStateViewModel);
|
|
var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel);
|
|
var logViewModel = new LogViewModel(loginStateViewModel);
|
|
|
|
|
|
// the view model map is our basic look up table for dependencies that may be injected into other view models
|
|
var viewModelMap = {
|
|
loginStateViewModel: loginStateViewModel,
|
|
printerProfilesViewModel: printerProfilesViewModel,
|
|
usersViewModel: usersViewModel,
|
|
settingsViewModel: settingsViewModel,
|
|
connectionViewModel: connectionViewModel,
|
|
timelapseViewModel: timelapseViewModel,
|
|
printerStateViewModel: printerStateViewModel,
|
|
appearanceViewModel: appearanceViewModel,
|
|
temperatureViewModel: temperatureViewModel,
|
|
controlViewModel: controlViewModel,
|
|
terminalViewModel: terminalViewModel,
|
|
gcodeFilesViewModel: gcodeFilesViewModel,
|
|
gcodeViewModel: gcodeViewModel,
|
|
navigationViewModel: navigationViewModel,
|
|
logViewModel: logViewModel,
|
|
slicingViewModel: slicingViewModel,
|
|
};
|
|
|
|
// helper to create a view model instance with injected constructor parameters from the view model map
|
|
var _createViewModelInstance = function(viewModel, viewModelMap){
|
|
var viewModelClass = viewModel[0];
|
|
var viewModelParameters = viewModel[1];
|
|
|
|
// now we'll try to resolve all of the view model's constructor parameters via our view model map
|
|
var constructorParameters = _.map(viewModelParameters, function(parameter){
|
|
return viewModelMap[parameter]
|
|
});
|
|
|
|
if (_.some(constructorParameters, function(parameter) { return parameter === undefined; })) {
|
|
var _extractName = function(entry) { return entry[0]; };
|
|
var _onlyUnresolved = function(entry) { return entry[1] === undefined; };
|
|
var missingParameters = _.map(_.filter(_.zip(viewModelParameters, constructorParameters), _onlyUnresolved), _extractName);
|
|
console.log("postponing", viewModel[0].name, "due to missing parameters:", missingParameters.join(", "));
|
|
return;
|
|
}
|
|
|
|
// if we came this far then we could resolve all constructor parameters, so let's construct that view model
|
|
return new viewModelClass(constructorParameters);
|
|
};
|
|
|
|
// helper for translating the name of a view model class into an identifier for the view model map
|
|
var _getViewModelId = function(viewModel){
|
|
var name = viewModel[0].name;
|
|
return name.substr(0, 1).toLowerCase() + name.substr(1); // FooBarViewModel => fooBarViewModel
|
|
};
|
|
|
|
// instantiation loop, will make multiple passes over the list of unprocessed view models until all
|
|
// view models have been successfully instantiated with all of their dependencies or no changes can be made
|
|
// any more which means not all view models can be instantiated due to missing dependencies
|
|
var unprocessedViewModels = ADDITIONAL_VIEWMODELS.slice();
|
|
var additionalViewModels = [];
|
|
var pass = 1;
|
|
while (unprocessedViewModels.length > 0) {
|
|
console.log("View model dependency resolution, pass #" + pass++);
|
|
var startLength = unprocessedViewModels.length;
|
|
var postponed = [];
|
|
|
|
// now try to instantiate every one of our as of yet unprocessed view model descriptors
|
|
while (unprocessedViewModels.length > 0){
|
|
var viewModel = unprocessedViewModels.shift();
|
|
var viewModelId = _getViewModelId(viewModel);
|
|
|
|
// make sure that we don't have to view models going by the same name
|
|
if (_.has(viewModelMap, viewModelId)) {
|
|
console.error("Duplicate class name while instantiating viewModel ", viewModelId);
|
|
continue;
|
|
}
|
|
|
|
var viewModelInstance = _createViewModelInstance(viewModel, viewModelMap);
|
|
|
|
// our view model couldn't yet be instantiated, so postpone it for a bit
|
|
if (viewModelInstance === undefined) {
|
|
postponed.push(viewModel);
|
|
continue;
|
|
}
|
|
|
|
// we could resolve the depdendencies and the view model is not defined yet => add it, it's now fully processed
|
|
var viewModelBindTarget = viewModel[2];
|
|
additionalViewModels.push([viewModelInstance, viewModelBindTarget]);
|
|
viewModelMap[viewModelId] = viewModelInstance;
|
|
}
|
|
|
|
// anything that's now in the postponed list has to be readded to the unprocessedViewModels
|
|
unprocessedViewModels = unprocessedViewModels.concat(postponed);
|
|
|
|
// if we still have the same amount of items in our list of unprocessed view models it means that we
|
|
// couldn't instantiate any more view models over a whole iteration, which in turn mean we can't resolve the
|
|
// dependencies of remaining ones, so log that as an error and then quit the loop
|
|
if (unprocessedViewModels.length == startLength) {
|
|
console.error("Could not instantiate the following view models due to unresolvable dependencies:");
|
|
_.each(unprocessedViewModels, function(entry) {
|
|
console.error(entry[0].name, "(missing:", _.filter(entry[1], function(id) { return !_.has(viewModelMap, id); }).join(", "), ")");
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
console.log("View model dependency resolution done");
|
|
|
|
var allViewModels = _.values(viewModelMap);
|
|
var dataUpdater = new DataUpdater(allViewModels);
|
|
|
|
|
|
//~~ Temperature
|
|
|
|
$('#tabs a[data-toggle="tab"]').on('shown', function (e) {
|
|
temperatureViewModel.updatePlot();
|
|
terminalViewModel.updateOutput();
|
|
});
|
|
|
|
//~~ File list
|
|
|
|
$(".gcode_files").slimScroll({
|
|
height: "80vh",
|
|
size: "5px",
|
|
distance: "0",
|
|
railVisible: true,
|
|
alwaysVisible: true,
|
|
scrollBy: "102px"
|
|
});
|
|
|
|
//~~ Gcode upload
|
|
|
|
function gcode_upload_done(e, data) {
|
|
var filename = undefined;
|
|
var location = undefined;
|
|
if (data.result.files.hasOwnProperty("sdcard")) {
|
|
filename = data.result.files.sdcard.name;
|
|
location = "sdcard";
|
|
} else if (data.result.files.hasOwnProperty("local")) {
|
|
filename = data.result.files.local.name;
|
|
location = "local";
|
|
}
|
|
gcodeFilesViewModel.requestData(filename, location);
|
|
|
|
if (_.endsWith(filename.toLowerCase(), ".stl")) {
|
|
slicingViewModel.show(location, filename);
|
|
}
|
|
|
|
if (data.result.done) {
|
|
$("#gcode_upload_progress .bar").css("width", "0%");
|
|
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
|
|
$("#gcode_upload_progress .bar").text("");
|
|
}
|
|
|
|
if(data.result.files.hasOwnProperty("local")){
|
|
var f = data.result.files.local;
|
|
if(_.endsWith(filename.toLowerCase(), ".svg")){
|
|
f.type = "model"
|
|
viewModelMap['workingAreaViewModel'].placeSVG(f);
|
|
}
|
|
if(_.endsWith(filename.toLowerCase(), ".gco")){
|
|
f.type = "machinecode"
|
|
viewModelMap['workingAreaViewModel'].placeGcode(f);
|
|
}
|
|
}
|
|
}
|
|
|
|
function gcode_upload_fail(e, data) {
|
|
var error = "<p>" + gettext("Could not upload the file. Make sure that it is a GCODE file and has the extension \".gcode\" or \".gco\" or that it is an STL file with the extension \".stl\" and slicing support is enabled and configured.") + "</p>";
|
|
error += pnotifyAdditionalInfo("<pre>" + data.jqXHR.responseText + "</pre>");
|
|
new PNotify({
|
|
title: "Upload failed",
|
|
text: error,
|
|
type: "error",
|
|
hide: false
|
|
});
|
|
$("#gcode_upload_progress .bar").css("width", "0%");
|
|
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
|
|
$("#gcode_upload_progress .bar").text("");
|
|
}
|
|
|
|
function gcode_upload_progress(e, data) {
|
|
var progress = parseInt(data.loaded / data.total * 100, 10);
|
|
$("#gcode_upload_progress .bar").css("width", progress + "%");
|
|
$("#gcode_upload_progress .bar").text(gettext("Uploading ..."));
|
|
if (progress >= 100) {
|
|
$("#gcode_upload_progress").addClass("progress-striped").addClass("active");
|
|
$("#gcode_upload_progress .bar").text(gettext("Saving ..."));
|
|
}
|
|
}
|
|
|
|
function enable_local_dropzone() {
|
|
$("#gcode_upload").fileupload({
|
|
url: API_BASEURL + "files/local",
|
|
dataType: "json",
|
|
dropZone: localTarget,
|
|
done: gcode_upload_done,
|
|
fail: gcode_upload_fail,
|
|
progressall: gcode_upload_progress
|
|
});
|
|
}
|
|
|
|
function disable_local_dropzone() {
|
|
$("#gcode_upload").fileupload({
|
|
url: API_BASEURL + "files/local",
|
|
dataType: "json",
|
|
dropZone: null,
|
|
done: gcode_upload_done,
|
|
fail: gcode_upload_fail,
|
|
progressall: gcode_upload_progress
|
|
});
|
|
}
|
|
|
|
function enable_sd_dropzone() {
|
|
$("#gcode_upload_sd").fileupload({
|
|
url: API_BASEURL + "files/sdcard",
|
|
dataType: "json",
|
|
dropZone: $("#drop_sd"),
|
|
done: gcode_upload_done,
|
|
fail: gcode_upload_fail,
|
|
progressall: gcode_upload_progress
|
|
});
|
|
}
|
|
|
|
function disable_sd_dropzone() {
|
|
$("#gcode_upload_sd").fileupload({
|
|
url: API_BASEURL + "files/sdcard",
|
|
dataType: "json",
|
|
dropZone: null,
|
|
done: gcode_upload_done,
|
|
fail: gcode_upload_fail,
|
|
progressall: gcode_upload_progress
|
|
});
|
|
}
|
|
|
|
var localTarget;
|
|
if (CONFIG_SD_SUPPORT) {
|
|
localTarget = $("#drop_locally");
|
|
} else {
|
|
localTarget = $("#drop");
|
|
}
|
|
|
|
loginStateViewModel.isUser.subscribe(function(newValue) {
|
|
if (newValue === true) {
|
|
enable_local_dropzone();
|
|
} else {
|
|
disable_local_dropzone();
|
|
}
|
|
});
|
|
|
|
if (loginStateViewModel.isUser()) {
|
|
enable_local_dropzone();
|
|
} else {
|
|
disable_local_dropzone();
|
|
}
|
|
|
|
if (CONFIG_SD_SUPPORT) {
|
|
printerStateViewModel.isSdReady.subscribe(function(newValue) {
|
|
if (newValue === true && loginStateViewModel.isUser()) {
|
|
enable_sd_dropzone();
|
|
} else {
|
|
disable_sd_dropzone();
|
|
}
|
|
});
|
|
|
|
loginStateViewModel.isUser.subscribe(function(newValue) {
|
|
if (newValue === true && printerStateViewModel.isSdReady()) {
|
|
enable_sd_dropzone();
|
|
} else {
|
|
disable_sd_dropzone();
|
|
}
|
|
});
|
|
|
|
if (printerStateViewModel.isSdReady() && loginStateViewModel.isUser()) {
|
|
enable_sd_dropzone();
|
|
} else {
|
|
disable_sd_dropzone();
|
|
}
|
|
}
|
|
|
|
|
|
$(document).bind("dragover", function (e) {
|
|
var dropOverlay = $("#drop_overlay");
|
|
var dropZone = $("#drop");
|
|
var dropZoneLocal = $("#drop_locally");
|
|
var dropZoneSd = $("#drop_sd");
|
|
var dropZoneBackground = $("#drop_background");
|
|
var dropZoneLocalBackground = $("#drop_locally_background");
|
|
var dropZoneSdBackground = $("#drop_sd_background");
|
|
var timeout = window.dropZoneTimeout;
|
|
|
|
if (!timeout) {
|
|
dropOverlay.addClass('in');
|
|
} else {
|
|
clearTimeout(timeout);
|
|
}
|
|
|
|
var foundLocal = false;
|
|
var foundSd = false;
|
|
var found = false;
|
|
var node = e.target;
|
|
do {
|
|
if (dropZoneLocal && node === dropZoneLocal[0]) {
|
|
foundLocal = true;
|
|
break;
|
|
} else if (dropZoneSd && node === dropZoneSd[0]) {
|
|
foundSd = true;
|
|
break;
|
|
} else if (dropZone && node === dropZone[0]) {
|
|
found = true;
|
|
break;
|
|
}
|
|
node = node.parentNode;
|
|
} while (node != null);
|
|
|
|
if (foundLocal) {
|
|
dropZoneLocalBackground.addClass("hover");
|
|
dropZoneSdBackground.removeClass("hover");
|
|
} else if (foundSd && printerStateViewModel.isSdReady()) {
|
|
dropZoneSdBackground.addClass("hover");
|
|
dropZoneLocalBackground.removeClass("hover");
|
|
} else if (found) {
|
|
dropZoneBackground.addClass("hover");
|
|
} else {
|
|
if (dropZoneLocalBackground) dropZoneLocalBackground.removeClass("hover");
|
|
if (dropZoneSdBackground) dropZoneSdBackground.removeClass("hover");
|
|
if (dropZoneBackground) dropZoneBackground.removeClass("hover");
|
|
}
|
|
|
|
window.dropZoneTimeout = setTimeout(function () {
|
|
window.dropZoneTimeout = null;
|
|
dropOverlay.removeClass("in");
|
|
if (dropZoneLocal) dropZoneLocalBackground.removeClass("hover");
|
|
if (dropZoneSd) dropZoneSdBackground.removeClass("hover");
|
|
if (dropZone) dropZoneBackground.removeClass("hover");
|
|
}, 1000);
|
|
});
|
|
|
|
//~~ Underscore setup
|
|
|
|
_.mixin(_.str.exports());
|
|
|
|
//~~ knockout.js bindings
|
|
|
|
ko.bindingHandlers.popover = {
|
|
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
|
var val = ko.utils.unwrapObservable(valueAccessor());
|
|
|
|
var options = {
|
|
title: val.title,
|
|
animation: val.animation,
|
|
placement: val.placement,
|
|
trigger: val.trigger,
|
|
delay: val.delay,
|
|
content: val.content,
|
|
html: val.html
|
|
};
|
|
$(element).popover(options);
|
|
}
|
|
};
|
|
|
|
ko.bindingHandlers.allowBindings = {
|
|
init: function (elem, valueAccessor) {
|
|
return { controlsDescendantBindings: !valueAccessor() };
|
|
}
|
|
};
|
|
ko.virtualElements.allowedBindings.allowBindings = true;
|
|
|
|
ko.bindingHandlers.slimScrolledForeach = {
|
|
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
|
return ko.bindingHandlers.foreach.init(element, valueAccessor(), allBindings, viewModel, bindingContext);
|
|
},
|
|
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
|
setTimeout(function() {
|
|
$(element).slimScroll({scrollBy: 0});
|
|
}, 10);
|
|
return ko.bindingHandlers.foreach.update(element, valueAccessor(), allBindings, viewModel, bindingContext);
|
|
}
|
|
};
|
|
|
|
ko.bindingHandlers.qrcode = {
|
|
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
|
var val = ko.utils.unwrapObservable(valueAccessor());
|
|
|
|
var defaultOptions = {
|
|
text: "",
|
|
size: 200,
|
|
fill: "#000",
|
|
background: null,
|
|
label: "",
|
|
fontname: "sans",
|
|
fontcolor: "#000",
|
|
radius: 0,
|
|
ecLevel: "L"
|
|
};
|
|
|
|
var options = {};
|
|
_.each(defaultOptions, function(value, key) {
|
|
options[key] = ko.utils.unwrapObservable(val[key]) || value;
|
|
});
|
|
|
|
$(element).empty().qrcode(options);
|
|
}
|
|
};
|
|
|
|
ko.bindingHandlers.invisible = {
|
|
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
|
|
if (!valueAccessor()) return;
|
|
ko.bindingHandlers.style.update(element, function() {
|
|
return { visibility: 'hidden' };
|
|
})
|
|
}
|
|
};
|
|
|
|
//~~ startup commands
|
|
|
|
_.each(allViewModels, function(viewModel) {
|
|
if (viewModel.hasOwnProperty("onStartup")) {
|
|
viewModel.onStartup();
|
|
}
|
|
});
|
|
|
|
loginStateViewModel.subscribe(function(change, data) {
|
|
if ("login" == change) {
|
|
$("#gcode_upload").fileupload("enable");
|
|
|
|
if (data.admin) {
|
|
usersViewModel.requestData();
|
|
}
|
|
} else {
|
|
$("#gcode_upload").fileupload("disable");
|
|
}
|
|
});
|
|
|
|
//~~ view model binding
|
|
|
|
settingsViewModel.requestData(function() {
|
|
ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog"));
|
|
|
|
ko.applyBindings(connectionViewModel, document.getElementById("connection"));
|
|
ko.applyBindings(printerStateViewModel, document.getElementById("state"));
|
|
ko.applyBindings(gcodeFilesViewModel, document.getElementById("files_accordion"));
|
|
ko.applyBindings(controlViewModel, document.getElementById("control"));
|
|
ko.applyBindings(controlViewModel, document.getElementById("focus"));
|
|
ko.applyBindings(terminalViewModel, document.getElementById("term"));
|
|
var gcode = document.getElementById("gcode");
|
|
if (gcode) {
|
|
gcodeViewModel.initialize();
|
|
ko.applyBindings(gcodeViewModel, gcode);
|
|
}
|
|
ko.applyBindings(navigationViewModel, document.getElementById("navbar"));
|
|
// ko.applyBindings(appearanceViewModel, document.getElementsByTagName("head")[0]);
|
|
ko.applyBindings(printerStateViewModel, document.getElementById("drop_overlay"));
|
|
ko.applyBindings(logViewModel, document.getElementById("logs"));
|
|
|
|
// var timelapseElement = document.getElementById("timelapse");
|
|
// if (timelapseElement) {
|
|
// ko.applyBindings(timelapseViewModel, timelapseElement);
|
|
// }
|
|
//
|
|
// ko.applyBindings(slicingViewModel, document.getElementById("slicing_configuration_dialog"));
|
|
|
|
// apply bindings and signal startup
|
|
_.each(additionalViewModels, function(additionalViewModel) {
|
|
var viewModel = additionalViewModel[0];
|
|
var targets = additionalViewModel[1];
|
|
|
|
if (targets === undefined) {
|
|
return;
|
|
}
|
|
|
|
if (!Array.isArray(targets)) {
|
|
targets = [targets];
|
|
}
|
|
|
|
|
|
if (viewModel.hasOwnProperty("onBeforeBinding")) {
|
|
viewModel.onBeforeBinding();
|
|
}
|
|
|
|
_.each(targets, function(target) {
|
|
try {
|
|
ko.applyBindings(viewModel, target);
|
|
} catch (exc) {
|
|
console.log("Could not apply bindings for additional view model " + viewModel + ": " + exc.message);
|
|
}
|
|
});
|
|
|
|
if (viewModel.hasOwnProperty("onAfterBinding")) {
|
|
viewModel.onAfterBinding();
|
|
}
|
|
});
|
|
});
|
|
|
|
//~~ UI stuff
|
|
|
|
var webcamDisableTimeout;
|
|
$('#tabs a[data-toggle="tab"]').on('show', function (e) {
|
|
var current = e.target;
|
|
var previous = e.relatedTarget;
|
|
|
|
if (current.hash == "#control") {
|
|
clearTimeout(webcamDisableTimeout);
|
|
var webcamImage = $("#webcam_image");
|
|
var currentSrc = webcamImage.attr("src");
|
|
if (currentSrc === undefined || currentSrc.trim() == "") {
|
|
var newSrc = CONFIG_WEBCAM_STREAM;
|
|
if (CONFIG_WEBCAM_STREAM.lastIndexOf("?") > -1) {
|
|
newSrc += "&";
|
|
} else {
|
|
newSrc += "?";
|
|
}
|
|
newSrc += new Date().getTime();
|
|
|
|
webcamImage.attr("src", newSrc);
|
|
}
|
|
} else if (previous.hash == "#control") {
|
|
// only disable webcam stream if tab is out of focus for more than 5s, otherwise we might cause
|
|
// more load by the constant connection creation than by the actual webcam stream
|
|
webcamDisableTimeout = setTimeout(function() {
|
|
$("#webcam_image").attr("src", "");
|
|
}, 5000);
|
|
}
|
|
});
|
|
|
|
$(".accordion-toggle[data-target='#files']").click(function() {
|
|
var files = $("#files");
|
|
if (files.hasClass("in")) {
|
|
files.removeClass("overflow_visible");
|
|
} else {
|
|
setTimeout(function() {
|
|
files.addClass("overflow_visible");
|
|
}, 100);
|
|
}
|
|
});
|
|
|
|
$.fn.modal.defaults.maxHeight = function(){
|
|
// subtract the height of the modal header and footer
|
|
return $(window).height() - 165;
|
|
};
|
|
|
|
// Fix input element click problem on login dialog
|
|
$(".dropdown input, .dropdown label").click(function(e) {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
$(document).bind("drop dragover", function (e) {
|
|
e.preventDefault();
|
|
});
|
|
|
|
$("#login_user").keyup(function(event) {
|
|
if (event.keyCode == 13) {
|
|
$("#login_pass").focus();
|
|
}
|
|
});
|
|
$("#login_pass").keyup(function(event) {
|
|
if (event.keyCode == 13) {
|
|
$("#login_button").click();
|
|
}
|
|
});
|
|
|
|
if (CONFIG_FIRST_RUN) {
|
|
var firstRunViewModel = new FirstRunViewModel();
|
|
ko.applyBindings(firstRunViewModel, document.getElementById("first_run_dialog"));
|
|
firstRunViewModel.showDialog();
|
|
}
|
|
|
|
}
|
|
);
|
|
|