Merge pull request #6917 from keymanapp/feat/developer/touch-layout-schema-high-quality

feat(developer): cleanup touch layout files on load and save 🙊
This commit is contained in:
Marc Durdin 2022-07-25 19:57:53 +10:00 committed by GitHub
commit ca47fce5b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 114 additions and 33 deletions

View file

@ -42,7 +42,8 @@ The general structure of a .keyman-touch-layout file is:
## `root` object
The `root` object may contain up to three properties, each being a [`platform`
object](#platform-object), named `"phone"`, `"tablet"` or `"desktop"`.
object](#platform-object), named `"phone"`, `"tablet"` or `"desktop"`. There
must be at least one property.
## `platform` object
@ -68,7 +69,7 @@ optionally contain three additional properties: `font`, `fontsize`, and
`displayUnderlying`.
* `layer`
: An array of [`layer` objects](#layer-object). Required.
: A non-empty array of [`layer` objects](#layer-object). Required.
* `font`
: An optional CSS-style font-family specification, being the name of the font to
@ -140,7 +141,7 @@ affect the modifiers in any key events generated by keys on the layer.
* `row`
: An array of [`row` objects](#row-object). Required.
: A non-empty array of [`row` objects](#row-object). Required.
## `row` object
@ -150,19 +151,19 @@ properties: `id` and `key`.
* `id`
: An non-negative integer denoting where the row appears, with `0` being the top
row.
row. Required.
Legacy: this value was sometimes an integer in a string. This string format is
deprecated and will not be supported in future versions of Keyman Developer.
* `key`
: An array of [`key` objects](#key-object).
: A non-empty array of [`key` objects](#key-object). Required.
## `key` object
Describes a single key on a touch layout row. See also [`sk`
object](#sk-object). A `key` object has no required properties.
object](#sk-object). A `key` object must include either an `id` or `sp` property.
* `id`
@ -314,7 +315,9 @@ properties are not fully supported as of Keyman 15: `font`, `fontsize`, `pad`,
and `width`.
* `id`
: A string identifier for the longpress key. See `key` object for details.
: String. Required. An identifier for the longpress key. See `key` object for
details.
* `text`

View file

@ -11,7 +11,9 @@
"tablet": { "$ref": "#/definitions/platform" },
"phone": { "$ref": "#/definitions/platform" },
"desktop": { "$ref": "#/definitions/platform" }
}
},
"minProperties": 1,
"additionalProperties": false
},
"platform": {
@ -28,7 +30,8 @@
"layers": {
"type": "array",
"items": { "$ref": "#/definitions/layer" }
"items": { "$ref": "#/definitions/layer" },
"minItems": 1
},
"layer": {
@ -48,7 +51,8 @@
"rows": {
"type": "array",
"items": { "$ref": "#/definitions/row" }
"items": { "$ref": "#/definitions/row" },
"minItems": 1
},
"row": {
@ -69,7 +73,8 @@
"keys": {
"type": "array",
"items": { "$ref": "#/definitions/key" }
"items": { "$ref": "#/definitions/key" },
"minItems": 1
},
"key": {
@ -88,6 +93,13 @@
"flick": { "$ref": "#/definitions/flick" },
"multitap": { "$ref": "#/definitions/subkeys" }
},
"anyOf": [
{"required": ["id"]},
{"required": ["sp"]},
{"required": ["sk"]},
{"required": ["flick"]},
{"required": ["multitap"]}
],
"additionalProperties": false
},
@ -98,24 +110,25 @@
"key-sp": {
"type": "integer",
"enum": [0, 1, 2, 8, 9, 10],
"enum": [0, 1, 2, 8, 9, 10]
},
"key-pad": {
"type": "number",
"minimum": 0,
"maximum": 10000
"maximum": 100000
},
"key-width": {
"type": "number",
"minimum": 0,
"maximum": 1000
"maximum": 100000
},
"subkeys": {
"type": "array",
"items": { "$ref": "#/definitions/subkey" }
"items": { "$ref": "#/definitions/subkey" },
"minItems": 1
},
"subkey": {
@ -140,6 +153,7 @@
"patternProperties": {
"^(n|s|e|w|ne|nw|se|sw)$": { "$ref": "#/definitions/subkey" }
},
"minProperties": 1,
"additionalProperties": false
},

View file

@ -813,7 +813,7 @@ $(function() {
};
this.saveJSON = function(force) {
builder.removeEmptyRows();
builder.cleanupKVKL();
var newJson = JSON.stringify(KVKL, null, ' ');
if(force || newJson != json) {
// When adding or deleting layers and platforms, we need to force because
@ -978,23 +978,6 @@ $(function() {
);
};
builder.removeEmptyRows = function() {
var json = JSON.stringify(KVKL, null, ' ');
for (var platform in KVKL) {
for (let layer of KVKL[platform].layer) {
let newRow = [], n = 1;
for(let row of layer.row) {
if(row.key.length > 0) {
row.id = n;
n++;
newRow.push(row);
}
}
layer.row = newRow;
}
}
};
builder.textControlsInToolbar = function() {
return $('body').hasClass('text-controls-in-toolbar');
}
@ -1004,6 +987,7 @@ $(function() {
function initBuilder() {
$(function() {
builder.cleanupKVKL();
builder.prepareKeyCapTypes();
builder.preparePlatforms();
builder.enableUndoControls();

View file

@ -30,6 +30,7 @@
<script><xsl:attribute name="src"><xsl:value-of select="/TouchLayoutBuilder/LibPath"/>layer-controls.js</xsl:attribute></script>
<script><xsl:attribute name="src"><xsl:value-of select="/TouchLayoutBuilder/LibPath"/>builder-charmap.js</xsl:attribute></script>
<script><xsl:attribute name="src"><xsl:value-of select="/TouchLayoutBuilder/LibPath"/>drag-drop.js</xsl:attribute></script>
<script><xsl:attribute name="src"><xsl:value-of select="/TouchLayoutBuilder/LibPath"/>cleanup.js</xsl:attribute></script>
<script>initBuilder();</script>
</head>
<body class='text-controls-in-toolbar'>

View file

@ -0,0 +1,79 @@
$(function() {
builder.cleanupKVKL = function() {
let cleanupEmptyArrays = function() {
for (let platform in KVKL) {
for (let layer of KVKL[platform].layer) {
let newRow = [], n = 1;
for(let row of layer.row) {
if(row.key && Array.isArray(row.key)) {
for(let key of row.key) {
if(key.sk && (!Array.isArray(key.sk) || key.sk.length == 0)) {
delete key.sk;
}
}
if(row.key.length > 0) {
row.id = n;
n++;
newRow.push(row);
}
}
}
layer.row = newRow;
}
}
return true;
};
let cleanupNumericField = function(obj, prop) {
if(typeof obj[prop] == 'number') {
return true;
}
if(typeof obj[prop] == 'string') {
let v = obj[prop].trim();
if(v != '') {
v = parseInt(v, 10);
if(isFinite(v)) {
obj[prop] = v;
return true;
}
}
}
delete obj[prop];
return true;
}
let cleanupKeyTypes = function(key) {
// Delete empty strings for id
if(typeof key.id != 'undefined') {
if(typeof key.id != 'string') delete key.id;
else if(key.id.trim() == '') delete key.id;
}
// Enforce numeric types for sp, pad, width
return cleanupNumericField(key, 'sp') &&
cleanupNumericField(key, 'pad') &&
cleanupNumericField(key, 'width');
}
let cleanupTypes = function() {
for(let platform in KVKL) {
for(let layer of KVKL[platform].layer) {
for(let row of layer.row) {
for(let key of row.key) {
cleanupKeyTypes(key);
if(key.sk) {
for(let sk of key.sk) {
cleanupKeyTypes(sk);
}
}
}
}
}
}
return true;
}
return cleanupEmptyArrays() && cleanupTypes();
};
}.bind(builder));