cccc/static/js/cccc.js
2020-12-05 13:02:55 +01:00

734 lines
22 KiB
JavaScript

function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
function api(method, params, callback) {
var request = new XMLHttpRequest()
request.addEventListener('load', function (event) {
var response
try {
response = JSON.parse(event.target.responseText)
} catch(e) {
response = {
error: {code: -32700, message: 'Parse error'}
}
}
callback && callback(response)
}, false)
request.addEventListener('error', function (evt) {
callback && callback({error: {code: -32000, message: 'Server error'}})
}, false)
request.open('POST', '/api/')
request.setRequestHeader('Content-type', 'application/json')
request.send(JSON.stringify(
{method: method, params: params, jsonrpc: '2.0'}
))
}
function requiredFieldValidator(value) {
if (value == null || value == undefined || !value.length) {
return {valid: false, msg: "This is a required field"};
} else {
return {valid: true, msg: null};
}
}
function isInt(value) {
return !isNaN(value) &&
parseInt(Number(value)) == value &&
!isNaN(parseInt(value, 10));
}
function integerValidator(value) {
if (value == null || value == undefined || !value.length || !isInt(value)) {
return {valid: false, msg: "Value must be a number"};
} else {
return {valid: true, msg: null};
}
}
var presets = [];
var system_presets = [
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105
];
function presetSelect(all) {
var options = ''
if (all) {
for (var id=1;id<=300;id++) {
if (system_presets.indexOf(id) == -1) {
var preset = getPreset(id)
if (preset) {
options += '<option value="'+preset.id+'">' + preset.id + ': ' + preset.name + '</option>';
} else {
options += '<option value="'+id+'">' + id + ': Preset ' + id + '</option>';
}
}
}
} else {
presets.forEach(function(preset) {
options += '<option value="'+preset.id+'">' + preset.id + ': ' + preset.name + '</option>';
})
}
return $('<select name="preset-editor">' + options + '</select>')
}
function PresetEditor(args) {
var $preset;
var scope = this;
this.init = function () {
$preset = presetSelect()
.appendTo(args.container);
scope.focus();
};
this.destroy = function () {
$(args.container).empty();
};
this.focus = function () {
$preset.focus();
};
this.serializeValue = function () {
return parseInt($preset.val(), 10);
};
this.applyValue = function (item, state) {
console.log('apply', item, state)
item.preset = state;
};
this.loadValue = function (item) {
$preset.val(item.preset);
};
this.isValueChanged = function () {
return args.item.preset != parseInt($preset.val(), 10);
};
this.validate = function () {
if (isNaN(parseInt($preset.val(), 10))) {
return {valid: false, msg: "Invalid preset."};
}
return {valid: true, msg: null};
};
this.init();
}
function PresetFormatter(row, cell, value, columnDef, dataContext) {
preset = presets.filter(function(preset) { return preset.id == value })[0];
if (preset) {
return preset.id + ': ' + preset.name;
} else {
return value
}
}
function formatNumber(number, decimals) {
var array = [],
abs = Math.abs(number),
split = abs.toFixed(decimals).split('.');
while (split[0]) {
array.unshift(split[0].slice(-3));
split[0] = split[0].slice(0, -3);
}
split[0] = array.join(',');
return (number < 0 ? '-' : '') + split.join('.');
};
function formatDuration(seconds) {
if (seconds == '...' || !seconds) {
return seconds
}
var values = [
Math.floor(seconds / 31536000),
Math.floor(seconds % 31536000 / 86400),
Math.floor(seconds % 86400 / 3600),
Math.floor(seconds % 3600 / 60),
formatNumber(seconds % 60, 3)
];
var labels = ['y', 'd', 'h', 'm', 's'];
var duration = '';
values.forEach(function(v, i) {
if (v) {
if (labels[i] == 's') {
v = v.replace('.', 's ').replace(' 000', '')
duration += v;
} else {
duration += v + labels[i] + ' ';
}
}
});
return duration
}
function formatTime(row, cell, value, columnDef, dataContext) {
var time = '';
if (row == 0) {
return 0
}
if (data[row].duration) {
time = data.slice(0, row + 1).map(function(row) {
return row.duration || 0
}).reduce(function(a, b) { return a+b }, 0)
if (data[row].sleep) {
time -= data[row].sleep
}
}
return formatDuration(time)
}
function formatShotTime(row, cell, value, columnDef, dataContext) {
if (data[row].duration) {
return formatDuration(data[row].duration)
}
return ''
}
function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
return value ? "<img src='/static/sg/images/tick.png'>" : "";
}
function StatusFormatter(row, cell, value, columnDef, dataContext) {
return value ? "Next" : "";
}
var grid;
var data = [];
var nextStep;
var columns = [
{
id: "#",
name: "",
width: 10,
behavior: "selectAndMove",
selectable: false,
resizable: false,
cssClass: "cell-reorder dnd"
},
{id: "preset", name: "Preset", field: "preset", width: 180,
cssClass: "cell-title",
formatter: PresetFormatter,
editor: PresetEditor,
validator: requiredFieldValidator
},
{id: "speed", name: "Speed", field: "speed", editor: Slick.Editors.Integer, validator: integerValidator,
width: 60
},
{id: "sleep", name: "Sleep", field: "sleep", editor: Slick.Editors.Integer, validator: integerValidator,
width: 60
},
{id: "zoom", name: "Zoom Speed", field: "zoom", editor: Slick.Editors.Integer, validator: integerValidator, width: 75},
{id: "zoom_last", name: "Zoom Last", field: "zoom_last", cssClass: "cell-status", formatter: CheckmarkFormatter, editor: Slick.Editors.Checkbox, width: 60},
//{id: "duration", name: "Time", field: "duration", editor: Slick.Editors.Text, formatter: formatTime},
{id: "duration", name: "Time", field: "duration", formatter: formatTime},
{id: "shot_duration", name: "Duration", field: "shot_duration", formatter: formatShotTime},
{id: "status", name: "Status", width: 80, minWidth: 20, maxWidth: 80, cssClass: "cell-status", field: "status", formatter: StatusFormatter}
];
var options = {
editable: true,
enableAddRow: true,
enableCellNavigation: true,
asyncEditorLoading: false,
autoEdit: false
};
function loadData(sequence) {
data = sequence;
grid = new Slick.Grid("#myGrid", data, columns, options);
grid.setSelectionModel(new Slick.CellSelectionModel());
var moveRowsPlugin = new Slick.RowMoveManager({
cancelEditOnDrag: true
});
moveRowsPlugin.onBeforeMoveRows.subscribe(function (e, data) {
for (var i = 0; i < data.rows.length; i++) {
// no point in moving before or after itself
if (data.rows[i] == data.insertBefore || data.rows[i] == data.insertBefore - 1) {
e.stopPropagation();
return false;
}
}
return true;
});
moveRowsPlugin.onMoveRows.subscribe(function (e, args) {
var extractedRows = [], left, right;
var rows = args.rows;
var insertBefore = args.insertBefore;
left = data.slice(0, insertBefore);
right = data.slice(insertBefore, data.length);
rows.sort(function(a,b) { return a-b; });
for (var i = 0; i < rows.length; i++) {
extractedRows.push(data[rows[i]]);
}
rows.reverse();
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
if (row < insertBefore) {
left.splice(row, 1);
} else {
right.splice(row - insertBefore, 1);
}
}
data = left.concat(extractedRows.concat(right));
var selectedRows = [];
for (var i = 0; i < rows.length; i++)
selectedRows.push(left.length + i);
grid.resetActiveCell();
grid.setData(data);
grid.setSelectedRows(selectedRows);
grid.render();
});
grid.registerPlugin(moveRowsPlugin);
grid.onAddNewRow.subscribe(function (e, args) {
var item = args.item;
item.speed = item.speed || data[data.length-1].speed
item.sleep = item.sleep || 0
grid.invalidateRow(data.length);
data.push(item);
grid.updateRowCount();
grid.render();
});
}
function totalDuration() {
return data.map(function(row) {
return row.duration || 0
}).reduce(function(a, b) { return a+b }, 0)
}
function updateStatus() {
api('status', {}, function(response) {
if (response.result) {
$('#status').html(response.result.status)
$('button.run').attr({disabled: response.result.status == 'Active'})
$('button.run_from').attr({disabled: response.result.status == 'Active'})
$('button.continue_from').attr({disabled: response.result.status == 'Active'})
$('button.stop').attr({disabled: response.result.status != 'Active'})
if (response.result.duration && response.result.status == 'Active') {
$('#duration').html(formatDuration(response.result.duration))
} else {
$('#duration').html(formatDuration(totalDuration()))
}
var update = false;
if (response.result.time) {
data.forEach(function(row) {
if (row.seqid in response.result.time && response.result.time[row.seqid] != row.duration) {
if (row.seqid == data[0].seqid || response.result.time[row.seqid]) {
console.log(row.duration, response.result.time[row.seqid]);
row.duration = response.result.time[row.seqid];
update = true
}
}
})
}
if (response.result.position) {
$('#position').html(JSON.stringify(response.result.position))
}
data.forEach(function(row) {
var s = (response.result.next && row.seqid == response.result.next.seqid);
if (row.status != s) {
update = true
row.status = s
}
})
if (response.result.next) {
nextStep = response.result.next
if (response.result.next.seqid) {
delete response.result.next.seqid
}
if (response.result.next.duration) {
delete response.result.next.duration
}
$('#next').html(JSON.stringify(response.result.next))
}
if (update) {
grid.invalidate();
grid.render();
}
}
})
data.forEach(function(seq) {
if (!seq.seqid) {
seq.seqid = uuidv4()
}
})
localStorage.sequence = JSON.stringify(data)
var gotSelection = grid.getSelectedRows().length > 0
$('button.goto').attr({disabled: !gotSelection})
$('button.run_from').attr({disabled: !gotSelection})
$('button.continue_from').attr({disabled: !gotSelection})
$('button.insert').attr({disabled: !gotSelection})
$('button.delete').attr({disabled: !gotSelection})
}
var preset_offset = {}, current_position = {};
function updateShiftStatus() {
api('camera', {
'status': {}
}, function(response) {
if (response.result) {
if (response.result.status) {
current_position = response.result.status;
var preset = presets.filter(function(p) {
return p.id == parseInt($('select[name=preset-editor]').val(), 10)
})[0];
preset_offset['azimuth'] = preset['position']['azimuth'] - current_position['azimuth'];
preset_offset['elevation'] = preset['position']['elevation'] - current_position['elevation'];
preset_offset['absoluteZoom'] = preset['position']['absoluteZoom'] - current_position['absoluteZoom'];
updateDelta()
}
}
})
}
function updateDelta() {
$('#delta').html(JSON.stringify(preset_offset, null, ' '))
}
function deleteRows() {
var result = confirm("Are you sure you want to delete " + grid.getSelectedRows().length + " row(s)?");
if (result) {
var rowsToDelete = grid.getSelectedRows().sort().reverse();
for (var i = 0; i < rowsToDelete.length; i++) {
data.splice(rowsToDelete[i], 1);
}
grid.invalidate();
grid.setSelectedRows([]);
}
}
$('button.goto').on({click: function() {
var selected = grid.getSelectedRows()[0];
api('camera', {
'fast_preset': {id: data[selected].preset}
}, function(response) {
//console.log(response)
})
}})
$('button.run').on({click: function() {
data.forEach(function(seq) {
if (!seq.seqid) {
seq.seqid = uuidv4()
}
})
api('run', {
'steps': data
}, function(response) {
//console.log(response)
})
}})
$('button.run_from').on({click: function() {
var selected = grid.getSelectedRows()[0];
api('run', {
'steps': data.slice(selected)
}, function(response) {
//console.log(response)
})
}})
$('button.continue_from').on({click: function() {
var selected = grid.getSelectedRows()[0];
api('run', {
'steps': data.slice(selected),
'goto_first': false
}, function(response) {
console.log(response)
})
}})
$('button.stop').on({click: function() {
api('stop', {}, function(response) {
if (nextStep && grid.getSelectedRows().length == 0) {
var selected = data.map(function(row) { return row.preset }).indexOf(nextStep.preset)
grid.setSelectedRows([selected]);
}
})
}})
$('button.delete').on({click: deleteRows})
$('button.insert').on({click: function() {
var selected = grid.getSelectedRows()[0];
data.splice(selected, 0, {
preset: data[selected].preset,
speed: data[selected].speed,
seqid: uuidv4()
});
grid.invalidate();
grid.setSelectedRows([selected+1]);
}})
$('button.set_speed').on({click: function() {
var speed = parseInt($('input.default_speed').val(), 10)
data.forEach(function(row) {
row.speed = speed
})
grid.invalidate()
}})
function textBlob(data, type) {
type = type || 'text/plain; charset=utf-8';
var byteNumbers = new Array(data.length);
for (var i = 0; i < data.length; i++) {
byteNumbers[i] = data.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
var blob = new Blob([byteArray], {type: type});
return blob;
}
function exportSequence() {
return data.map(function(row) {
var r = {};
Object.keys(row).forEach(function(key) {
if (['status'].indexOf(key) == -1) {
r[key] = row[key];
}
})
return r
})
}
function getPreset(id) {
return presets.filter(function(preset) {
return preset.id == id;
})[0]
}
function exportSequenceCSV() {
var position = 0;
return [
['preset', 'name', 'time', 'duration', 'speed', 'sleep', 'elevation', 'azimuth', 'zoom']
].concat(data.map(function(row) {
var r = [];
r.push(row.preset)
var preset = getPreset(row.preset)
if (preset) {
r.push(preset.name)
} else {
r.push(row.preset)
}
if (row.duration) {
position += row.duration
r.push(formatDuration(position))
r.push(formatDuration(row.duration))
} else {
r.push('')
r.push('')
}
r.push(row.speed)
r.push(row.sleep)
if (preset) {
r.push(preset.position.elevation)
r.push(preset.position.azimuth)
r.push(preset.position.absoluteZoom)
} else {
r.push('')
r.push('')
r.push('')
}
return r.join(',');
})).join('\n');
}
$('button.export_sequence_csv').on({click: function() {
data.forEach(function(seq) {
if (!seq.seqid) {
seq.seqid = uuidv4()
}
})
var blob = textBlob(exportSequenceCSV(), 'text/csv; charset=utf-8')
var url = window.URL.createObjectURL(blob);
$(this).parent().attr({
href: url, download: 'sequence.csv'
});
}})
$('button.export_sequence').on({click: function() {
data.forEach(function(seq) {
if (!seq.seqid) {
seq.seqid = uuidv4()
}
})
var blob = textBlob(JSON.stringify(exportSequence(), null, ' '))
var url = window.URL.createObjectURL(blob);
$(this).parent().attr({
href: url, download: 'sequence.json'
});
}})
$('button.export_presets').on({click: function() {
var blob = textBlob(JSON.stringify(presets, null, ' '))
var url = window.URL.createObjectURL(blob);
$(this).parent().attr({
href: url, download: 'presets.json'
});
}})
$('button.shift_presets').on({click: function() {
document.location.href = '/static/shift_presets.html';
}});
$('button.all_presets').on({click: function() {
api('getPresets', {}, function(response) {
presets = response.result.presets
var seq = [].concat(data, presets.filter(function(preset) {
return data.filter(function(row) {
return row.preset == preset.id
}).length == 0
}).map(function(preset) {
return {
preset: preset.id,
speed: parseInt($('input.default_speed').val(), 10)
}
}))
loadData(seq)
})
}})
$('input.import_sequence').on({change: function() {
var reader = new FileReader()
reader.onload = function(event) {
localStorage.sequence = reader.result
loadData(JSON.parse(reader.result))
}
reader.readAsText(this.files[0]);
}})
$('input.import_presets').on({change: function() {
var reader = new FileReader()
reader.onload = function(event) {
var data = JSON.parse(reader.result)
api('setPresets', {
'presets': data
}, function(response) {
presets = response.result.presets
})
}
reader.readAsText(this.files[0]);
}})
function shiftPresets(offset, callback) {
api('getPresets', {}, function(response) {
presets = response.result.presets
presets.forEach(function(preset) {
if (offset.azimuth) {
preset['position']['azimuth'] -= offset.azimuth
}
if (offset.elevation) {
preset['position']['elevation'] -= offset.elevation
}
if (offset.absoluteZoom) {
preset['position']['absoluteZoom'] -= offset.absoluteZoom
}
})
api('setPresets', {
'presets': presets
}, function(response) {
presets = response.result.presets
callback && callback()
})
})
}
function init() {
api('getPresets', {}, function(response) {
presets = response.result.presets
loadData(JSON.parse(localStorage.sequence || '[]'))
updateStatus()
setInterval(updateStatus, 1000)
})
}
function init_shift() {
api('getPresets', {}, function(response) {
presets = response.result.presets
presetSelect().on({
change: updateShiftStatus
}).appendTo($('body'))
$('<pre>').attr({id: 'delta'}).appendTo($('body'))
$('<button>').html('Shift All Presets')
.appendTo($('body')).on({
click: function() {
$('body').html('updating presets...')
shiftPresets(preset_offset, function() {
document.location.href = '/'
})
}
})
updateShiftStatus()
setInterval(updateShiftStatus, 5000)
})
}
function init_ptz() {
var ptz = document.querySelector('#ptz')
if (ptz) {
ptz.querySelectorAll('button').forEach(button => {
button.addEventListener('click', event => {
var duration = parseFloat(ptz.querySelector('input[name="duration"]').value)
var speed = parseInt(ptz.querySelector('input[name="speed"]').value)
api('move', {
direction: button.dataset.direction,
duration: duration,
speed: speed
}, response=> {})
})
})
}
api('getPresets', {}, function(response) {
presets = response.result.presets
var id = 1
presetSelect(true).on({
change: event => {
id = parseInt(event.target.value)
var preset = getPreset(id)
name.val(preset ? preset.name : '')
}
}).appendTo($('body'))
$('<button>').html('Go')
.appendTo($('body')).on({
click: function() {
api('camera', {
'fast_preset': {id: id}
}, function(response) {
//console.log(response)
})
}
})
var name = $('<input>').appendTo($('body'))
name.val(getPreset(id).name)
$('<button>').html('Set')
.appendTo($('body')).on({
click: function() {
var data = {}
if (name.val().length) {
data['name'] = name.val()
}
data['id'] = id
api('setPreset', data)
}
})
})
}