keyboard-layout-editor/serial.js
Ian Prest df91acc938 Remove unnecessary string.prototyp extensions
-- I had another copy of the same thing in extensions.js
2015-07-13 23:32:17 -04:00

234 lines
9.3 KiB
JavaScript

var $serial = {};
(function () {
"use strict";
// We need this so we can test locally and still save layouts to AWS
$serial.base_href = "http://www.keyboard-layout-editor.com";
// Lenient JSON reader/writer
$serial.toJsonL = function(obj) {
var res = [], key;
if(obj instanceof Array) {
obj.forEach(function(elem) { res.push($serial.toJsonL(elem)); });
return '['+res.join(',')+']';
}
if(typeof obj === 'object') {
for(key in obj) { if(obj.hasOwnProperty(key)) { res.push(key+':'+$serial.toJsonL(obj[key])); } }
return '{'+res.join(',')+'}';
}
if(typeof obj === 'number') {
return Math.round10(obj,-4);
}
return angular.toJson(obj);
};
$serial.fromJsonL = function(json) { return jsonl.parse(json); };
// function to sort the key array
$serial.sortKeys = function(keys) {
keys.sort(function(a,b) {
return ((a.rotation_angle+360)%360 - (b.rotation_angle+360)%360) ||
(a.rotation_x - b.rotation_x) ||
(a.rotation_y - b.rotation_y) ||
(a.y - b.y) ||
(a.x - b.x);
});
};
var _defaultKeyProps = {
x: 0, y: 0, x2: 0, y2: 0, // position
width: 1, height: 1, width2: 1, height2: 1, // size
color: "#cccccc", text: ["#000000"], // colors
labels:[], align: 4, fontheight: 3, fontheight2: 3, // label properties
rotation_angle: 0, rotation_x: 0, rotation_y: 0, // rotation
profile: "", nub: false, ghost: false, stepped: false // misc
};
var _defaultMetaData = { backcolor: '#eeeeee', name: '', author: '', notes: '' };
$serial.defaultKeyProps = function() { return angular.copy(_defaultKeyProps); };
$serial.defaultMetaData = function() { return angular.copy(_defaultMetaData); };
// Convert between our in-memory format & our serialized format
function serializeProp(props, nname, val, defval) { if(val !== defval) { props[nname] = val; } return val; }
$serial.serialize = function(keyboard) {
var keys = keyboard.keys;
var rows = [], row = [];
var current = $serial.defaultKeyProps();
current.text = current.text[0];
var cluster = {r:0, rx:0, ry:0};
// Serialize metadata
var meta = {};
for(var metakey in keyboard.meta) {
serializeProp(meta, metakey, keyboard.meta[metakey], _defaultMetaData[metakey]);
}
if(!$.isEmptyObject(meta)) {
rows.push(meta);
}
var newRow = true;
current.y--; // will be incremented on first row
// Serialize row/key-data
$serial.sortKeys(keys);
keys.forEach(function(key) {
var props = {};
var label = key.labels.join("\n").trimEnd();
// start a new row when necessary
var clusterChanged = (key.rotation_angle != cluster.r || key.rotation_x != cluster.rx || key.rotation_y != cluster.ry);
var rowChanged = (key.y !== current.y);
if(row.length>0 && (rowChanged || clusterChanged)) {
// Push the old row
rows.push(row);
row = [];
newRow = true;
}
if(newRow) {
// Set up for the new row
current.y++;
// 'y' is reset if *either* 'rx' or 'ry' are changed
if(key.rotation_y != cluster.ry || key.rotation_x != cluster.rx)
current.y = key.rotation_y;
current.x = key.rotation_x; // always reset x to rx (which defaults to zero)
// Update current cluster
cluster.r = key.rotation_angle;
cluster.rx = key.rotation_x;
cluster.ry = key.rotation_y;
newRow = false;
}
current.rotation_angle = serializeProp(props, "r", key.rotation_angle, current.rotation_angle);
current.rotation_x = serializeProp(props, "rx", key.rotation_x, current.rotation_x);
current.rotation_y = serializeProp(props, "ry", key.rotation_y, current.rotation_y);
current.y += serializeProp(props, "y", key.y-current.y, 0);
current.x += serializeProp(props, "x", key.x-current.x, 0) + key.width;
current.color = serializeProp(props, "c", key.color, current.color);
var textColor = key.text[0];
for(var i = 1; i < key.text.length; ++i) {
textColor += "\n";
if(key.text[i] && key.text[i] !== key.text[0]) {
textColor += key.text[i];
}
}
current.text = serializeProp(props, "t", textColor.trimEnd(), current.text);
current.ghost = serializeProp(props, "g", key.ghost, current.ghost);
current.profile = serializeProp(props, "p", key.profile, current.profile);
current.align = serializeProp(props, "a", key.align, current.align);
if(key.fontheight != current.fontheight) {
current.fontheight = serializeProp(props, "f", key.fontheight, current.fontheight);
current.fontheight2 = serializeProp(props, "f2", key.fontheight2, current.fontheight);
} else {
current.fontheight2 = serializeProp(props, "f2", key.fontheight2, current.fontheight2);
}
serializeProp(props, "w", key.width, 1);
serializeProp(props, "h", key.height, 1);
serializeProp(props, "w2", key.width2, key.width);
serializeProp(props, "h2", key.height2, key.height);
serializeProp(props, "x2", key.x2, 0);
serializeProp(props, "y2", key.y2, 0);
serializeProp(props, "n", key.nub || false, false);
serializeProp(props, "l", key.stepped || false, false);
if(!jQuery.isEmptyObject(props)) { row.push(props); }
row.push(label);
});
if(row.length>0) {
rows.push(row);
}
return rows;
}
function deserializeError(msg,data) {
throw "Error: " + msg + (data ? (":\n " + $serial.toJsonL(data)) : "");
}
$serial.deserialize = function(rows) {
// Initialize with defaults
var current = $serial.defaultKeyProps();
var meta = { backcolor: "#eeeeee" };
var keys = [];
var cluster = { x: 0, y: 0 };
for(var r = 0; r < rows.length; ++r) {
if(rows[r] instanceof Array) {
for(var k = 0; k < rows[r].length; ++k) {
var key = rows[r][k];
if(typeof key === 'string') {
var newKey = angular.copy(current);
newKey.width2 = newKey.width2 === 0 ? current.width : current.width2;
newKey.height2 = newKey.height2 === 0 ? current.height : current.height2;
newKey.labels = key.split('\n');
keys.push(newKey);
// Set up for the next key
current.x += current.width;
current.width = current.height = 1;
current.x2 = current.y2 = current.width2 = current.height2 = 0;
current.nub = current.stepped = false;
} else {
if(key.r != null) { if(k!=0) {deserializeError("'r' can only be used on the first key in a row", key);} current.rotation_angle = key.r; }
if(key.rx != null) { if(k!=0) {deserializeError("'rx' can only be used on the first key in a row", key);} current.rotation_x = cluster.x = key.rx; $.extend(current, cluster); }
if(key.ry != null) { if(k!=0) {deserializeError("ry' can only be used on the first key in a row", key);} current.rotation_y = cluster.y = key.ry; $.extend(current, cluster); }
if(key.a != null) { current.align = key.a; }
if(key.f) { current.fontheight = current.fontheight2 = key.f; }
if(key.f2) { current.fontheight2 = key.f2; }
if(key.p) { current.profile = key.p; }
if(key.c) { current.color = key.c; }
if(key.t) { current.text = key.t.split('\n'); }
if(key.x) { current.x += key.x; }
if(key.y) { current.y += key.y; }
if(key.w) { current.width = current.width2 = key.w; }
if(key.h) { current.height = current.height2 = key.h; }
if(key.x2) { current.x2 = key.x2; }
if(key.y2) { current.y2 = key.y2; }
if(key.w2) { current.width2 = key.w2; }
if(key.h2) { current.height2 = key.h2; }
if(key.n) { current.nub = key.n; }
if(key.l) { current.stepped = key.l; }
if(key.g != null) { current.ghost = key.g; }
}
}
// End of the row
current.y++;
} else if(typeof rows[r] === 'object') {
if(r != 0) { throw "Error: keyboard metadata must the be first element:\n "+$serial.toJsonL(rows[r]); }
$.extend(meta, rows[r]);
}
current.x = current.rotation_x;
}
return { meta:meta, keys:keys };
}
$serial.saveLayout = function($http, layout, success, error) {
var data = angular.toJson(layout);
var fn = CryptoJS.MD5(data).toString();
// First test to see if the file is already available
$http.get($serial.base_href+"/layouts/"+fn).success(function() { success(fn); }).error(function() {
// Nope... need to upload it
var fd = new FormData();
fd.append("key", "layouts/"+fn);
fd.append("AWSAccessKeyId", "AKIAJSXGG74EMFBC57QQ");
fd.append("acl", "public-read");
fd.append("success_action_redirect", $serial.base_href);
fd.append("policy", "eyJleHBpcmF0aW9uIjoiMjAwMTQtMDEtMDFUMDA6MDA6MDBaIiwiY29uZGl0aW9ucyI6W3siYnVja2V0Ijoid3d3LmtleWJvYXJkLWxheW91dC1lZGl0b3IuY29tIn0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCJsYXlvdXRzLyJdLHsiYWNsIjoicHVibGljLXJlYWQifSx7InN1Y2Nlc3NfYWN0aW9uX3JlZGlyZWN0IjoiaHR0cDovL3d3dy5rZXlib2FyZC1sYXlvdXQtZWRpdG9yLmNvbSJ9LHsiQ29udGVudC1UeXBlIjoiYXBwbGljYXRpb24vanNvbiJ9LFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsODE5Ml1dfQ==");
fd.append("signature", "WOsX5QV/y9UlOs2kmtduXYEPeEQ=");
fd.append("Content-Type", "application/json");
fd.append("file", data);
$http.post("http://www.keyboard-layout-editor.com.s3.amazonaws.com/", fd, {
headers: {'Content-Type': undefined },
transformRequest: angular.identity
}).success(function() { success(fn); }).error(function(data, status) {
if(status == 0) {
// We seem to get a 'cancelled' notification even though the POST
// is successful, so we have to double-check.
$http.get($serial.base_href+"/layouts/"+fn).success(function() { success(fn); }).error(error);
} else {
error(data,status);
}
});
});
};
}());