diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 52bace4f87b..d1d428c1ae9 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -232,7 +232,7 @@ export class ContextManager extends ContextManagerBase { } } - public setActiveTextStore(textStore: AbstractElementTextStore, sendEvents?: boolean) { + public setActiveTextStore(textStore: AbstractElementTextStore, sendEvents?: boolean): void { const previousTextStore = this.mostRecentTextStore; const originalTextStore = this.activeTextStore; // may differ, depending on focus state. @@ -410,6 +410,11 @@ export class ContextManager extends ContextManagerBase { * activates the keyboard if the specified control represents the * currently-active context. * + * If kbdId and langId are both null, the control will use the global + * keyboard. If both are the empty string, the control will use the + * system keyboard (on desktop), or the first installed keyboard (on + * touch devices). + * * This is the core method that backs * https://help.keyman.com/developer/engine/web/current-version/reference/core/setKeyboardForControl. * @param textStore diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 8f1f7ac54ca..9b56247cdd1 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -315,14 +315,14 @@ export class KeymanEngine extends KeymanEngineBase { - if(!(keyman.initialized || this.initialized)) { + public readonly updateKeyboardList = () => { + if (!(keyman.initialized || this.initialized)) { return; //TODO: may want to restart the timer?? } @@ -562,10 +558,9 @@ if(!keyman) { // var _SelectedMenuItem; /** - * Function selecKbd - * Scope Private + * Select a keyboard from the drop down menu + * * @param {number} kbdIndex - * Description Select a keyboard from the drop down menu **/ private async selectKbd(kbdIndex: number): Promise { let name: string, languageCode: string; @@ -588,13 +583,12 @@ if(!keyman) { }; /** - * Function updateMenu - * Scope Private + * Updates the menu selection when a change is required + * * @param {string} kbdName * @param {?string=} lgCode - * Description Updates the menu selection when a change is required **/ - updateMenu(kbdName: string, lgCode: string) { + public updateMenu(kbdName: string, lgCode: string) { let _k=document.getElementById('KMWSel_$'); for(let i=0; i < this.keyboards.length; i++) { @@ -629,7 +623,7 @@ if(!keyman) { } } - get stylingCSS() { + private get stylingCSS() { return ` #KeymanWeb_KbdList { display: block; @@ -713,11 +707,9 @@ if(!keyman) { } /** - * Function createMenu - * Scope Private - * Description Create the drop down menu and populate with loaded KeymanWeb keyboards + * Create the drop down menu and populate with loaded KeymanWeb keyboards **/ - createMenu() { + private createMenu() { if(typeof(this.keyboardMenu) == 'undefined') { // I2403 - Allow toggle design to be loaded twice this.keyboardMenu = util.createElement('ul'); this.keyboardMenu.id='KeymanWeb_KbdList'; diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index 034f34a4560..982c693e625 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -136,7 +136,7 @@ export class ContextManager extends ContextManagerBase { return this._activeKeyboard; } - activateKeyboardForTextStore(kbd: { keyboard: Keyboard, metadata: KeyboardStub }, textStore: TextStore) { + protected activateKeyboardForTextStore(kbd: { keyboard: Keyboard, metadata: KeyboardStub }, textStore: TextStore) { // `textStore` is irrelevant for `app/webview`, as it'll only ever use 'global' keyboard settings. // Clone the object to prevent accidental by-reference changes. diff --git a/web/src/engine/src/main/contextManagerBase.ts b/web/src/engine/src/main/contextManagerBase.ts index 8554c124bc8..931c18c039b 100644 --- a/web/src/engine/src/main/contextManagerBase.ts +++ b/web/src/engine/src/main/contextManagerBase.ts @@ -248,7 +248,7 @@ export abstract class ContextManagerBase const wasNull = !this.activeKeyboard; // If there was a previous activation attempt set and still active for the specified keyboard textStore, - // cancel it. For exmaple, if the user selects a preloaded keyboard after having tried to select one + // cancel it. For example, if the user selects a preloaded keyboard after having tried to select one // still async-loading, we should go with the later setting - the preloaded one. this.findAndPopActivation(this.currentKeyboardSrcTextStore()); @@ -314,31 +314,25 @@ export abstract class ContextManagerBase keyboardId: string, languageCode?: string ): {keyboard: Promise, metadata: KeyboardStub} { + if (!keyboardId) { + return { + keyboard: Promise.resolve(null), + metadata: null + } + } + // Set default language code languageCode ||= ''; // Check that the saved keyboard is currently registered - let requestedStub: KeyboardStub = null; - if(keyboardId) { - requestedStub = this.keyboardCache.getStub(keyboardId, languageCode); - } else { - languageCode == ''; - } - + const requestedStub: KeyboardStub = this.keyboardCache.getStub(keyboardId, languageCode); if(!requestedStub) { - if(keyboardId) { - const availableStubList = this.keyboardCache.getStubList().map(stub => `${stub.KI}@${stub.KLC}`); - throw new Error(`No matching stub has been registered for keyboard ${keyboardId}. Available stubs: ${JSON.stringify(availableStubList)}`); - } else { - return { - keyboard: Promise.resolve(null), - metadata: null - } - } + const availableStubList = this.keyboardCache.getStubList().map(stub => `${stub.KI}@${stub.KLC}`); + throw new Error(`No matching stub has been registered for keyboard ${keyboardId}. Available stubs: ${JSON.stringify(availableStubList)}`); } // Check if current keyboard matches requested keyboard, but not (necessarily) stub - if (this.activeKeyboard?.metadata && keyboardId == this.activeKeyboard.metadata.id) { + if (keyboardId == this.activeKeyboard?.metadata?.id) { const {keyboard} = this.activeKeyboard; // In this case, the keyboard is loaded; just update the stub. @@ -349,61 +343,61 @@ export abstract class ContextManagerBase } // Determine if the keyboard was previously loaded but is not active; use the cached, pre-loaded version if so. - let keyboard: Keyboard; - if(keyboard = this.keyboardCache.getKeyboardForStub(requestedStub)) { + const keyboard: Keyboard = this.keyboardCache.getKeyboardForStub(requestedStub); + if (keyboard) { return { keyboard: Promise.resolve(keyboard), metadata: requestedStub }; - } else { - // It's async time - the keyboard is not preloaded within the cache. Use the stub's data to load it. - - // `beforeKeyboardChange` - first call - this.emit('beforekeyboardchange', requestedStub); - - const defermentPromise = this.engineConfig.deferForInitialization.then(() => { - // Provide a Promise for completion of the async load process. - const completionPromise = new ManagedPromise(); - this.emit('keyboardasyncload', requestedStub, completionPromise.corePromise); - - const keyboardPromise = this.keyboardCache.fetchKeyboard(requestedStub.KI); - const timeoutPromise = new Promise((resolve, reject) => { - const timeoutMsg = `Download of ${requestedStub.KI} for language ${requestedStub.langId} timed out.`; - window.setTimeout(() => reject(new Error(timeoutMsg)), ContextManagerBase.TIMEOUT_THRESHOLD); - }); - - const combinedPromise = Promise.race([keyboardPromise, timeoutPromise]); - - // Ensure the async-load Promise completes properly. - combinedPromise.then(() => { - completionPromise.resolve(null); - // Prevent any 'unhandled Promise rejection' events that may otherwise occur from the timeout promise. - timeoutPromise.catch(() => {}); - }); - combinedPromise.catch((err) => { - completionPromise.resolve(err); - throw err; - }); - - return combinedPromise; + } + + // It's async time - the keyboard is not preloaded within the cache. Use the stub's data to load it. + + // `beforeKeyboardChange` - first call + this.emit('beforekeyboardchange', requestedStub); + + const defermentPromise = this.engineConfig.deferForInitialization.then(() => { + // Provide a Promise for completion of the async load process. + const completionPromise = new ManagedPromise(); + this.emit('keyboardasyncload', requestedStub, completionPromise.corePromise); + + const keyboardPromise = this.keyboardCache.fetchKeyboard(requestedStub.KI); + const timeoutPromise = new Promise((resolve, reject) => { + const timeoutMsg = `Download of ${requestedStub.KI} for language ${requestedStub.langId} timed out.`; + window.setTimeout(() => reject(new Error(timeoutMsg)), ContextManagerBase.TIMEOUT_THRESHOLD); }); - // Now the fun part: note the original call's parameters as a pending activation. - const promise = this.deferredKeyboardActivation(defermentPromise, requestedStub, this.currentKeyboardSrcTextStore()); - return { - keyboard: promise.then(async (activation) => { - // Is the activation we requested still pending, or was it cancelled in favor of a - // different activation in some manner? - if(!activation) { - // If the user chose to load a different keyboard afterward that would affect the same - // textStore, the activation is no longer valid. - return Promise.resolve(null); - } else { - return defermentPromise; - } - }), - metadata: requestedStub - } + const combinedPromise = Promise.race([keyboardPromise, timeoutPromise]); + + // Ensure the async-load Promise completes properly. + combinedPromise.then(() => { + completionPromise.resolve(null); + // Prevent any 'unhandled Promise rejection' events that may otherwise occur from the timeout promise. + timeoutPromise.catch(() => {}); + }); + combinedPromise.catch((err) => { + completionPromise.resolve(err); + throw err; + }); + + return combinedPromise; + }); + + // Now the fun part: note the original call's parameters as a pending activation. + const promise = this.deferredKeyboardActivation(defermentPromise, requestedStub, this.currentKeyboardSrcTextStore()); + return { + keyboard: promise.then(async (activation) => { + // Is the activation we requested still pending, or was it cancelled in favor of a + // different activation in some manner? + if(!activation) { + // If the user chose to load a different keyboard afterward that would affect the same + // textStore, the activation is no longer valid. + return Promise.resolve(null); + } else { + return defermentPromise; + } + }), + metadata: requestedStub } } } diff --git a/web/src/engine/src/main/keymanEngineBase.ts b/web/src/engine/src/main/keymanEngineBase.ts index 793f1bcc773..2ee22a2b370 100644 --- a/web/src/engine/src/main/keymanEngineBase.ts +++ b/web/src/engine/src/main/keymanEngineBase.ts @@ -543,8 +543,8 @@ export class KeymanEngineBase< /** * Allow to change active keyboard by (internal) keyboard name * - * @param {string} PInternalName Internal name - * @param {string} PLgCode Language code + * @param {string} keyboardId Keyboard name + * @param {string} languageCode Language code * * See https://help.keyman.com/developer/engine/web/current-version/reference/core/setActiveKeyboard */ @@ -632,4 +632,4 @@ export class KeymanEngineBase< }; } -// Intent: define common behaviors for both primary app types; each then subclasses & extends where needed. \ No newline at end of file +// Intent: define common behaviors for both primary app types; each then subclasses & extends where needed.