Merge branch 'master' into lmlayer-specify-punctuation

This commit is contained in:
Eddie Antonio Santos 2019-07-30 09:22:34 -06:00
commit fe0a99c6e4
No known key found for this signature in database
GPG key ID: DD411EA4D9509D87
27 changed files with 654 additions and 225 deletions

View file

@ -9,7 +9,7 @@ android {
defaultConfig {
applicationId "com.tavultesoft.kmapro"
minSdkVersion 16
minSdkVersion 19
targetSdkVersion 28
//println "===DUMPING PROPERTIES==="

View file

@ -12,6 +12,7 @@
var device = window.jsInterface.getDeviceType();
var oskHeight = window.jsInterface.getKeyboardHeight();
var oskWidth = 0;
var fragmentToggle = 0;
window.addEventListener('load', init, false);
function init() {
@ -39,6 +40,18 @@
kmw.addEventListener('keyboardloaded', setIsChiral);
kmw.addEventListener('keyboardchange', setIsChiral);
kmw.addEventListener('mm.modelchange', onModelChange);
notifyHost('pageLoaded');
}
function notifyHost(event, params) {
// TODO: Update all other host notifications to use notifyHost instead of directly setting window.location.hash
window.setTimeout(function() {
// We use a timeout so that the navigation doesn't cause the calling function to abort after the call
fragmentToggle = (fragmentToggle + 1) % 100;
params = params ? '+'+params : '';
window.location.hash = event+'-'+fragmentToggle+params;
}, 10);
}
// Update the KMW banner height
@ -211,7 +224,6 @@
kmw['setActiveElement'](ta);
}
var fragmentToggle = 0;
function oskCreateKeyPreview(x,y,w,h,t) {
fragmentToggle = (fragmentToggle + 1) % 100;
var div = document.createElement('div');

View file

@ -62,6 +62,8 @@ final class KMKeyboard extends WebView {
private String keyboardName;
private String keyboardVersion;
protected ArrayList<String> javascriptAfterLoad = new ArrayList<String>();
private static String currentKeyboard = null;
private static String txtFont = "";
private static String oskFont = null;
@ -136,12 +138,16 @@ final class KMKeyboard extends WebView {
public boolean onConsoleMessage(ConsoleMessage cm) {
if (KMManager.isDebugMode()) {
Log.d("KMEA", "Keyman JS Log: Line " + cm.lineNumber() + ", " + cm.sourceId() + ":" + cm.message());
if (cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) {
Log.e("KMEA", "Keyman JS Log: ERROR");
}
}
// Send console errors to Firebase Analytics.
// (Ignoring spurious message "No keyboard stubs exist = ...")
// TODO: Analyze if this error warrants reverting to default keyboard
// TODO: Fix base error rather than trying to ignore it "No keyboard stubs exist"
if ((cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) && (!cm.message().startsWith("No keyboard stubs exist"))) {
sendKMWError(cm.lineNumber(), cm.sourceId(), cm.message());
Toast.makeText(context, "Fatal Error with " + currentKeyboard +
@ -151,6 +157,7 @@ final class KMKeyboard extends WebView {
KMManager.KMDefault_LanguageID, KMManager.KMDefault_KeyboardName,
KMManager.KMDefault_LanguageName, KMManager.KMDefault_KeyboardFont, null);
}
return true;
}
});
@ -167,10 +174,11 @@ final class KMKeyboard extends WebView {
if (subKeysList != null) {
showSubKeys(context);
return;
} else if(suggestionJSON != null) {
} /* For future implementation
else if(suggestionJSON != null) {
showSuggestionLongpress(context);
return;
}
}*/
}
@Override
@ -182,20 +190,55 @@ final class KMKeyboard extends WebView {
public void loadKeyboard() {
keyboardSet = false;
if(keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP)
KMManager.InAppKeyboardLoaded = false;
else
KMManager.SystemKeyboardLoaded = false;
String htmlPath = "file://" + getContext().getDir("data", Context.MODE_PRIVATE) + "/" + KMManager.KMFilename_KeyboardHtml;
loadUrl(htmlPath);
setBackgroundColor(0);
}
public void loadJavascript(String func) {
this.javascriptAfterLoad.add(func);
if((keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP && KMManager.InAppKeyboardLoaded) ||
(keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM && KMManager.SystemKeyboardLoaded)) {
if(this.javascriptAfterLoad.size() == 1)
callJavascriptAfterLoad();
}
}
public void callJavascriptAfterLoad() {
if(this.javascriptAfterLoad.size() > 0) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
if(javascriptAfterLoad.size() > 0) {
loadUrl("javascript:" + javascriptAfterLoad.get(0));
javascriptAfterLoad.remove(0);
if (javascriptAfterLoad.size() > 0) {
callJavascriptAfterLoad();
}
}
}
}, 1);
}
}
public void hideKeyboard() {
String jsString = "javascript:hideKeyboard()";
loadUrl(jsString);
String jsString = "hideKeyboard()";
loadJavascript(jsString);
}
public void executeHardwareKeystroke(int code, int shift, int lstates, int eventModifiers) {
String jsFormat = "javascript:executeHardwareKeystroke(%d,%d, %d, %d)";
String jsFormat = "executeHardwareKeystroke(%d,%d, %d, %d)";
String jsString = String.format(jsFormat, code, shift, lstates, eventModifiers);
loadUrl(jsString);
loadJavascript(jsString);
}
@SuppressLint("ClickableViewAccessibility")
@ -225,8 +268,8 @@ final class KMKeyboard extends WebView {
DisplayMetrics dms = context.getResources().getDisplayMetrics();
int kbWidth = (int) (dms.widthPixels / dms.density);
// Ensure window is loaded for javascript functions
loadUrl(String.format(
"javascript:window.onload = function(){ setOskWidth(\"%d\");"+
loadJavascript(String.format(
"window.onload = function(){ setOskWidth(\"%d\");"+
"setOskHeight(\"0\"); };", kbWidth));
if (ShouldShowHelpBubble) {
ShouldShowHelpBubble = false;
@ -234,7 +277,7 @@ final class KMKeyboard extends WebView {
handler.postDelayed(new Runnable() {
@Override
public void run() {
loadUrl("javascript:showHelpBubble()");
loadJavascript("showHelpBubble()");
}
}, 2000);
}
@ -257,15 +300,15 @@ final class KMKeyboard extends WebView {
dismissKeyPreview(0);
dismissSubKeysWindow();
int bannerHeight = KMManager.getBannerHeight(context);
loadUrl(String.format("javascript:setBannerHeight(%d)", bannerHeight));
loadUrl(String.format("javascript:setOskWidth(%d)", newConfig.screenWidthDp));
loadUrl("javascript:setOskHeight(0)");
loadJavascript(String.format("setBannerHeight(%d)", bannerHeight));
loadJavascript(String.format("setOskWidth(%d)", newConfig.screenWidthDp));
loadJavascript("setOskHeight(0)");
if (dismissHelpBubble()) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
loadUrl("javascript:showHelpBubble()");
loadJavascript("showHelpBubble()");
}
}, 2000);
}
@ -385,9 +428,9 @@ final class KMKeyboard extends WebView {
keyboardName = keyboardName.replaceAll("\'", "\\\\'"); // Double-escaped-backslash b/c regex.
languageName = languageName.replaceAll("\'", "\\\\'");
String jsFormat = "javascript:setKeymanLanguage('%s','%s','%s','%s','%s', %s, %s, '%s')";
String jsFormat = "setKeymanLanguage('%s','%s','%s','%s','%s', %s, %s, '%s')";
String jsString = String.format(jsFormat, keyboardName, keyboardID, languageName, languageID, keyboardPath, tFont, oFont, packageID);
loadUrl(jsString);
loadJavascript(jsString);
this.packageID = packageID;
this.keyboardID = keyboardID;
@ -401,7 +444,7 @@ final class KMKeyboard extends WebView {
handler.postDelayed(new Runnable() {
@Override
public void run() {
loadUrl("javascript:showHelpBubble()");
loadJavascript("showHelpBubble()");
}
}, 2000);
}
@ -486,9 +529,9 @@ final class KMKeyboard extends WebView {
keyboardName = keyboardName.replaceAll("\'", "\\\\'"); // Double-escaped-backslash b/c regex.
languageName = languageName.replaceAll("\'", "\\\\'");
String jsFormat = "javascript:setKeymanLanguage('%s','%s','%s','%s','%s', %s, %s, '%s')";
String jsFormat = "setKeymanLanguage('%s','%s','%s','%s','%s', %s, %s, '%s')";
String jsString = String.format(jsFormat, keyboardName, keyboardID, languageName, languageID, keyboardPath, tFont, oFont, packageID);
loadUrl(jsString);
loadJavascript(jsString);
this.packageID = packageID;
this.keyboardID = keyboardID;
@ -502,7 +545,7 @@ final class KMKeyboard extends WebView {
handler.postDelayed(new Runnable() {
@Override
public void run() {
loadUrl("javascript:showHelpBubble()");
loadJavascript("showHelpBubble()");
}
}, 2000);
}
@ -712,14 +755,14 @@ final class KMKeyboard extends WebView {
public void onDismiss() {
suggestionJSON = null;
suggestionMenuWindow = null;
String jsString = "javascript:popupVisible(0)";
loadUrl(jsString);
String jsString = "popupVisible(0)";
loadJavascript(jsString);
}
});
suggestionMenuWindow.showAtLocation(KMKeyboard.this, Gravity.TOP | Gravity.LEFT, posX , posY);
String jsString = "javascript:popupVisible(1)";
loadUrl(jsString);
String jsString = "popupVisible(1)";
loadJavascript(jsString);
return;
}
@ -822,9 +865,9 @@ final class KMKeyboard extends WebView {
int index = v.getId() - 1;
String keyId = subkeyList.get(index).get("keyId");
String keyText = getSubkeyText(keyId, subkeyList.get(index).get("keyText"));
String jsFormat = "javascript:executePopupKey('%s','%s')";
String jsFormat = "executePopupKey('%s','%s')";
String jsString = String.format(jsFormat, keyId, keyText);
loadUrl(jsString);
loadJavascript(jsString);
}
});
button.setClickable(false);
@ -909,8 +952,8 @@ final class KMKeyboard extends WebView {
public void onDismiss() {
subKeysList = null;
subKeysWindow = null;
String jsString = "javascript:popupVisible(0)";
loadUrl(jsString);
String jsString = "popupVisible(0)";
loadJavascript(jsString);
}
});
@ -933,8 +976,8 @@ final class KMKeyboard extends WebView {
// And now to actually display it.
subKeysWindow.showAtLocation(KMKeyboard.this, Gravity.TOP | Gravity.LEFT, posX, posY);
String jsString = "javascript:popupVisible(1)";
loadUrl(jsString);
String jsString = "popupVisible(1)";
loadJavascript(jsString);
}
// Attempt to get the subkey text.

View file

@ -718,11 +718,11 @@ public final class KMManager {
RelativeLayout.LayoutParams params = getKeyboardLayoutParams();
if (InAppKeyboard != null && InAppKeyboardLoaded && !InAppKeyboardShouldIgnoreTextChange) {
InAppKeyboard.setLayoutParams(params);
InAppKeyboard.loadUrl(String.format("javascript:enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
InAppKeyboard.loadJavascript(String.format("enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
}
if (SystemKeyboard != null && SystemKeyboardLoaded && !SystemKeyboardShouldIgnoreTextChange) {
SystemKeyboard.setLayoutParams(params);
SystemKeyboard.loadUrl(String.format("javascript:enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
SystemKeyboard.loadJavascript(String.format("enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
}
return true;
}
@ -733,13 +733,13 @@ public final class KMManager {
currentLexicalModel = null;
}
String url = String.format("javascript:deregisterModel('%s')", modelID);
if (InAppKeyboard != null && InAppKeyboardLoaded) {
InAppKeyboard.loadUrl(url);
String url = String.format("deregisterModel('%s')", modelID);
if (InAppKeyboard != null) { // && InAppKeyboardLoaded) {
InAppKeyboard.loadJavascript(url);
}
if (SystemKeyboard != null && SystemKeyboardLoaded) {
SystemKeyboard.loadUrl(url);
if (SystemKeyboard != null) { // && SystemKeyboardLoaded) {
SystemKeyboard.loadJavascript(url);
}
return true;
}
@ -1103,11 +1103,11 @@ public final class KMManager {
public static void setNumericLayer(KeyboardType kbType) {
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
if (InAppKeyboard != null && InAppKeyboardLoaded && !InAppKeyboardShouldIgnoreTextChange) {
InAppKeyboard.loadUrl("javascript:setNumericLayer()");
InAppKeyboard.loadJavascript("setNumericLayer()");
}
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
if (SystemKeyboard != null && SystemKeyboardLoaded && !SystemKeyboardShouldIgnoreTextChange) {
SystemKeyboard.loadUrl("javascript:setNumericLayer()");
SystemKeyboard.loadJavascript("setNumericLayer()");
}
}
}
@ -1121,14 +1121,14 @@ public final class KMManager {
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
if (InAppKeyboard != null && InAppKeyboardLoaded && !InAppKeyboardShouldIgnoreTextChange) {
InAppKeyboard.loadUrl(String.format("javascript:updateKMText('%s')", kmText));
InAppKeyboard.loadJavascript(String.format("updateKMText('%s')", kmText));
result = true;
}
InAppKeyboardShouldIgnoreTextChange = false;
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
if (SystemKeyboard != null && SystemKeyboardLoaded && !SystemKeyboardShouldIgnoreTextChange) {
SystemKeyboard.loadUrl(String.format("javascript:updateKMText('%s')", kmText));
SystemKeyboard.loadJavascript(String.format("updateKMText('%s')", kmText));
result = true;
}
@ -1142,7 +1142,7 @@ public final class KMManager {
boolean result = false;
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
if (InAppKeyboard != null && InAppKeyboardLoaded && !InAppKeyboardShouldIgnoreSelectionChange) {
InAppKeyboard.loadUrl(String.format("javascript:updateKMSelectionRange(%d,%d)", selStart, selEnd));
InAppKeyboard.loadJavascript(String.format("updateKMSelectionRange(%d,%d)", selStart, selEnd));
result = true;
}
@ -1157,7 +1157,7 @@ public final class KMManager {
}
}
SystemKeyboard.loadUrl(String.format("javascript:updateKMSelectionRange(%d,%d)", selStart, selEnd));
SystemKeyboard.loadJavascript(String.format("updateKMSelectionRange(%d,%d)", selStart, selEnd));
result = true;
}
@ -1170,11 +1170,11 @@ public final class KMManager {
public static void resetContext(KeyboardType kbType) {
if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) {
if (InAppKeyboard != null && InAppKeyboardLoaded) {
InAppKeyboard.loadUrl("javascript:resetContext()");
InAppKeyboard.loadJavascript("resetContext()");
}
} else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
if (SystemKeyboard != null && SystemKeyboardLoaded) {
SystemKeyboard.loadUrl("javascript:resetContext()");
SystemKeyboard.loadJavascript("resetContext()");
}
}
}
@ -1301,15 +1301,18 @@ public final class KMManager {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (url.endsWith(KMFilename_KeyboardHtml)) {
InAppKeyboardLoaded = false;
}
}
@Override
public void onPageFinished(WebView view, String url) {
Log.d("KMEA", "onPageFinished: [inapp] " + url);
shouldOverrideUrlLoading(view, url);
}
private void pageLoaded(WebView view, String url) {
String langId = KMManager.KMKey_LanguageID;
if (url.endsWith(KMFilename_KeyboardHtml)) {
Log.d("KMEA", "pageLoaded: [inapp] " + url);
if (url.startsWith("file")) { //endsWith(KMFilename_KeyboardHtml)) {
InAppKeyboardLoaded = true;
if (!InAppKeyboard.keyboardSet) {
@ -1342,20 +1345,23 @@ public final class KMManager {
public void run() {
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
if (prefs.getBoolean(KMManager.KMKey_ShouldShowHelpBubble, true)) {
InAppKeyboard.loadUrl("javascript:showHelpBubble()");
InAppKeyboard.loadJavascript("showHelpBubble()");
}
}
}, 2000);
InAppKeyboard.callJavascriptAfterLoad();
KeyboardEventHandler.notifyListeners(KMTextView.kbEventListeners, KeyboardType.KEYBOARD_TYPE_INAPP, EventType.KEYBOARD_LOADED, null);
}
shouldOverrideUrlLoading(view, url);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.indexOf("hideKeyboard") >= 0) {
Log.d("KMEA", "shouldOverrideUrlLoading [inapp]: "+url);
if(url.indexOf("pageLoaded") >= 0) {
pageLoaded(view, url);
} else if (url.indexOf("hideKeyboard") >= 0) {
if (KMTextView.activeView != null && KMTextView.activeView.getClass() == KMTextView.class) {
InAppKeyboard.dismissHelpBubble();
KMTextView textView = (KMTextView) KMTextView.activeView;
@ -1490,17 +1496,19 @@ public final class KMManager {
JSONParser parser = new JSONParser();
JSONObject obj = parser.getJSONObjectFromURIString(suggestionJSON);
/* // For future implementation
InAppKeyboard.suggestionWindowPos = new double[]{x, y};
InAppKeyboard.suggestionJSON = suggestionJSON;
try {
Log.v("KMEA", "Suggestion display: " + obj.getString("displayAs"));
Log.v("KMEA", "Suggestion's banner coords: " + x + ", " + y + ", " + width + ", " + height);
Log.v("KMEA", "Is a <keep> suggestion: " + isCustom);
Log.v("KMEA", "Is a <keep> suggestion: " + isCustom); // likely outdated now that tags exist.
} catch (JSONException e) {
//e.printStackTrace();
Log.v("KMEA", "JSON parsing error: " + e.getMessage());
}
*/
}
return false;
}
@ -1515,15 +1523,18 @@ public final class KMManager {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (url.endsWith(KMFilename_KeyboardHtml)) {
SystemKeyboardLoaded = false;
}
}
@Override
public void onPageFinished(WebView view, String url) {
Log.d("KMEA", "onPageFinished: [system] " + url);
shouldOverrideUrlLoading(view, url);
}
private void pageLoaded(WebView view, String url) {
String langId = KMManager.KMKey_LanguageID;
if (url.endsWith(KMFilename_KeyboardHtml)) {
Log.d("KMEA", "pageLoaded: [system] " + url);
if (url.startsWith("file:")) {
SystemKeyboardLoaded = true;
if (!SystemKeyboard.keyboardSet) {
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
@ -1555,20 +1566,22 @@ public final class KMManager {
public void run() {
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
if (prefs.getBoolean(KMManager.KMKey_ShouldShowHelpBubble, true)) {
SystemKeyboard.loadUrl("javascript:showHelpBubble()");
SystemKeyboard.loadJavascript("showHelpBubble()");
}
}
}, 2000);
KeyboardEventHandler.notifyListeners(KMTextView.kbEventListeners, KeyboardType.KEYBOARD_TYPE_SYSTEM, EventType.KEYBOARD_LOADED, null);
}
shouldOverrideUrlLoading(view, url);
SystemKeyboard.callJavascriptAfterLoad();
}
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.indexOf("hideKeyboard") >= 0) {
if(url.indexOf("pageLoaded") >= 0) {
pageLoaded(view, url);
} else if (url.indexOf("hideKeyboard") >= 0) {
SystemKeyboard.dismissHelpBubble();
IMService.requestHideSelf(0);
} else if (url.indexOf("globeKeyAction") >= 0) {
@ -1718,14 +1731,19 @@ public final class KMManager {
JSONParser parser = new JSONParser();
JSONObject obj = parser.getJSONObjectFromURIString(suggestionJSON);
/* // For future implementation
SystemKeyboard.suggestionWindowPos = new double[]{x, y};
SystemKeyboard.suggestionJSON = suggestionJSON;
try {
Log.v("KMEA", "Suggestion display: " + obj.getString("displayAs"));
Log.v("KMEA", "Suggestion's banner coords: " + x + ", " + y + ", " + width + ", " + height);
Log.v("KMEA", "Is a <keep> suggestion: " + isCustom);
Log.v("KMEA", "Is a <keep> suggestion: " + isCustom); // likely outdated now that tags exist.
} catch (JSONException e) {
//e.printStackTrace();
Log.v("KMEA", "JSON parsing error: " + e.getMessage());
}
*/
}
return false;

View file

@ -1,10 +1,9 @@
# Keyman for Android
## 12.0 alpha
## 2019-07-29 12.0.2200 beta
* Add new "Settings" menu (#1751)
* Update compile and target Android SDK version to 28 (#1654)
* Add feature to install lexical model packages (#1641, 1653)
* Started work on Keyman for Android 12.
* Add predictive text support (#1641, #1653)
* Minimum supported Android version is now 4.4 (KitKat) (#1905)
## 2019-06-28 11.0.2108 stable
* Changes:

View file

@ -14,7 +14,7 @@
"url": "git+https://github.com/eddieantonio/keyman-lmlayer-prototype.git"
},
"author": "Eddie Antonio Santos <easantos@ualberta.ca> (http://eddieantonio.ca/)",
"license": "AGPL-3.0",
"license": "MIT",
"bugs": {
"url": "https://github.com/eddieantonio/keyman-lmlayer-prototype/issues"
},

View file

@ -73,12 +73,14 @@ module.exports = function(config) {
};
var CURRENT_WIN_LAUNCHERS = {
bs_firefox_win: {
os: 'Windows',
os_version: '10',
browser: 'firefox',
browser_version: '62.0'
},
// Currently, Firefox launcher is unstable; see https://github.com/karma-runner/karma-firefox-launcher/issues/93
// (in particular "not maintained" commentary).
//bs_firefox_win: {
// os: 'Windows',
// os_version: '10',
// browser: 'firefox',
// browser_version: '62.0'
//},
bs_chrome_win: {
os: 'Windows',
os_version: '10',
@ -91,12 +93,13 @@ module.exports = function(config) {
browser: 'ie',
browser_version: '11.0'
},
bs_edge_win: {
os: 'Windows',
os_version: '10',
browser: 'edge',
browser_version: '17.0'
}
// On recent versions of Edge, launcher fails to start and/or stop Edge successfully
//bs_edge_win: {
// os: 'Windows',
// os_version: '10',
// browser: 'edge',
// browser_version: '17.0'
//}
}
var CURRENT_ANDROID_LAUNCHERS = {

View file

@ -32,7 +32,7 @@ get_browser_set_for_OS ( ) {
if [ $os_id = "mac" ]; then
BROWSERS="--browsers Firefox,Chrome,Safari"
elif [ $os_id = "win" ]; then
BROWSERS="--browsers Firefox,Chrome,IE,Edge"
BROWSERS="--browsers Chrome,IE"
else
BROWSERS="--browsers Firefox,Chrome"
fi

View file

@ -9,7 +9,8 @@ module.exports = function(config) {
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Firefox', 'IE', 'Chrome', 'Edge'], // Can be specified at run-time instead!
browsers: ['IE', 'Chrome'], // Can be specified at run-time instead!
// Note: disabled Firefox, Edge due to instability
// Future note for us: https://www.npmjs.com/package/karma-browserstack-launcher
// Concurrency level

View file

@ -135,8 +135,54 @@ interface LMLayerWorkerState {
* The model implementation, within the Worker.
*/
interface WorkerInternalModel {
/**
* Processes `config` messages, configuring the newly-loaded model based on the host
* platform's capability restrictions.
*
* This allows the model to configure its suggestions according to what the platform
* allows the host to actually perform - for example, if post-caret deletions are not
* supported, no suggestions requiring this feature should be produced by the model.
*
* Returns a `Configuration` object detailing the capabilities the model plans to
* actually utilize, which must be as restrictive or more restrictive than those
* indicated within the provided `Capabilities` object.
* @param capabilities
*/
configure(capabilities: Capabilities): Configuration;
/**
* Generates predictive suggestions corresponding to the state of context after the proposed
* transform is applied to it. This transform may correspond to a 'correction' of a recent
* keystroke rather than one actually received.
*
* This method should NOT attempt to perform any form of correction; this is modeled within a
* separate component of the LMLayer predictive engine. That is, "th" + "e" should not be
* have "this" for a suggestion ("e" has been 'corrected' to "i"), while "there" would be
* a reasonable prediction.
*
* However, addition of diacritics to characters (which may transform the underlying char code
* when Unicode-normalized) is permitted. For example, "pur" + "e" may reasonably predict
* "purée", where "e" has been transformed to "é" as part of the suggestion.
*
* When both prediction and correction are permitted, said component (the `ModelCompositor`) will
* generally call this method once per 'likely' generated corrected state of the context,
* utilizing the results to compute an overall likelihood across all possible suggestions.
* @param transform A Transform corresponding to a recent input keystroke
* @param context A depiction of the context to which `transform` is applied.
* @returns A probability distribution (`Distribution<Suggestion>`) on the resulting `Suggestion`
* space for use in determining the most optimal overall suggestions.
*/
predict(transform: Transform, context: Context): Distribution<Suggestion>;
/**
* Performs a wordbreak operation given the current context state, returning whatever word
* or word fragment exists that starts before the caret but after the most recent whitespace
* preceding the caret. If no such text exists, the empty string is returned.
*
* This function is designed for use in generating display text for 'keep' `Suggestions`
* and display text for reverting any previously-applied `Suggestions`.
* @param context
*/
wordbreak(context: Context): USVString;
/**

59
developer/js/build.sh Executable file
View file

@ -0,0 +1,59 @@
#!/bin/bash
#
# Compiles the developer tools, including the language model compilers.
#
# Exit on command failure and when using unset variables:
set -eu
# Include some helper functions from resources
. ../../resources/shellHelperFunctions.sh
EX_USAGE=64
# Build the main script.
build () {
npm run build || fail "Could not build top-level JavaScript file."
}
display_usage ( ) {
echo "Usage: $0 [-test]"
echo " $0 -help"
echo
echo " -help displays this screen and exits"
echo " -test runs unit tests after building"
}
################################ Main script ################################
run_tests=0
# Process command-line arguments
while [[ $# -gt 0 ]] ; do
key="$1"
case $key in
-help|-h)
display_usage
exit
;;
-test)
run_tests=1
;;
*)
echo "$0: invalid option: $key"
display_usage
exit $EX_USAGE
esac
shift # past the processed argument
done
# Check if Node.JS/npm is installed.
type npm >/dev/null ||\
fail "Build environment setup error detected! Please ensure Node.js is installed!"
build || fail "Compilation failed."
echo "Typescript compilation successful."
if (( run_tests )); then
npm test || fail "Tests failed"
fi

View file

@ -123,12 +123,12 @@
9A3B14D3229370B20052A11F /* InstalledLanguagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3B14D1229370B20052A11F /* InstalledLanguagesViewController.swift */; };
9A4609972241B39B00B0BFD1 /* LexicalModelInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4609962241B39B00B0BFD1 /* LexicalModelInfoViewController.swift */; };
9A4609992242047400B0BFD1 /* LexicalModelAPICall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4609982242047400B0BFD1 /* LexicalModelAPICall.swift */; };
9A60764422893A4E003BCFBA /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A60764322893A4E003BCFBA /* SettingsViewController.swift */; };
9A7EEFA222DCF64F00877C22 /* LanguageSpecificViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7EEFA122DCF64F00877C22 /* LanguageSpecificViewController.swift */; };
9A5091B122B418140094B99C /* KeyboardCommandStructs.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE67D960228A6F190029F2B5 /* KeyboardCommandStructs.swift */; };
9A5091B222B418A90094B99C /* KeyboardPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C62161F67CFB800268D03 /* KeyboardPickerViewController.swift */; };
9A5091B322B418CA0094B99C /* KeyboardInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C62141F67C8D500268D03 /* KeyboardInfoViewController.swift */; };
9A5091B422B419B10094B99C /* LanguageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04C2A6C1F6B7D9A00BA42B6 /* LanguageViewController.swift */; };
9A60764422893A4E003BCFBA /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A60764322893A4E003BCFBA /* SettingsViewController.swift */; };
9A7EEFA222DCF64F00877C22 /* LanguageSpecificViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7EEFA122DCF64F00877C22 /* LanguageSpecificViewController.swift */; };
9A862D2822B429FB005FE26D /* KeyboardNameTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C62121F67C31100268D03 /* KeyboardNameTableViewCell.swift */; };
9A862D2922B42A0C005FE26D /* LanguageDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C092D8381F6A70C8005C5485 /* LanguageDetailViewController.swift */; };
9A9CB08022416E5400231FB9 /* LexicalModelPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CB07F22416E5400231FB9 /* LexicalModelPickerViewController.swift */; };
@ -139,6 +139,11 @@
9AD4F53D229F85AC007992D3 /* LanguageSettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9AD4F53B229F85AC007992D3 /* LanguageSettingsViewController.xib */; };
9AD4F53E22A8A25B007992D3 /* LanguageSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD4F53A229F85AC007992D3 /* LanguageSettingsViewController.swift */; };
9AD4F53F22A8A286007992D3 /* LanguageSettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9AD4F53B229F85AC007992D3 /* LanguageSettingsViewController.xib */; };
9ADC459A22E186F5004C78C6 /* LanguageSpecificViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7EEFA122DCF64F00877C22 /* LanguageSpecificViewController.swift */; };
9ADC459D22E1895D004C78C6 /* LanguageLMDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADC459B22E1895D004C78C6 /* LanguageLMDetailViewController.swift */; };
9ADC459E22E1895D004C78C6 /* LanguageLMDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADC459B22E1895D004C78C6 /* LanguageLMDetailViewController.swift */; };
9ADC459F22E1895D004C78C6 /* LanguageLMDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9ADC459C22E1895D004C78C6 /* LanguageLMDetailViewController.xib */; };
9ADC45A022E1895D004C78C6 /* LanguageLMDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9ADC459C22E1895D004C78C6 /* LanguageLMDetailViewController.xib */; };
C007C4651F9F52D8006461B9 /* LanguagesAPICall.swift in Sources */ = {isa = PBXBuildFile; fileRef = C007C4641F9F52D8006461B9 /* LanguagesAPICall.swift */; };
C024C9941FA6EB470060583B /* NotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C9931FA6EB470060583B /* NotificationName.swift */; };
C024C9961FA6EC650060583B /* NotificationCenter+Typed.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C9951FA6EC650060583B /* NotificationCenter+Typed.swift */; };
@ -335,8 +340,8 @@
9A079E3F223B602B00581263 /* LexicalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModel.swift; sourceTree = "<group>"; };
9A079E42223B61AE00581263 /* FullLexicalModelID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullLexicalModelID.swift; sourceTree = "<group>"; };
9A082558227589360051EBB0 /* Formatter+ISODateExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Formatter+ISODateExtension.swift"; sourceTree = "<group>"; };
9A3B14D1229370B20052A11F /* InstalledLanguagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InstalledLanguagesViewController.swift; path = Settings/InstalledLanguagesViewController.swift; sourceTree = "<group>"; };
9A0FC9FC22D66D9E00D33F86 /* Reachability.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reachability.framework; path = ../../Carthage/Build/iOS/Reachability.framework; sourceTree = "<group>"; };
9A3B14D1229370B20052A11F /* InstalledLanguagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InstalledLanguagesViewController.swift; path = Settings/InstalledLanguagesViewController.swift; sourceTree = "<group>"; };
9A4609962241B39B00B0BFD1 /* LexicalModelInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModelInfoViewController.swift; sourceTree = "<group>"; };
9A4609982242047400B0BFD1 /* LexicalModelAPICall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModelAPICall.swift; sourceTree = "<group>"; };
9A60763C22892485003BCFBA /* Settings.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Settings.storyboard; path = Settings/Settings.storyboard; sourceTree = "<group>"; };
@ -348,6 +353,8 @@
9A9CB0852241717C00231FB9 /* LexicalModelRepositoryDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModelRepositoryDelegate.swift; sourceTree = "<group>"; };
9AD4F53A229F85AC007992D3 /* LanguageSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LanguageSettingsViewController.swift; path = Settings/LanguageSettingsViewController.swift; sourceTree = "<group>"; };
9AD4F53B229F85AC007992D3 /* LanguageSettingsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = LanguageSettingsViewController.xib; path = Settings/LanguageSettingsViewController.xib; sourceTree = "<group>"; };
9ADC459B22E1895D004C78C6 /* LanguageLMDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageLMDetailViewController.swift; sourceTree = "<group>"; };
9ADC459C22E1895D004C78C6 /* LanguageLMDetailViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LanguageLMDetailViewController.xib; sourceTree = "<group>"; };
C007C4641F9F52D8006461B9 /* LanguagesAPICall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguagesAPICall.swift; sourceTree = "<group>"; };
C024C9931FA6EB470060583B /* NotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationName.swift; sourceTree = "<group>"; };
C024C9951FA6EC650060583B /* NotificationCenter+Typed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationCenter+Typed.swift"; sourceTree = "<group>"; };
@ -801,6 +808,8 @@
C08C62161F67CFB800268D03 /* KeyboardPickerViewController.swift */,
9A9CB07F22416E5400231FB9 /* LexicalModelPickerViewController.swift */,
C092D8381F6A70C8005C5485 /* LanguageDetailViewController.swift */,
9ADC459B22E1895D004C78C6 /* LanguageLMDetailViewController.swift */,
9ADC459C22E1895D004C78C6 /* LanguageLMDetailViewController.xib */,
C04C2A6C1F6B7D9A00BA42B6 /* LanguageViewController.swift */,
9A7EEFA122DCF64F00877C22 /* LanguageSpecificViewController.swift */,
98C0B11817A22DB5007ECAFC /* PickerButtons */,
@ -1030,6 +1039,7 @@
buildActionMask = 2147483647;
files = (
9AD4F53F22A8A286007992D3 /* LanguageSettingsViewController.xib in Resources */,
9ADC45A022E1895D004C78C6 /* LanguageLMDetailViewController.xib in Resources */,
9A0FCA0922D7C58B00D33F86 /* Keyman.bundle in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1039,6 +1049,7 @@
buildActionMask = 2147483647;
files = (
C06D37601F82095200F61AE0 /* Keyman.bundle in Resources */,
9ADC459F22E1895D004C78C6 /* LanguageLMDetailViewController.xib in Resources */,
9AD4F53D229F85AC007992D3 /* LanguageSettingsViewController.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1145,6 +1156,7 @@
9A31E2C8224AE87600D9A491 /* RegisteredFont.swift in Sources */,
9A5091B422B419B10094B99C /* LanguageViewController.swift in Sources */,
9A079E352236235700581263 /* Version.swift in Sources */,
9ADC459E22E1895D004C78C6 /* LanguageLMDetailViewController.swift in Sources */,
9A079DDC2231A11000581263 /* Font.swift in Sources */,
9A079E49223F176100581263 /* String+Helpers.swift in Sources */,
9A079DE82236138800581263 /* HTTPDownloadRequest.swift in Sources */,
@ -1170,7 +1182,7 @@
9A5091B222B418A90094B99C /* KeyboardPickerViewController.swift in Sources */,
9A31E2C2224AAD9A00D9A491 /* Notifications.swift in Sources */,
9A079DDD2231A11C00581263 /* InstallableKeyboard.swift in Sources */,
9A7EEFA222DCF64F00877C22 /* LanguageSpecificViewController.swift in Sources */,
9ADC459A22E186F5004C78C6 /* LanguageSpecificViewController.swift in Sources */,
9A079DD2223194B100581263 /* KeymanEngineTests.swift in Sources */,
9A079E41223B602B00581263 /* LexicalModel.swift in Sources */,
9A3B14C0228F59490052A11F /* SettingsViewController.swift in Sources */,
@ -1241,6 +1253,7 @@
C06D37381F81F5C400F61AE0 /* SubKeysView.swift in Sources */,
C0A93A541F8B21240079948B /* Manager.swift in Sources */,
C024C9981FA6F2340060583B /* NotificationObserver.swift in Sources */,
9ADC459D22E1895D004C78C6 /* LanguageLMDetailViewController.swift in Sources */,
C06D373E1F81F5C400F61AE0 /* KeyboardInfoViewController.swift in Sources */,
C0B901AA1FA1AFC200764EB8 /* UserDefaults+Types.swift in Sources */,
9A079E372238680700581263 /* KMPLexicalModel.swift in Sources */,

View file

@ -179,8 +179,10 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
open override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Necessary for existing infrastructure to resend info for the keyboard after reloading
// as system keyboard.
Manager.shared.shouldReloadKeyboard = true
// as system keyboard. Do NOT perform if in-app, as this unnecessarily resets the WebView.
if(Manager.shared.isSystemKeyboard) {
Manager.shared.shouldReloadKeyboard = true
}
}
open override func textDidChange(_ textInput: UITextInput?) {

View file

@ -29,9 +29,9 @@ class KeyboardNameTableViewCell: UITableViewCell, UIAlertViewDelegate {
}
self.accessoryType = accessoryType
let isSelectedView = viewWithTag(cellSelectedTag)
let selectedView = viewWithTag(cellSelectedTag)
if isSelected {
if isSelectedView == nil {
if selectedView == nil {
let view = UIView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height))
view.tag = cellSelectedTag
view.backgroundColor = UIColor.green
@ -39,7 +39,7 @@ class KeyboardNameTableViewCell: UITableViewCell, UIAlertViewDelegate {
addSubview(view)
}
} else {
isSelectedView?.removeFromSuperview()
selectedView?.removeFromSuperview()
}
}
}

View file

@ -50,9 +50,11 @@ class LanguageDetailViewController: UITableViewController, UIAlertViewDelegate {
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
log.info("didAppear: LanguageDetailViewController (actually willAppear)")
log.info("willAppear: LanguageDetailViewController")
}
// MARK: - Table view data source UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return language.keyboards!.count
}

View file

@ -0,0 +1,201 @@
//
// LanguageLMDetailViewController.swift
// KeymanEngine
//
// Created by Randy Boring on 7/19/19.
// Copyright © 2019 SIL International. All rights reserved.
//
import UIKit
private let toolbarButtonTag = 100
private let toolbarLabelTag = 101
private let toolbarActivityIndicatorTag = 102
class LanguageLMDetailViewController: UITableViewController, UIAlertViewDelegate {
private var userLexicalModels: [String: InstallableLexicalModel] = [:]
private var isUpdate = false // unused currently, may be used when we switch to HTTPDownloader
private let language: Language
public var lexicalModels: [LexicalModel]? = nil
private var lexicalModelDownloadStartedObserver: NotificationObserver?
//NOTE: there is no need for a CompletedObserver, as our parent LexicalModelPickerViewController
// is registered for that and deals with it by popping us out to root.
private var lexicalModelDownloadFailedObserver: NotificationObserver?
init(language: Language) {
self.language = language
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
loadUserLexicalModels()
tableView.dataSource = self
}
override func viewDidLoad() {
super.viewDidLoad()
lexicalModelDownloadStartedObserver = NotificationCenter.default.addObserver(
forName: Notifications.lexicalModelDownloadStarted,
observer: self,
function: LanguageLMDetailViewController.lexicalModelDownloadStarted)
lexicalModelDownloadFailedObserver = NotificationCenter.default.addObserver(
forName: Notifications.lexicalModelDownloadFailed,
observer: self,
function: LanguageLMDetailViewController.lexicalModelDownloadFailed)
log.info("viewDidLoad: LanguageLMDetailViewController (registered for lexicalModelDownloadStarted)")
}
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
log.info("willAppear: LanguageLMDetailViewController")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
log.info("didAppear: LanguageLMDetailViewController")
navigationController?.setToolbarHidden(true, animated: true)
}
// MARK: - Table view data source UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return lexicalModels!.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
if let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) {
return cell
}
let cell = KeyboardNameTableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
let selectionColor = UIView()
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0)
cell.selectedBackgroundView = selectionColor
return cell
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lexicalModel = lexicalModels![indexPath.section]
let cell = cell as! KeyboardNameTableViewCell
cell.indexPath = indexPath
cell.textLabel?.text = lexicalModel.name
if isAdded(languageID: language.id, lexicalModelID: lexicalModel.id) {
cell.accessoryType = .checkmark
cell.isUserInteractionEnabled = false
cell.textLabel?.isEnabled = false
cell.detailTextLabel?.isEnabled = false
} else {
cell.accessoryType = .none
cell.isUserInteractionEnabled = true
cell.textLabel?.isEnabled = true
cell.detailTextLabel?.isEnabled = true
}
let kbState = Manager.shared.stateForLexicalModel(withID: lexicalModel.id)
cell.setKeyboardState(kbState, selected: false, defaultAccessoryType: cell.accessoryType)
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.isSelected = false
let lexicalModelIndex = indexPath.section
let lexicalModel = lexicalModels![lexicalModelIndex]
let state = Manager.shared.stateForLexicalModel(withID: lexicalModel.id)
if state != .downloading {
if state == .needsDownload {
isUpdate = false
} else {
isUpdate = true
}
let alertController = UIAlertController(title: "\(language.name): \(lexicalModel.name)",
message: "Would you like to download this dictionary?",
preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Cancel",
style: UIAlertActionStyle.cancel,
handler: nil))
alertController.addAction(UIAlertAction(title: "Download",
style: UIAlertActionStyle.default,
handler: {_ in self.downloadHandler(lexicalModelIndex)} ))
self.present(alertController, animated: true, completion: nil)
}
}
func downloadHandler(_ lexicalModelIndex: Int) {
Manager.shared.downloadLexicalModelPackage(string: (lexicalModels?[lexicalModelIndex].packageFilename)!)
}
private func lexicalModelDownloadStarted() {
log.info("lexicalModelDownloadStarted: LanguageLMDetailViewController")
view.isUserInteractionEnabled = false
navigationItem.setHidesBackButton(true, animated: true)
let toolbarFrame = navigationController!.toolbar.frame
let labelFrame = CGRect(origin: toolbarFrame.origin,
size: CGSize(width: toolbarFrame.width * 0.95, height: toolbarFrame.height * 0.7))
let label = UILabel(frame: labelFrame)
label.backgroundColor = UIColor.clear
label.textColor = UIColor.white
label.textAlignment = .center
label.center = CGPoint(x: toolbarFrame.width * 0.5, y: toolbarFrame.height * 0.5)
label.text = "Downloading\u{2026}"
label.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin,
.flexibleBottomMargin, .flexibleWidth, .flexibleHeight]
label.tag = toolbarLabelTag
let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicatorView.center = CGPoint(x: toolbarFrame.width - indicatorView.frame.width,
y: toolbarFrame.height * 0.5)
indicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin, .flexibleBottomMargin]
indicatorView.tag = toolbarActivityIndicatorTag
indicatorView.startAnimating()
navigationController?.toolbar.viewWithTag(toolbarButtonTag)?.removeFromSuperview()
navigationController?.toolbar.viewWithTag(toolbarLabelTag)?.removeFromSuperview()
navigationController?.toolbar.viewWithTag(toolbarActivityIndicatorTag)?.removeFromSuperview()
navigationController?.toolbar.addSubview(label)
navigationController?.toolbar.addSubview(indicatorView)
navigationController?.setToolbarHidden(false, animated: true)
}
private func lexicalModelDownloadFailed() {
log.info("lexicalModelDownloadFailed: LanguageLMDetailViewController")
view.isUserInteractionEnabled = true
navigationItem.setHidesBackButton(false, animated: true)
}
private func loadUserLexicalModels() {
guard let userLmList = Storage.active.userDefaults.userLexicalModels, !userLmList.isEmpty else {
userLexicalModels = [:]
return
}
userLexicalModels = [:]
for lm in userLmList {
let dictKey = "\(lm.languageID)_\(lm.id)"
userLexicalModels[dictKey] = lm
}
}
private func isAdded(languageID: String?, lexicalModelID: String?) -> Bool {
guard let languageID = languageID, let lexicalModelID = lexicalModelID else {
return false
}
return userLexicalModels["\(languageID)_\(lexicalModelID)"] != nil
}
}

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="LanguageLMDetailViewController" customModuleProvider="target">
<connections>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" bouncesZoom="NO" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="vLr-E1-eTs"/>
<connections>
<outlet property="dataSource" destination="-1" id="Tng-2m-Rnh"/>
<outlet property="delegate" destination="-1" id="9aC-8N-iBw"/>
</connections>
</tableView>
</objects>
</document>

View file

@ -101,7 +101,9 @@ class LanguageViewController: UITableViewController, UIAlertViewDelegate {
} else {
log.info("didAppear: LanguageViewController")
}
}
}
// MARK: - Table view data source UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return languages.count
@ -242,7 +244,7 @@ class LanguageViewController: UITableViewController, UIAlertViewDelegate {
func downloadHandler(_ keyboardIndex: Int) {
let language = languages[selectedSection]
let keyboard = language.keyboards![keyboardIndex]
Manager.shared.downloadKeyboard(withID: keyboard.id, languageID: language.id, isUpdate: isUpdate)
Manager.shared.downloadKeyboard(withID: keyboard.id, languageID: language.id, isUpdate: isUpdate)
}
private func keyboardDownloadStarted() {

View file

@ -24,7 +24,7 @@ class LexicalModelInfoViewController: UITableViewController, UIAlertViewDelegate
infoArray = [[String: String]]()
infoArray.append([
"title": "Model version",
"title": "Dictionary version",
"subtitle": lexicalModelVersion
])
@ -35,7 +35,7 @@ class LexicalModelInfoViewController: UITableViewController, UIAlertViewDelegate
])
}
infoArray.append([
"title": "Uninstall model",
"title": "Uninstall dictionary",
"subtitle": ""
])
}
@ -135,7 +135,7 @@ class LexicalModelInfoViewController: UITableViewController, UIAlertViewDelegate
}
private func showDeleteLexicalModel() {
let alertController = UIAlertController(title: title ?? "", message: "Would you like to delete this lexical model?",
let alertController = UIAlertController(title: title ?? "", message: "Would you like to delete this dictionary?",
preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Cancel",
style: UIAlertActionStyle.cancel,

View file

@ -15,7 +15,7 @@ private let toolbarActivityIndicatorTag = 102
class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelegate {
private var userLexicalModels: [InstallableLexicalModel] = [InstallableLexicalModel]()
public var language: Language? = nil
public var language: Language!
private var updateQueue: [InstallableLexicalModel]?
private var _isDoneButtonEnabled = false
private var isDidUpdateCheck = false
@ -24,17 +24,19 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
private var lexicalModelDownloadCompletedObserver: NotificationObserver?
private var lexicalModelDownloadFailedObserver: NotificationObserver?
public init(_ language: Language) {
self.language = language
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
if let langName = language?.name {
title = "\(langName) Lexical Models"
} else {
title = "Lexical Models"
}
if language == nil {
setIsDoneButtonEnabled(false)
}
title = "\(language.name) Dictionaries"
isDidUpdateCheck = false
updateQueue = nil
if Manager.shared.canAddNewLexicalModels {
@ -137,7 +139,6 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
if Manager.shared.removeLexicalModel(at: globalIndex) {
loadUserLexicalModels()
}
setIsDoneButtonEnabled(true)
}
override func tableView(_ tableView: UITableView,
@ -146,9 +147,6 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
}
func showLexicalModelInfoView(with index: Int) {
if language == nil {
setIsDoneButtonEnabled(true)
}
let lm = userLexicalModels[index]
let version = lm.version
@ -178,7 +176,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
if Manager.shared.currentLexicalModelID == lm.fullID {
cell.selectionStyle = .blue
cell.isSelected = true
cell.accessoryType = .checkmark
cell.accessoryType = .detailDisclosureButton
} else {
cell.selectionStyle = .none
cell.isSelected = false
@ -236,14 +234,14 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
}
updateQueue = nil
let label = navigationController?.toolbar?.viewWithTag(toolbarLabelTag) as? UILabel
label?.text = "Lexical models successfully updated!"
label?.text = "Dictionaries successfully updated!"
navigationController?.toolbar?.viewWithTag(toolbarActivityIndicatorTag)?.removeFromSuperview()
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.hideToolbarDelayed),
userInfo: nil, repeats: false)
}
} else {
let label = navigationController?.toolbar?.viewWithTag(toolbarLabelTag) as? UILabel
label?.text = "Lexical model successfully downloaded!"
label?.text = "Dictionary successfully downloaded!"
navigationController?.toolbar?.viewWithTag(toolbarActivityIndicatorTag)?.removeFromSuperview()
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.hideToolbarDelayed),
userInfo: nil, repeats: false)
@ -276,9 +274,9 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
let title: String
if view == navigationController?.topViewController?.view {
updateQueue = nil
title = "Lexical model Update Error"
title = "Dictionary Update Error"
} else {
title = "Lexical model Download Error"
title = "Dictionary Download Error"
}
navigationController?.setToolbarHidden(true, animated: true)
@ -399,8 +397,8 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
}
private func scroll(toSelectedLexicalModel animated: Bool) {
let index = userLexicalModels.index { kb in
return Manager.shared.currentLexicalModelID == kb.fullID
let index = userLexicalModels.index { lm in
return Manager.shared.currentLexicalModelID == lm.fullID
}
if let index = index {
@ -428,13 +426,32 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
}
func showAddLexicalModel() {
let button: UIButton? = (navigationController?.toolbar?.viewWithTag(toolbarButtonTag) as? UIButton)
button?.isEnabled = false
let vc = LanguageViewController(Manager.shared.apiLexicalModelRepository) //may need to be different for models
navigationController?.pushViewController(vc, animated: true)
if language == nil {
setIsDoneButtonEnabled(true)
//get list of lexical models for this languageID and show it
func listCompletionHandler(lexicalModels: [LexicalModel]?, error: Error?) -> Void {
if let error = error {
log.info("Failed to fetch lexical model list for "+language.id+". error: "+(error as! String))
DispatchQueue.main.async {
self.lexicalModelDownloadFailed(LexicalModelDownloadFailedNotification(lmOrLanguageID: self.language.id, error: error))
}
} else if nil == lexicalModels {
log.info("No lexical models available for language \(language.id) (nil)")
} else if 0 == lexicalModels?.count {
log.info("No lexical models available for language \(language.id) (empty)")
} else {
log.info("Fetched lexical model list for "+language.id+".")
// show the list of lexical models (on the main thread)
DispatchQueue.main.async {
let button: UIButton? = (self.navigationController?.toolbar?.viewWithTag(toolbarButtonTag) as? UIButton)
button?.isEnabled = false
let vc = LanguageLMDetailViewController(language: self.language)
vc.lexicalModels = lexicalModels!
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
Manager.shared.apiLexicalModelRepository.fetchList(languageID: language.id, completionHandler: listCompletionHandler)
}
}

View file

@ -17,7 +17,7 @@ typealias FetchKeyboardsBlock = ([String: Any]?) -> Void
// MARK: - Constants
// Possible states that a keyboard can be in
// Possible states that a keyboard or lexical model can be in
public enum KeyboardState {
case needsDownload
case needsUpdate
@ -26,15 +26,6 @@ public enum KeyboardState {
case none
}
// Possible states that a lexical model can be in
public enum LexicalModelState {
case needsDownload
case needsUpdate
case upToDate
case downloading
case none
}
public enum VibrationSupport {
case none // Has no vibrator
case basic // Has only the basic 0.4 sec long vibration
@ -313,6 +304,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
// If we have a lexical model for the keyboard's language, activate it.
if let preferred_model = preferredLexicalModel(userDefaults, forLanguage: kb.languageID) {
_ = Manager.shared.registerLexicalModel(preferred_model)
} else if let first_model = userDefaults.userLexicalModels?.first(where: { $0.languageID == kb.languageID }) {
_ = Manager.shared.registerLexicalModel(first_model)
}
return true
@ -482,30 +475,50 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
}
/// Removes the lexical model at index from the lexical models list if it exists.
/// - Returns: The lexical model exists and was removed
public func removeLexicalModel(at index: Int) -> Bool {
let userData = Storage.active.userDefaults
public func removeLexicalModelFromUserList(userDefs ud: UserDefaults, at index: Int) -> InstallableLexicalModel? {
// If user defaults for lexical models list does not exist, do nothing.
guard var userLexicalModels = userData.userLexicalModels else {
return false
guard var userLexicalModels = ud.userLexicalModels else {
return nil
}
guard index < userLexicalModels.count else {
return false
return nil
}
let lm = userLexicalModels[index]
log.info("Removing lexical model with ID \(lm.id) and languageID \(lm.languageID) from user list of all models")
userLexicalModels.remove(at: index)
userData.userLexicalModels = userLexicalModels
userData.set([Date()], forKey: Key.synchronizeSWLexicalModel)
userData.synchronize()
log.info("Removing lexical model with ID \(lm.id) and languageID \(lm.languageID)")
ud.userLexicalModels = userLexicalModels
ud.set([Date()], forKey: Key.synchronizeSWLexicalModel)
ud.synchronize()
return lm
}
/// Removes the lexical model at index from the lexical models list if it exists.
public func removeLexicalModelFromLanguagePreference(userDefs ud: UserDefaults, _ lm: InstallableLexicalModel) {
log.info("Removing lexical model with ID \(lm.id) and languageID \(lm.languageID) from per-language prefs")
ud.set(preferredLexicalModelID: nil, forKey: lm.languageID)
}
/// Removes the lexical model at index from the lexical models list if it exists.
/// - Returns: The lexical model exists and was removed
public func removeLexicalModel(at index: Int) -> Bool {
let userData = Storage.active.userDefaults
guard let lm = removeLexicalModelFromUserList(userDefs: userData, at: index) else {
return false
}
removeLexicalModelFromLanguagePreference(userDefs: userData, lm)
// Set a new lexical model if deleting the current one
let userLexicalModels = userData.userLexicalModels! //removeLexicalModelFromUserList fails above if this is not present
if lm.fullID == currentLexicalModelID {
_ = registerLexicalModel(userLexicalModels[0])
if let first_lm = userLexicalModels.first(where: {$0.languageID == lm.languageID}) {
_ = registerLexicalModel(first_lm)
} else {
log.info("no more lexical models available for language \(lm.fullID)")
currentLexicalModelID = nil
}
}
if !userLexicalModels.contains(where: { $0.id == lm.id }) {
@ -1076,6 +1089,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
log.info("Failed to fetch lexical model list for "+languageID+". error: "+(error as! String))
self.downloadFailed(forLanguageID: languageID, error: error) //???forKeyboards
} else if nil == lexicalModels {
//TODO: put up an alert instead
log.info("No lexical models available for language \(languageID) (nil)")
} else if 0 == lexicalModels?.count {
log.info("No lexical models available for language \(languageID) (empty)")
@ -1276,7 +1290,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
}
/// - Returns: The current state for a lexical model
public func stateForLexicalModel(withID lexicalModelID: String) -> LexicalModelState {
//TODO: rename KeyboardState to ResourceState? so it can be used with both keybaoards and lexical models without confusion
public func stateForLexicalModel(withID lexicalModelID: String) -> KeyboardState {
if lexicalModelIdForCurrentRequest() == lexicalModelID {
return .downloading
}

View file

@ -9,7 +9,7 @@
import Foundation
struct InstallableConstants {
static let defaultVersion = "0.1.0"
static let defaultVersion = "1.0"
}
/// Mainly differs from the API `LexicalModel` by having an associated language.

View file

@ -12,7 +12,8 @@ public class LexicalModelKeymanPackage : KeymanPackage {
public var models : [KMPLexicalModel]!
public override func defaultInfoHtml() -> String {
var str = "Found lexical models in package:<br/>"
let count = models.count
var str = "Found "+(count > 1 ? "\(count) dictionaries" : "dictionary")+" in package:<br/>"
for model in models {
str += model.lexicalModelId! + "<br/>"
}

View file

@ -20,7 +20,6 @@ class InstalledLanguagesViewController: UITableViewController, UIAlertViewDelega
private var sectionIndexTitles: [String] = []
private var indices: [Int] = []
private var selectedSection = 0
private var isUpdate = false
private var installedLanguages: [String: Language]
private var languages: [Language] = []
private let keyboardRepository: KeyboardRepository?
@ -165,49 +164,13 @@ class InstalledLanguagesViewController: UITableViewController, UIAlertViewDelega
}
cell.textLabel?.text = language.name
if cell.accessoryType == .disclosureIndicator {
return
}
guard let cell = cell as? KeyboardNameTableViewCell else {
return
}
let keyboardID = language.keyboards![0].id
cell.accessoryType = .none
cell.isUserInteractionEnabled = true
cell.textLabel?.isEnabled = true
cell.detailTextLabel?.isEnabled = true
let kbState = Manager.shared.stateForKeyboard(withID: keyboardID)
cell.setKeyboardState(kbState, selected: false, defaultAccessoryType: cell.accessoryType)
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedSection = indexPath.section
tableView.cellForRow(at: indexPath)?.isSelected = false
if tableView.cellForRow(at: indexPath)?.accessoryType == .disclosureIndicator {
let title = tableView.cellForRow(at: indexPath)?.textLabel?.text ?? ""
showLanguageSettingsView(title: title, languageIndex: indexPath.section)
return
}
let language = languages[indexPath.section]
let keyboardIndex = 0;
let keyboard = language.keyboards![keyboardIndex]
//TODO: launch real Language Settings controller here
let languageSettingsController = UIAlertController(title: "\(language.name): \(keyboard.name)",
message: "This should be Language Settings",
preferredStyle: UIAlertControllerStyle.alert)
languageSettingsController.addAction(UIAlertAction(title: "Cancel",
style: UIAlertActionStyle.cancel,
handler: nil))
// languageSettingsController.addAction(UIAlertAction(title: "Download",
// style: UIAlertActionStyle.default,
// handler: {_ in self.downloadHandler(keyboardIndex)} ))
self.present(languageSettingsController, animated: true, completion: nil)
let title = tableView.cellForRow(at: indexPath)?.textLabel?.text ?? ""
showLanguageSettingsView(title: title, languageIndex: indexPath.section)
}
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
@ -230,7 +193,7 @@ class InstalledLanguagesViewController: UITableViewController, UIAlertViewDelega
func downloadHandler(_ keyboardIndex: Int) {
let language = languages[selectedSection]
let keyboard = language.keyboards![keyboardIndex]
Manager.shared.downloadKeyboard(withID: keyboard.id, languageID: language.id, isUpdate: isUpdate)
Manager.shared.downloadKeyboard(withID: keyboard.id, languageID: language.id, isUpdate: false)
}
private func restoreNavigation() {
@ -336,7 +299,7 @@ class InstalledLanguagesViewController: UITableViewController, UIAlertViewDelega
log.info("lexicalModelDownloadFailed: InstalledLanguagesViewController")
restoreNavigation()
let title = "Lexical Model Download Error"
let title = "Dictionary Download Error"
navigationController?.setToolbarHidden(true, animated: true)
let alertController = UIAlertController(title: title, message: "",

View file

@ -153,9 +153,9 @@ class LanguageSettingsViewController: UITableViewController {
var title: String
switch (section) {
case 0:
title = "keyboards"
title = "Keyboards"
case 1:
title = "language settings"
title = "Language settings"
default:
title = "unknown header"
}
@ -199,19 +199,19 @@ class LanguageSettingsViewController: UITableViewController {
case 1:
cell.textLabel?.text = "Enable corrections"
case 2:
cell.textLabel?.text = "Model"
cell.textLabel?.text = "Dictionaries"
cell.accessoryType = .disclosureIndicator
if let modelCt = language.lexicalModels?.count {
switch modelCt {
case 0:
cell.detailTextLabel?.text = "no models installed"
cell.detailTextLabel?.text = "no dictionaries installed"
case 1:
cell.detailTextLabel?.text = "one model installed"
cell.detailTextLabel?.text = "\(language.lexicalModels![0].name)"
default:
cell.detailTextLabel?.text = "\(modelCt) models installed"
cell.detailTextLabel?.text = "\(modelCt) dictionaries installed"
}
} else {
cell.detailTextLabel?.text = "no models installed"
cell.detailTextLabel?.text = "no dictionaries installed"
}
case 3: // future
cell.textLabel?.text = "Manage dictionary"
@ -297,7 +297,7 @@ class LanguageSettingsViewController: UITableViewController {
func showLexicalModelsView() {
//LanguageLexicalModelPickerViewController? (should show just the models for this language)
let lmListView = LexicalModelPickerViewController()
let lmListView = LexicalModelPickerViewController(self.language)
lmListView.language = self.language
navigationController?.pushViewController(lmListView, animated: true)
}

View file

@ -16,7 +16,7 @@ open class SettingsViewController: UITableViewController {
super.viewWillAppear(animated)
loadUserLanguages()
log.info("didAppear: SettingsViewController (actually willAppear)")
log.info("willAppear: SettingsViewController")
}
override open func viewDidLoad() {
@ -256,7 +256,13 @@ open class SettingsViewController: UITableViewController {
}
// MARK: - language access -
private func installed2API(_ installedList: [InstallableLexicalModel]) -> [LexicalModel] {
/** returns an array of LexicalModel created from an array of InstallableLexicalModel
* @param installedList: The InstallableLexicalModel are probably already installed
* The returned LexicalModels are not complete (usually we go the other way round)
* but sufficient for future API calls
*/
public static func installed2API(_ installedList: [InstallableLexicalModel]) -> [LexicalModel] {
var returnList = [LexicalModel]()
for ilm in installedList {
returnList.append(LexicalModel(id: ilm.id, name: ilm.name, license: "", version: ilm.version, languages: [], authorName: "", fileSize: 0, filename: "no filename", sourcePath: nil, authorEmail: nil, description: nil, packageFileSize: 0, packageFilename: "", packageIncludes: nil, isDefault: false, lastModified: nil, minKeymanVersion: nil))
@ -283,7 +289,7 @@ open class SettingsViewController: UITableViewController {
}
let userDefaults : UserDefaults = Storage.active.userDefaults
let lmListInstalled: [InstallableLexicalModel] = userDefaults.userLexicalModelsForLanguage(languageID: l) ?? []
let lmList = installed2API(lmListInstalled)
let lmList = SettingsViewController.installed2API(lmListInstalled)
keyboardLanguages[l] = Language(name: k.languageName, id: k.languageID, keyboards: kbds, lexicalModels: lmList, font: nil, oskFont: nil)
}
// there shouldn't be any lexical models for languages that don't have a keyboard installed

46
web/package-lock.json generated
View file

@ -73,13 +73,13 @@
"arraybuffer.slice": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
"integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=",
"dev": true
},
"assertion-error": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
"integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=",
"dev": true
},
"async": {
@ -94,7 +94,7 @@
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==",
"integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=",
"dev": true
},
"backo2": {
@ -367,7 +367,7 @@
"color-convert": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=",
"dev": true,
"requires": {
"color-name": "^1.1.1"
@ -424,7 +424,7 @@
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=",
"dev": true
},
"cookie": {
@ -484,7 +484,7 @@
"deep-eql": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
"integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=",
"dev": true,
"requires": {
"type-detect": "^4.0.0"
@ -872,7 +872,7 @@
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@ -1217,7 +1217,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
"dev": true
}
}
@ -1242,7 +1242,7 @@
"karma-chrome-launcher": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz",
"integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==",
"integrity": "sha1-zxudBxNswY/iOTJ9JGVMPbw2is8=",
"dev": true,
"requires": {
"fs-access": "^1.0.0",
@ -1252,7 +1252,7 @@
"karma-edge-launcher": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/karma-edge-launcher/-/karma-edge-launcher-0.4.2.tgz",
"integrity": "sha512-YAJZb1fmRcxNhMIWYsjLuxwODBjh2cSHgTW/jkVmdpGguJjLbs9ZgIK/tEJsMQcBLUkO+yO4LBbqYxqgGW2HIw==",
"integrity": "sha1-PZUpsJsTyQnF887uEtAOf5qYmz0=",
"dev": true,
"requires": {
"edge-launcher": "1.2.2"
@ -1261,7 +1261,7 @@
"karma-firefox-launcher": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz",
"integrity": "sha512-LbZ5/XlIXLeQ3cqnCbYLn+rOVhuMIK9aZwlP6eOLGzWdo1UVp7t6CN3DP4SafiRLjexKwHeKHDm0c38Mtd3VxA==",
"integrity": "sha1-LEcDBFLwRTHrfRPU/HZpYwu5Mzk=",
"dev": true
},
"karma-fixture": {
@ -1328,7 +1328,7 @@
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
@ -1374,7 +1374,7 @@
"karma-teamcity-reporter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/karma-teamcity-reporter/-/karma-teamcity-reporter-1.1.0.tgz",
"integrity": "sha512-Ca1uhHGtNqUuzsnW3I+QykNuS/jF9vdxnIrkbLCVJRunCc6yWJq+ai1UobQT13j0e3JVUOf0mKo3QHZ6A6mG9Q==",
"integrity": "sha1-eYD33JHmqwhH4SKt4Ls0x8b0KBY=",
"dev": true
},
"linkify-it": {
@ -1405,7 +1405,7 @@
"log-symbols": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
"integrity": "sha1-V0Dhxdbw39pK2TI7UzIQfva0xAo=",
"dev": true,
"requires": {
"chalk": "^2.0.1"
@ -1414,7 +1414,7 @@
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
@ -1548,7 +1548,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
@ -1863,7 +1863,7 @@
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
@ -1954,7 +1954,7 @@
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
"integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=",
"dev": true
},
"safer-buffer": {
@ -2198,7 +2198,7 @@
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
@ -2258,7 +2258,7 @@
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
"dev": true,
"requires": {
"os-tmpdir": "~1.0.2"
@ -2328,7 +2328,7 @@
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
"integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=",
"dev": true
},
"universalify": {
@ -2418,7 +2418,7 @@
"which": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
"integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
"integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=",
"dev": true,
"requires": {
"isexe": "^2.0.0"
@ -2482,7 +2482,7 @@
"ws": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
"integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=",
"dev": true,
"requires": {
"async-limiter": "~1.0.0",