2016-07-18 22:56:27 +00:00
|
|
|
/*
|
|
|
|
Node-OpenDroneMap Node.js App and REST API to access OpenDroneMap.
|
|
|
|
Copyright (C) 2016 Node-OpenDroneMap Contributors
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2017-04-08 04:05:20 +00:00
|
|
|
$(function() {
|
|
|
|
function hoursMinutesSecs(t) {
|
2016-07-14 21:42:12 +00:00
|
|
|
var ch = 60 * 60 * 1000,
|
|
|
|
cm = 60 * 1000,
|
|
|
|
h = Math.floor(t / ch),
|
2017-04-08 04:05:20 +00:00
|
|
|
m = Math.floor((t - h * ch) / cm),
|
|
|
|
s = Math.round((t - h * ch - m * cm) / 1000),
|
|
|
|
pad = function(n) { return n < 10 ? '0' + n : n; };
|
|
|
|
if (s === 60) {
|
|
|
|
m++;
|
|
|
|
s = 0;
|
|
|
|
}
|
|
|
|
if (m === 60) {
|
|
|
|
h++;
|
|
|
|
m = 0;
|
|
|
|
}
|
|
|
|
return [pad(h), pad(m), pad(s)].join(':');
|
2016-07-14 21:42:12 +00:00
|
|
|
}
|
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
function TaskList() {
|
2016-07-06 18:44:20 +00:00
|
|
|
var uuids = JSON.parse(localStorage.getItem("odmTaskList") || "[]");
|
|
|
|
if (Object.prototype.toString.call(uuids) !== "[object Array]") uuids = [];
|
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
this.tasks = ko.observableArray($.map(uuids, function(uuid) {
|
2016-07-06 18:44:20 +00:00
|
|
|
return new Task(uuid);
|
|
|
|
}));
|
|
|
|
}
|
2016-07-09 16:58:14 +00:00
|
|
|
TaskList.prototype.add = function(task) {
|
2016-07-06 18:44:20 +00:00
|
|
|
this.tasks.push(task);
|
|
|
|
this.saveTaskListToLocalStorage();
|
|
|
|
};
|
2017-04-08 04:05:20 +00:00
|
|
|
TaskList.prototype.saveTaskListToLocalStorage = function() {
|
|
|
|
localStorage.setItem("odmTaskList", JSON.stringify($.map(this.tasks(), function(task) {
|
|
|
|
return task.uuid;
|
|
|
|
})));
|
2016-07-06 18:44:20 +00:00
|
|
|
};
|
2017-04-08 04:05:20 +00:00
|
|
|
TaskList.prototype.remove = function(task) {
|
|
|
|
this.tasks.remove(function(t) {
|
2016-07-06 18:44:20 +00:00
|
|
|
return t === task;
|
|
|
|
});
|
|
|
|
this.saveTaskListToLocalStorage();
|
|
|
|
};
|
|
|
|
|
2016-07-30 18:30:56 +00:00
|
|
|
var codes = {
|
|
|
|
QUEUED: 10,
|
|
|
|
RUNNING: 20,
|
|
|
|
FAILED: 30,
|
|
|
|
COMPLETED: 40,
|
|
|
|
CANCELED: 50
|
|
|
|
};
|
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
function Task(uuid) {
|
2016-07-08 20:44:48 +00:00
|
|
|
var self = this;
|
|
|
|
|
2016-07-06 18:44:20 +00:00
|
|
|
this.uuid = uuid;
|
|
|
|
this.loading = ko.observable(true);
|
|
|
|
this.info = ko.observable({});
|
2016-07-09 16:58:14 +00:00
|
|
|
this.viewingOutput = ko.observable(false);
|
2016-07-14 21:42:12 +00:00
|
|
|
this.output = ko.observableArray();
|
2016-07-09 16:58:14 +00:00
|
|
|
this.resetOutput();
|
2016-07-14 21:42:12 +00:00
|
|
|
this.timeElapsed = ko.observable("00:00:00");
|
2016-07-07 22:07:17 +00:00
|
|
|
|
|
|
|
var statusCodes = {
|
|
|
|
10: {
|
|
|
|
descr: "Queued",
|
|
|
|
icon: "glyphicon-hourglass"
|
|
|
|
},
|
|
|
|
20: {
|
|
|
|
descr: "Running",
|
2016-07-14 21:42:12 +00:00
|
|
|
icon: "glyphicon-cog spinning"
|
2016-07-07 22:07:17 +00:00
|
|
|
},
|
|
|
|
30: {
|
|
|
|
descr: "Failed",
|
|
|
|
icon: "glyphicon-remove-circle"
|
|
|
|
},
|
|
|
|
40: {
|
|
|
|
descr: "Completed",
|
|
|
|
icon: "glyphicon-ok-circle"
|
|
|
|
},
|
|
|
|
50: {
|
|
|
|
descr: "Canceled",
|
|
|
|
icon: "glyphicon-ban-circle"
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
this.statusDescr = ko.pureComputed(function() {
|
|
|
|
if (this.info().status && this.info().status.code) {
|
|
|
|
if (statusCodes[this.info().status.code]) {
|
2016-07-07 22:07:17 +00:00
|
|
|
return statusCodes[this.info().status.code].descr;
|
2017-04-08 04:05:20 +00:00
|
|
|
} else return "Unknown (Status Code: " + this.info().status.code + ")";
|
|
|
|
} else return "-";
|
2016-07-06 18:44:20 +00:00
|
|
|
}, this);
|
2017-04-08 04:05:20 +00:00
|
|
|
this.icon = ko.pureComputed(function() {
|
|
|
|
if (this.info().status && this.info().status.code) {
|
|
|
|
if (statusCodes[this.info().status.code]) {
|
2016-07-07 22:07:17 +00:00
|
|
|
return statusCodes[this.info().status.code].icon;
|
2017-04-08 04:05:20 +00:00
|
|
|
} else return "glyphicon-question-sign";
|
|
|
|
} else return "";
|
2016-07-07 22:07:17 +00:00
|
|
|
}, this);
|
2017-04-08 04:05:20 +00:00
|
|
|
this.showCancel = ko.pureComputed(function() {
|
|
|
|
return this.info().status &&
|
|
|
|
(this.info().status.code === codes.QUEUED || this.info().status.code === codes.RUNNING);
|
2016-07-14 21:42:12 +00:00
|
|
|
}, this);
|
2017-04-08 04:05:20 +00:00
|
|
|
this.showRestart = ko.pureComputed(function() {
|
|
|
|
return this.info().status &&
|
|
|
|
(this.info().status.code === codes.CANCELED);
|
2016-07-14 21:42:12 +00:00
|
|
|
}, this);
|
2017-04-08 04:05:20 +00:00
|
|
|
this.showRemove = ko.pureComputed(function() {
|
|
|
|
return this.info().status &&
|
|
|
|
(this.info().status.code === codes.FAILED || this.info().status.code === codes.COMPLETED || this.info().status.code === codes.CANCELED);
|
2016-07-07 22:07:17 +00:00
|
|
|
}, this);
|
2017-04-08 04:05:20 +00:00
|
|
|
this.showDownload = ko.pureComputed(function() {
|
|
|
|
return this.info().status &&
|
|
|
|
(this.info().status.code === codes.COMPLETED);
|
2016-07-18 21:00:01 +00:00
|
|
|
}, this);
|
2016-07-09 16:58:14 +00:00
|
|
|
this.startRefreshingInfo();
|
2016-07-07 22:07:17 +00:00
|
|
|
}
|
2017-04-08 04:05:20 +00:00
|
|
|
Task.prototype.refreshInfo = function() {
|
2016-07-06 18:44:20 +00:00
|
|
|
var self = this;
|
2016-07-09 16:58:14 +00:00
|
|
|
var url = "/task/" + this.uuid + "/info";
|
2016-07-06 18:44:20 +00:00
|
|
|
$.get(url)
|
2017-04-08 04:05:20 +00:00
|
|
|
.done(function(json) {
|
|
|
|
// Track time
|
2016-07-14 21:42:12 +00:00
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
if (json.processingTime && json.processingTime !== -1) {
|
|
|
|
self.timeElapsed(hoursMinutesSecs(json.processingTime));
|
|
|
|
}
|
|
|
|
self.info(json);
|
|
|
|
})
|
|
|
|
.fail(function() {
|
|
|
|
self.info({ error: url + " is unreachable." });
|
|
|
|
})
|
|
|
|
.always(function() { self.loading(false); });
|
2016-07-07 22:07:17 +00:00
|
|
|
};
|
2017-04-08 04:05:20 +00:00
|
|
|
Task.prototype.consoleMouseOver = function() { this.autoScrollOutput = false; };
|
|
|
|
Task.prototype.consoleMouseOut = function() { this.autoScrollOutput = true; };
|
|
|
|
Task.prototype.resetOutput = function() {
|
2016-07-09 16:58:14 +00:00
|
|
|
this.viewOutputLine = 0;
|
|
|
|
this.autoScrollOutput = true;
|
2016-07-14 21:42:12 +00:00
|
|
|
this.output.removeAll();
|
2016-07-09 16:58:14 +00:00
|
|
|
};
|
2017-04-08 04:05:20 +00:00
|
|
|
Task.prototype.viewOutput = function() {
|
2016-07-09 16:58:14 +00:00
|
|
|
var self = this;
|
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
function fetchOutput() {
|
2016-07-09 16:58:14 +00:00
|
|
|
var url = "/task/" + self.uuid + "/output";
|
2017-04-08 04:05:20 +00:00
|
|
|
$.get(url, { line: self.viewOutputLine })
|
|
|
|
.done(function(output) {
|
|
|
|
for (var i in output) {
|
|
|
|
self.output.push(output[i]);
|
2016-07-09 16:58:14 +00:00
|
|
|
}
|
2017-04-08 04:05:20 +00:00
|
|
|
if (output.length) {
|
|
|
|
self.viewOutputLine += output.length;
|
|
|
|
if (self.autoScrollOutput) {
|
|
|
|
var $console = $("#console_" + self.uuid);
|
|
|
|
$console.scrollTop($console[0].scrollHeight - $console.height());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.fail(function() {
|
|
|
|
self.info({ error: url + " is unreachable." });
|
|
|
|
});
|
2016-07-09 16:58:14 +00:00
|
|
|
}
|
|
|
|
this.fetchOutputInterval = setInterval(fetchOutput, 2000);
|
|
|
|
fetchOutput();
|
|
|
|
|
|
|
|
this.viewingOutput(true);
|
|
|
|
};
|
2017-04-08 04:05:20 +00:00
|
|
|
Task.prototype.hideOutput = function() {
|
2016-07-09 16:58:14 +00:00
|
|
|
if (this.fetchOutputInterval) clearInterval(this.fetchOutputInterval);
|
|
|
|
this.viewingOutput(false);
|
|
|
|
};
|
|
|
|
Task.prototype.startRefreshingInfo = function() {
|
|
|
|
var self = this;
|
|
|
|
this.stopRefreshingInfo();
|
|
|
|
this.refreshInfo();
|
2017-04-08 04:05:20 +00:00
|
|
|
this.refreshInterval = setInterval(function() {
|
2016-07-09 16:58:14 +00:00
|
|
|
self.refreshInfo();
|
2016-07-14 21:42:12 +00:00
|
|
|
}, 500); // TODO: change to larger value
|
2016-07-09 16:58:14 +00:00
|
|
|
};
|
|
|
|
Task.prototype.stopRefreshingInfo = function() {
|
2017-04-08 04:05:20 +00:00
|
|
|
if (this.refreshInterval) {
|
2016-07-09 16:58:14 +00:00
|
|
|
clearInterval(this.refreshInterval);
|
|
|
|
this.refreshInterval = null;
|
|
|
|
}
|
|
|
|
};
|
2016-07-06 18:44:20 +00:00
|
|
|
Task.prototype.remove = function() {
|
2016-07-07 22:07:17 +00:00
|
|
|
var self = this;
|
2016-07-09 16:58:14 +00:00
|
|
|
var url = "/task/remove";
|
2016-07-07 22:07:17 +00:00
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
function doRemove() {
|
2016-07-30 18:30:56 +00:00
|
|
|
$.post(url, {
|
2017-04-08 04:05:20 +00:00
|
|
|
uuid: self.uuid
|
|
|
|
})
|
|
|
|
.done(function(json) {
|
|
|
|
if (json.success || self.info().error) {
|
|
|
|
taskList.remove(self);
|
|
|
|
} else {
|
|
|
|
self.info({ error: json.error });
|
|
|
|
}
|
2016-07-08 20:44:48 +00:00
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
self.stopRefreshingInfo();
|
|
|
|
})
|
|
|
|
.fail(function() {
|
|
|
|
self.info({ error: url + " is unreachable." });
|
|
|
|
self.stopRefreshingInfo();
|
|
|
|
});
|
2016-07-30 18:30:56 +00:00
|
|
|
}
|
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
if (this.info().status && this.info().status.code === codes.COMPLETED) {
|
2016-07-30 18:30:56 +00:00
|
|
|
if (confirm("Are you sure?")) doRemove();
|
2017-04-08 04:05:20 +00:00
|
|
|
} else {
|
2016-07-30 18:30:56 +00:00
|
|
|
doRemove();
|
|
|
|
}
|
2016-07-07 22:07:17 +00:00
|
|
|
};
|
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
function genApiCall(url, onSuccess) {
|
|
|
|
return function() {
|
2016-07-07 22:07:17 +00:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
$.post(url, {
|
2017-04-08 04:05:20 +00:00
|
|
|
uuid: this.uuid
|
|
|
|
})
|
|
|
|
.done(function(json) {
|
|
|
|
if (json.success) {
|
|
|
|
if (onSuccess !== undefined) onSuccess(self, json);
|
|
|
|
self.startRefreshingInfo();
|
|
|
|
} else {
|
|
|
|
self.stopRefreshingInfo();
|
|
|
|
self.info({ error: json.error });
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.fail(function() {
|
|
|
|
self.info({ error: url + " is unreachable." });
|
2016-07-09 16:58:14 +00:00
|
|
|
self.stopRefreshingInfo();
|
2017-04-08 04:05:20 +00:00
|
|
|
});
|
2016-07-26 19:20:37 +00:00
|
|
|
};
|
|
|
|
}
|
2016-07-09 16:58:14 +00:00
|
|
|
Task.prototype.cancel = genApiCall("/task/cancel");
|
2017-04-08 04:05:20 +00:00
|
|
|
Task.prototype.restart = genApiCall("/task/restart", function(task) {
|
2016-07-14 21:42:12 +00:00
|
|
|
task.resetOutput();
|
|
|
|
});
|
2017-04-08 04:05:20 +00:00
|
|
|
Task.prototype.download = function() {
|
2016-11-05 18:46:32 +00:00
|
|
|
location.href = "/task/" + this.uuid + "/download/all.zip";
|
|
|
|
};
|
2017-04-08 04:05:20 +00:00
|
|
|
Task.prototype.downloadOrthophoto = function() {
|
2016-11-05 18:46:32 +00:00
|
|
|
location.href = "/task/" + this.uuid + "/download/orthophoto.tif";
|
2016-07-18 21:00:01 +00:00
|
|
|
};
|
2016-07-06 18:44:20 +00:00
|
|
|
|
|
|
|
var taskList = new TaskList();
|
2016-07-26 01:10:18 +00:00
|
|
|
ko.applyBindings(taskList, document.getElementById('taskList'));
|
2016-07-06 18:44:20 +00:00
|
|
|
|
|
|
|
// Handle uploads
|
2016-07-05 18:06:22 +00:00
|
|
|
$("#images").fileinput({
|
2016-07-09 16:58:14 +00:00
|
|
|
uploadUrl: '/task/new',
|
2016-07-07 22:07:17 +00:00
|
|
|
showPreview: false,
|
2017-04-12 03:55:52 +00:00
|
|
|
allowedFileExtensions: ['jpg', 'jpeg', 'txt', 'zip'],
|
2016-07-05 18:06:22 +00:00
|
|
|
elErrorContainer: '#errorBlock',
|
|
|
|
showUpload: false,
|
|
|
|
uploadAsync: false,
|
2017-04-08 04:05:20 +00:00
|
|
|
uploadExtraData: function() {
|
2016-07-07 22:07:17 +00:00
|
|
|
return {
|
2016-07-26 19:20:37 +00:00
|
|
|
name: $("#taskName").val(),
|
2017-04-08 04:05:20 +00:00
|
|
|
zipurl: $("#zipurl").val(),
|
2017-04-12 03:55:52 +00:00
|
|
|
webhook: $("#webhook").val(),
|
2016-07-26 19:20:37 +00:00
|
|
|
options: JSON.stringify(optionsModel.getUserOptions())
|
2016-07-07 22:07:17 +00:00
|
|
|
};
|
2016-07-05 18:06:22 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
$("#btnUpload").click(function() {
|
2016-07-07 22:07:17 +00:00
|
|
|
$("#btnUpload").attr('disabled', true)
|
2017-04-08 04:05:20 +00:00
|
|
|
.val("Uploading...");
|
2016-07-05 18:06:22 +00:00
|
|
|
|
2016-07-07 22:07:17 +00:00
|
|
|
// Start upload
|
|
|
|
$("#images").fileinput('upload');
|
2017-04-08 04:05:20 +00:00
|
|
|
});
|
|
|
|
|
2017-04-12 03:55:52 +00:00
|
|
|
$('#resetWebhook').on('click', function(){
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$("#webhook").val('');
|
|
|
|
});
|
|
|
|
|
2017-04-09 00:20:32 +00:00
|
|
|
// zip file control
|
|
|
|
$('#btnShowImport').on('click', function(e){
|
|
|
|
e.preventDefault();
|
|
|
|
$('#zipFileInput').removeClass('hidden');
|
|
|
|
$('#btnShowUpload').removeClass('hidden');
|
2017-04-08 04:05:20 +00:00
|
|
|
|
2017-04-09 00:20:32 +00:00
|
|
|
$('#imagesInput').addClass('hidden');
|
|
|
|
$('#btnShowImport').addClass('hidden');
|
2017-04-08 04:05:20 +00:00
|
|
|
|
2017-04-09 00:20:32 +00:00
|
|
|
});
|
2017-04-08 04:05:20 +00:00
|
|
|
|
2017-04-09 00:20:32 +00:00
|
|
|
$('#btnShowUpload').on('click', function(e){
|
|
|
|
e.preventDefault();
|
|
|
|
$('#imagesInput').removeClass('hidden');
|
|
|
|
$('#btnShowImport').removeClass('hidden');
|
2017-04-08 04:05:20 +00:00
|
|
|
|
2017-04-09 00:20:32 +00:00
|
|
|
$('#zipFileInput').addClass('hidden');
|
|
|
|
$('#btnShowUpload').addClass('hidden');
|
|
|
|
$('#zipurl').val('');
|
|
|
|
});
|
2016-07-06 18:44:20 +00:00
|
|
|
|
2016-07-07 22:07:17 +00:00
|
|
|
var btnUploadLabel = $("#btnUpload").val();
|
|
|
|
$("#images")
|
2017-04-08 04:05:20 +00:00
|
|
|
.on('filebatchuploadsuccess', function(e, params) {
|
2016-07-07 22:07:17 +00:00
|
|
|
$("#images").fileinput('reset');
|
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
if (params.response && params.response.uuid) {
|
2016-07-09 16:58:14 +00:00
|
|
|
taskList.add(new Task(params.response.uuid));
|
2016-07-07 22:07:17 +00:00
|
|
|
}
|
|
|
|
})
|
2017-04-08 04:05:20 +00:00
|
|
|
.on('filebatchuploadcomplete', function() {
|
2016-07-07 22:07:17 +00:00
|
|
|
$("#btnUpload").removeAttr('disabled')
|
2017-04-08 04:05:20 +00:00
|
|
|
.val(btnUploadLabel);
|
2016-07-07 22:07:17 +00:00
|
|
|
})
|
2016-09-25 22:35:44 +00:00
|
|
|
.on('filebatchuploaderror', console.warn);
|
2016-07-26 01:10:18 +00:00
|
|
|
|
|
|
|
// Load options
|
2017-04-08 04:05:20 +00:00
|
|
|
function Option(properties) {
|
2016-07-28 16:28:18 +00:00
|
|
|
this.properties = properties;
|
2016-07-26 19:20:37 +00:00
|
|
|
this.value = ko.observable();
|
2016-07-26 01:10:18 +00:00
|
|
|
}
|
2017-04-08 04:05:20 +00:00
|
|
|
Option.prototype.resetToDefault = function() {
|
2016-07-26 19:20:37 +00:00
|
|
|
this.value(undefined);
|
|
|
|
};
|
2016-07-26 01:10:18 +00:00
|
|
|
|
2017-04-08 04:05:20 +00:00
|
|
|
function OptionsModel() {
|
2016-07-26 01:10:18 +00:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
this.options = ko.observableArray();
|
2017-04-08 04:05:20 +00:00
|
|
|
this.options.subscribe(function() {
|
|
|
|
setTimeout(function() {
|
2016-07-26 19:20:37 +00:00
|
|
|
$('#options [data-toggle="tooltip"]').tooltip();
|
|
|
|
}, 100);
|
|
|
|
});
|
|
|
|
this.showOptions = ko.observable(false);
|
2016-07-26 01:10:18 +00:00
|
|
|
this.error = ko.observable();
|
|
|
|
|
2016-09-19 21:25:39 +00:00
|
|
|
$.get("/options")
|
2017-04-08 04:05:20 +00:00
|
|
|
.done(function(json) {
|
|
|
|
if (json.error) self.error(json.error);
|
|
|
|
else {
|
|
|
|
for (var i in json) {
|
|
|
|
self.options.push(new Option(json[i]));
|
|
|
|
}
|
2016-07-26 01:10:18 +00:00
|
|
|
}
|
2017-04-08 04:05:20 +00:00
|
|
|
})
|
|
|
|
.fail(function() {
|
|
|
|
self.error("options are not available.");
|
|
|
|
});
|
2016-07-26 01:10:18 +00:00
|
|
|
}
|
2017-04-08 04:05:20 +00:00
|
|
|
OptionsModel.prototype.getUserOptions = function() {
|
2016-07-26 19:20:37 +00:00
|
|
|
var result = [];
|
2017-04-08 04:05:20 +00:00
|
|
|
for (var i = 0; i < this.options().length; i++) {
|
2016-07-26 19:20:37 +00:00
|
|
|
var opt = this.options()[i];
|
2017-04-08 04:05:20 +00:00
|
|
|
if (opt.value() !== undefined) {
|
2016-07-26 19:20:37 +00:00
|
|
|
result.push({
|
2016-07-28 16:28:18 +00:00
|
|
|
name: opt.properties.name,
|
2016-07-26 19:20:37 +00:00
|
|
|
value: opt.value()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
2016-07-26 01:10:18 +00:00
|
|
|
|
|
|
|
var optionsModel = new OptionsModel();
|
|
|
|
ko.applyBindings(optionsModel, document.getElementById("options"));
|
2017-04-08 04:05:20 +00:00
|
|
|
});
|