From 4b1394a54f8dfbe3e35e7b8cb08d0cbeaf3ed9d9 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 5 Apr 2024 10:26:42 +0700 Subject: [PATCH 1/7] change(web): initializes OSK with keyboard if available during init This prevents unnecessary construction of a stand-in touch layout and related DOM initialization on touch devices when fully leveraged. --- .../KMEA/app/src/main/assets/android-host.js | 4 +- web/src/app/browser/src/contextManager.ts | 4 +- web/src/app/browser/src/keymanEngine.ts | 44 ++++++++++++------- web/src/app/browser/src/viewsAnchorpoint.ts | 6 +-- web/src/app/webview/src/keymanEngine.ts | 37 +++++++++++++--- .../osk/src/config/viewConfiguration.ts | 9 ++++ .../engine/osk/src/keyboard-layout/oskKey.ts | 2 +- web/src/engine/osk/src/views/oskView.ts | 11 ++++- 8 files changed, 87 insertions(+), 30 deletions(-) diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 7736fc6c60f..eb6db5311c3 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -36,7 +36,9 @@ function init() { keyman.beepKeyboard = beepKeyboard; // Readies the keyboard stub for instant loading during the init process. - KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard())); + try { + KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard())); + } catch {}; keyman.init({ 'embeddingApp':device, diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index b095886edfd..435c8d68dfc 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -799,7 +799,9 @@ export default class ContextManager extends ContextManagerBase = this.contextManager.restoreSavedKeyboard(savedKeyboardStr); + + // Wait for the initial keyboard to load before setting the OSK; this will avoid building an + // empty OSK that we'll instantly discard after. + try { + await loadingKbd; + } catch { /* in case of failed fetch due to network error or bad URI; we must still let the OSK init. */ }; + + const firstKbdConfig = { + keyboardToActivate: this.contextManager.activeKeyboard + }; + const osk = device.touchable ? new views.AnchoredOSKView(this, firstKbdConfig) : new views.FloatingOSKView(this, firstKbdConfig); - await Promise.resolve(); + setupOskListeners(this, osk, this.contextManager); + // And, now that we have our loaded active keyboard - or failed, thus must use that default... + // Now we set the OSK in place, an act which triggers VisualKeyboard construction. + this.osk = osk; } get register(): (x: CloudQueryResult) => void { diff --git a/web/src/app/browser/src/viewsAnchorpoint.ts b/web/src/app/browser/src/viewsAnchorpoint.ts index 3df908c402e..3bc6064dbfa 100644 --- a/web/src/app/browser/src/viewsAnchorpoint.ts +++ b/web/src/app/browser/src/viewsAnchorpoint.ts @@ -17,7 +17,7 @@ function buildBaseOskConfiguration(engine: KeymanEngine) { }; class PublishedAnchoredOSKView extends AnchoredOSKView { - constructor(engine: KeymanEngine, config?: ViewConfiguration) { + constructor(engine: KeymanEngine, config?: Partial) { let finalConfig = { ...buildBaseOskConfiguration(engine), ...(config || {}) @@ -28,7 +28,7 @@ class PublishedAnchoredOSKView extends AnchoredOSKView { } class PublishedFloatingOSKView extends FloatingOSKView { - constructor(engine: KeymanEngine, config?: FloatingOSKViewConfiguration) { + constructor(engine: KeymanEngine, config?: Partial) { let finalConfig: FloatingOSKViewConfiguration = { ...buildBaseOskConfiguration(engine), ...(config || {}) @@ -39,7 +39,7 @@ class PublishedFloatingOSKView extends FloatingOSKView { } class PublishedInlineOSKView extends InlinedOSKView { - constructor(engine: KeymanEngine, config?: ViewConfiguration) { + constructor(engine: KeymanEngine, config?: Partial) { let finalConfig: ViewConfiguration = { ...buildBaseOskConfiguration(engine), ...(config || {}) diff --git a/web/src/app/webview/src/keymanEngine.ts b/web/src/app/webview/src/keymanEngine.ts index 60259443afc..f9095aa0222 100644 --- a/web/src/app/webview/src/keymanEngine.ts +++ b/web/src/app/webview/src/keymanEngine.ts @@ -1,4 +1,4 @@ -import { DefaultRules, DeviceSpec, RuleBehavior, isEmptyTransform } from '@keymanapp/keyboard-processor' +import { DefaultRules, DeviceSpec, RuleBehavior, isEmptyTransform, Keyboard } from '@keymanapp/keyboard-processor' import { KeymanEngine as KeymanEngineBase, KeyboardInterface } from 'keyman/engine/main'; import { AnchoredOSKView, ViewConfiguration, StaticActivator } from 'keyman/engine/osk'; import { getAbsoluteX, getAbsoluteY } from 'keyman/engine/dom-utils'; @@ -71,7 +71,32 @@ export default class KeymanEngine extends KeymanEngineBase {}; } this._fontFamily = lblStyle.fontFamily; diff --git a/web/src/engine/osk/src/views/oskView.ts b/web/src/engine/osk/src/views/oskView.ts index 984ba63d856..6163b8ba774 100644 --- a/web/src/engine/osk/src/views/oskView.ts +++ b/web/src/engine/osk/src/views/oskView.ts @@ -238,7 +238,7 @@ export default abstract class OSKView this._bannerController = new BannerController(this.bannerView, this.hostDevice, this.config.predictionContextManager); - this.keyboardView = this._GenerateKeyboardView(null, null); + this.keyboardView = new EmptyView(); this._Box.appendChild(this.keyboardView.element); // Install the default OSK stylesheets - but don't have it managed by the keyboard-specific stylesheet manager. @@ -251,6 +251,8 @@ export default abstract class OSKView this.uiStyleSheetManager.linkExternalSheet(sheetHref); } + this.activeKeyboard = this.config.keyboardToActivate; + this.setBaseMouseEventListeners(); if(this.hostDevice.touchable) { this.setBaseTouchEventListeners(); @@ -540,6 +542,13 @@ export default abstract class OSKView keyboard: Keyboard, metadata: KeyboardProperties }) { + // Is the keyboard already loaded? If so, ignore the change command. + // + // Note: ensures that the _instances_ are the same; it's possible to make new instances + // to force a refresh. Does not perform a deep-equals. + if(this.keyboardData?.keyboard == keyboardData?.keyboard && this.keyboardData?.metadata == keyboardData?.metadata) { + return; + } this.keyboardData = keyboardData; this.loadActiveKeyboard(); From eebf3e5ba80c735cbc5aa629482d353b5e82385a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 5 Apr 2024 10:41:07 +0700 Subject: [PATCH 2/7] fix(web): OSK set activeKeyboard should not bypass during init --- web/src/engine/osk/src/views/oskView.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/src/engine/osk/src/views/oskView.ts b/web/src/engine/osk/src/views/oskView.ts index 6163b8ba774..20a51f5c5fa 100644 --- a/web/src/engine/osk/src/views/oskView.ts +++ b/web/src/engine/osk/src/views/oskView.ts @@ -201,6 +201,7 @@ export default abstract class OSKView private _baseFontSize: ParsedLengthStyle; private needsLayout: boolean = true; + private initialized: boolean; private _animatedHideTimeout: number; @@ -257,6 +258,8 @@ export default abstract class OSKView if(this.hostDevice.touchable) { this.setBaseTouchEventListeners(); } + + this.initialized = true; } protected get configuration(): Configuration { @@ -546,7 +549,7 @@ export default abstract class OSKView // // Note: ensures that the _instances_ are the same; it's possible to make new instances // to force a refresh. Does not perform a deep-equals. - if(this.keyboardData?.keyboard == keyboardData?.keyboard && this.keyboardData?.metadata == keyboardData?.metadata) { + if(this.initialized && this.keyboardData?.keyboard == keyboardData?.keyboard && this.keyboardData?.metadata == keyboardData?.metadata) { return; } this.keyboardData = keyboardData; From 786c48074249dd457145cc4ed670ee8e8c0c4804 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 8 Apr 2024 08:55:38 +0700 Subject: [PATCH 3/7] fix(web): adds newly-needed null guards due to new pattern --- web/src/app/browser/src/keymanEngine.ts | 4 +++- web/src/engine/osk/src/views/floatingOskView.ts | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 69281370d29..1797cfae6c4 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -71,7 +71,9 @@ export default class KeymanEngine extends KeymanEngineBase) => { const e = target?.getElement(); - (this.osk.activationModel as TwoStateActivator).activationTrigger = e; + if(this.osk) { + (this.osk.activationModel as TwoStateActivator).activationTrigger = e; + } if(this.config.hostDevice.touchable) { if(!e || !target || !this.osk) { diff --git a/web/src/engine/osk/src/views/floatingOskView.ts b/web/src/engine/osk/src/views/floatingOskView.ts index 7748d463b83..af8a9655f91 100644 --- a/web/src/engine/osk/src/views/floatingOskView.ts +++ b/web/src/engine/osk/src/views/floatingOskView.ts @@ -31,7 +31,7 @@ export default class FloatingOSKView extends OSKView { dfltX: string; dfltY: string; - layoutSerializer = new FloatingOSKCookieSerializer(); + private layoutSerializer = new FloatingOSKCookieSerializer(); private titleBar: TitleBar; private resizeBar: ResizeBar; @@ -220,6 +220,15 @@ export default class FloatingOSKView extends OSKView { * @return {boolean} */ private loadPersistedLayout(): void { + /* + If a keyboard is available during OSK construction, it is possible + for this field to be `undefined`. `loadPersistedLayout` will be called + later in construction, so it's safe to skip. + */ + if(!this.layoutSerializer) { + return; + } + let c = this.layoutSerializer.loadWithDefaults({ visible: 1, userSet: 0, From 829d40f5f35d6f4230b172409682f314d14c2b9b Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 9 Apr 2024 08:15:40 +0700 Subject: [PATCH 4/7] fix(ios): ios-host adjustments --- .../resources/Keyman.bundle/Contents/Resources/ios-host.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js b/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js index bd323b970b4..93730c72ff4 100644 --- a/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js +++ b/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js @@ -131,8 +131,10 @@ function getOskWidth() { function getOskHeight() { var height = oskHeight; - if(keyman.osk.banner._activeType != 'blank') { + if(keyman.osk && keyman.osk.banner._activeType != 'blank') { height = height - keyman.osk.banner.height; + } else { + height = height - (bannerHeight ? bannerHeight : 0); } return height; } From 7466b82893859acafe8c529d099944627c70c150 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 10 Apr 2024 11:04:56 +0700 Subject: [PATCH 5/7] fix(web): OSK now checks pred-text state on init --- .../src/text/prediction/predictionContext.ts | 4 ++++ web/src/engine/main/src/keymanEngine.ts | 2 ++ web/src/engine/osk/src/views/oskView.ts | 7 +++++++ 3 files changed, 13 insertions(+) diff --git a/common/web/input-processor/src/text/prediction/predictionContext.ts b/common/web/input-processor/src/text/prediction/predictionContext.ts index eebccb0fe3e..def3dfa7c04 100644 --- a/common/web/input-processor/src/text/prediction/predictionContext.ts +++ b/common/web/input-processor/src/text/prediction/predictionContext.ts @@ -105,6 +105,10 @@ export default class PredictionContext extends EventEmitter Date: Thu, 18 Apr 2024 14:33:17 +0700 Subject: [PATCH 6/7] chore(android): reverts error-silencing try-catch --- android/KMEA/app/src/main/assets/android-host.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index eb6db5311c3..7736fc6c60f 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -36,9 +36,7 @@ function init() { keyman.beepKeyboard = beepKeyboard; // Readies the keyboard stub for instant loading during the init process. - try { - KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard())); - } catch {}; + KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard())); keyman.init({ 'embeddingApp':device, From d705589726a1fb553e7a8b4daf8b92617fc4a67d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 19 Apr 2024 09:25:08 +0700 Subject: [PATCH 7/7] chore(android): restores try-catch, now with console.error err forwarding --- android/KMEA/app/src/main/assets/android-host.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 7736fc6c60f..7491b56ea7a 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -36,7 +36,11 @@ function init() { keyman.beepKeyboard = beepKeyboard; // Readies the keyboard stub for instant loading during the init process. - KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard())); + try { + KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard())); + } catch(error) { + console.error(error); + } keyman.init({ 'embeddingApp':device,