chore(web): Merge branch 'epic/web-core' into feat/web/keyboardDetails

This commit is contained in:
Eberhard Beilharz 2026-04-28 19:03:16 +02:00
commit 16dcfaa0cc
No known key found for this signature in database
GPG key ID: E9140597606020D3
9 changed files with 155 additions and 33 deletions

View file

@ -18,7 +18,8 @@ Keyman configures two separate instances of this gesture engine - one for main k
----
For the main keyboard implementation of Keyman Engine for Web, the full set of Keyman gesture components is defined in `web/src/engine/osk/src/input/gestures/specsForLayout.ts`.
For the main keyboard implementation of Keyman Engine for Web, the full set of Keyman gesture
components is defined in `web/src/engine/src/osk/input/gestures/specsForLayout.ts`.
### Longpresses
@ -44,4 +45,4 @@ Components:
1. The user holds down on a modipress-friendly key
2. Engine alt-state: a different set of initial gesture components is activated and used for newly-starting concurrent gestures
3. Release of the modipressed key
3. Release of the modipressed key

View file

@ -474,8 +474,7 @@ export class KeymanEngine extends KeymanEngineBase<BrowserConfiguration, Context
const cache = this.keyboardRequisitioner.cache;
const keyboardStubs = cache.getStubList()
for (let i = 0; i < keyboardStubs.length; i++) {
const stub = keyboardStubs[i];
for (const stub of keyboardStubs) {
const keyboard = cache.getKeyboardForStub(stub);
const keyboardDetails = this.getKeyboardDetails(stub, keyboard);
if (keyboardDetails) {

View file

@ -1,13 +1,17 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*/
import { KM_Core, km_core_option_item } from 'keyman/engine/core-adapter';
import { KM_Core, km_core_option_item, KM_CORE_OPTION_SCOPE } from 'keyman/engine/core-adapter';
import { KeyboardMinimalInterface, Keyboard, VariableStoreSerializer, KMXKeyboard } from 'keyman/engine/keyboard';
import { toPrefixedKeyboardId } from 'keyman/engine/keyboard-storage';
export class CoreKeyboardInterface implements KeyboardMinimalInterface {
private _activeKeyboard: Keyboard;
public constructor(public variableStoreSerializer: VariableStoreSerializer) {
public constructor(public readonly variableStoreSerializer: VariableStoreSerializer) {
if (!variableStoreSerializer) {
throw new Error('variableStoreSerializer is required');
}
}
public get activeKeyboard(): Keyboard {
@ -15,33 +19,36 @@ export class CoreKeyboardInterface implements KeyboardMinimalInterface {
}
public set activeKeyboard(keyboard: Keyboard) {
this._activeKeyboard = keyboard;
const options = this.loadSerializedOptions(keyboard);
const options = this.loadSerializedOptions();
if (options.length > 0) {
const kmxKeyboard = this._activeKeyboard as KMXKeyboard;
KM_Core.instance.state_options_update(kmxKeyboard.state, options);
KM_Core.instance.state_options_update(this.KmxKeyboard.state, options);
}
}
private get KmxKeyboard(): KMXKeyboard {
return this._activeKeyboard as KMXKeyboard;
}
private loadSerializedOptions(keyboard: Keyboard): km_core_option_item[] {
// TODO-WEB-CORE: use km_core_keyboard_get_attrs to get list of all variable
// store names and then iterate through those rather than reading from
// cookie props
private loadSerializedOptions(): km_core_option_item[] {
const options: km_core_option_item[] = [];
/*const stores = this.variableStoreSerializer.findStores(toPrefixedKeyboardId(keyboard.id));
for (const store of stores) {
for (const key in store) {
if (store.hasOwnProperty(key)) {
const attrs = KM_Core.instance.keyboard_get_attrs(this.KmxKeyboard.keyboard);
const prefixedKeyboardId = toPrefixedKeyboardId(this.KmxKeyboard.id);
const defaultOptions = attrs.object.default_options as km_core_option_item[];
for (const defaultOption of defaultOptions) {
if (defaultOption.scope == KM_CORE_OPTION_SCOPE.OPT_KEYBOARD) {
const savedValue = this.variableStoreSerializer.loadStore(prefixedKeyboardId, defaultOption.key);
if (savedValue !== undefined) {
const item: km_core_option_item = {
key: key,
value: store[key],
scope: KM_CORE_OPTION_SCOPE.OPT_KEYBOARD
key: defaultOption.key,
value: savedValue,
scope: defaultOption.scope
};
options.push(item);
}
}
}*/
}
attrs.delete();
return options;
}
}

View file

@ -1 +1,10 @@
import { CoreKeyboardInterface } from './coreKeyboardInterface.js';
export { CoreKeyboardProcessor } from './coreKeyboardProcessor.js';
/**
* these are exported only for unit tests, do not use
*/
export const unitTestEndPoints = {
CoreKeyboardInterface,
};

View file

@ -7,6 +7,6 @@
export type VariableStores = { [name: string]: string; };
export interface VariableStoreSerializer {
loadStore(keyboardID: string, storeName: string): string;
loadStore(keyboardID: string, storeName: string): string | undefined;
saveStore(keyboardID: string, storeName: string, storeValue: string): void;
}

View file

@ -6,11 +6,11 @@ export class VariableStoreCookieSerializer implements VariableStoreSerializer {
return `KeymanWeb_${keyboardID}_Option_${storeName}`;
}
public loadStore(keyboardID: string, storeName: string): string {
public loadStore(keyboardID: string, storeName: string): string | undefined {
// Note, for historical reasons, we pass a VariableStores object into the
// cookie serializer but expect only a single value to be loaded
const storeCookieSerializer = new CookieSerializer<VariableStores>(this.getStoreCookieName(keyboardID, storeName));
return storeCookieSerializer.load(decodeURIComponent)[storeName];
return storeCookieSerializer.load(decodeURIComponent)[storeName] ?? undefined;
}
public saveStore(keyboardID: string, storeName: string, storeValue: string) {

View file

@ -47,13 +47,6 @@ const testsToFix = {
'k_0202___alt.kmn',
'k_0203___generic_ctrlalt.kmn',
'k_0400___groups_and_virtual_keys.kmn', // kmx only
'k_0501___options_with_preset.kmn', // variable stores initialization work to do with keyboard_attrs
'k_0503___options_with_save_and_preset.kmn', // variable stores initialization work to do with keyboard_attrs
'k_0504___options_with_reset.kmn', // variable stores initialization work to do with keyboard_attrs
'k_0505___options_double_set_reset.kmn', // variable stores initialization work to do with keyboard_attrs
'k_0506___options_double_set_staged.kmn', // variable stores initialization work to do with keyboard_attrs
'k_0507___options___double_reset_staged.kmn', // variable stores initialization work to do with keyboard_attrs
'k_0508___options___double_reset.kmn', // variable stores initialization work to do with keyboard_attrs
'k_0600___system_stores.kmn',
'k_0601___system_stores_2.kmn',
'k_0700___caps_lock.kmn',

View file

@ -10,8 +10,8 @@ export class VariableStoreTestSerializer implements VariableStoreSerializer {
return `KeymanWeb_${keyboardID}_Option_${storeName}`;
}
loadStore(keyboardID: string, storeName: string): string {
return this.stores[this.getStoreName(keyboardID, storeName)] ?? '';
loadStore(keyboardID: string, storeName: string): string | undefined {
return this.stores[this.getStoreName(keyboardID, storeName)] ?? undefined;
}
saveStore(keyboardID: string, storeName: string, storeValue: string): void {

View file

@ -0,0 +1,113 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*/
import { assert } from 'chai';
import sinon from 'sinon';
import { unitTestEndPoints } from 'keyman/engine/core-processor';
import { VariableStoreTestSerializer } from 'keyman/test/headless-resources';
import { KM_Core, KM_CORE_STATUS, KM_CORE_OPTION_SCOPE, type km_core_option_item } from 'keyman/engine/core-adapter';
import { coreurl } from '../loadKeyboardHelper.js';
describe('CoreKeyboardInterface tests', function () {
let sandbox: sinon.SinonSandbox;
const mockKeyboard = {
keyboard: {} as any,
id: 'test_keyboard',
state: {} as any
};
beforeEach(async function () {
sandbox = sinon.createSandbox();
await KM_Core.createCoreProcessor(coreurl);
});
afterEach(function () {
sandbox.restore();
});
describe('loadSerializedOptions', function () {
it('returns empty array if there are no default options', function () {
// Setup
const mockAttrs = {
object: {
id: 'test_keyboard',
version_string: '1.234',
default_options: [] as km_core_option_item[],
},
status: KM_CORE_STATUS.OK,
delete: sandbox.stub()
};
sandbox.stub(KM_Core.instance, 'keyboard_get_attrs').returns(mockAttrs as any);
const keyboardInterface = new unitTestEndPoints.CoreKeyboardInterface(new VariableStoreTestSerializer());
keyboardInterface['_activeKeyboard'] = mockKeyboard as any;
// Execute
const options = keyboardInterface['loadSerializedOptions']();
// Verify
assert.isArray(options);
assert.lengthOf(options, 0);
});
it('returns no options if no options serialized', function () {
// Setup
const mockAttrs = {
object: {
id: 'test_keyboard',
version_string: '1.234',
default_options: [
{ key: 'opt1', value: 'val1', scope: KM_CORE_OPTION_SCOPE.OPT_KEYBOARD }
],
},
status: KM_CORE_STATUS.OK,
delete: sandbox.stub(),
};
sandbox.stub(KM_Core.instance, 'keyboard_get_attrs').returns(mockAttrs as any);
const keyboardInterface = new unitTestEndPoints.CoreKeyboardInterface(new VariableStoreTestSerializer());
keyboardInterface['_activeKeyboard'] = mockKeyboard as any;
// Execute
const options = keyboardInterface['loadSerializedOptions']();
// Verify
assert.isArray(options);
assert.lengthOf(options, 0);
});
it('returns serialized options from keyboard attrs', function () {
// Setup
const mockAttrs = {
object: {
id: 'test_keyboard',
version_string: '1.234',
default_options: [
{ key: 'opt1', value: 'default1', scope: KM_CORE_OPTION_SCOPE.OPT_KEYBOARD },
{ key: 'opt2', value: 'default2', scope: KM_CORE_OPTION_SCOPE.OPT_KEYBOARD }, // ignored because not serialized
{ key: 'opt3', value: 'default3', scope: KM_CORE_OPTION_SCOPE.OPT_KEYBOARD },
{ key: 'opt4', value: 'default4', scope: KM_CORE_OPTION_SCOPE.OPT_ENVIRONMENT },
],
},
status: KM_CORE_STATUS.OK,
delete: sandbox.stub(),
};
sandbox.stub(KM_Core.instance, 'keyboard_get_attrs').returns(mockAttrs as any);
const serializer = new VariableStoreTestSerializer();
serializer.saveStore('Keyboard_test_keyboard', 'opt1', 'val1');
serializer.saveStore('Keyboard_test_keyboard', 'opt3', '');
serializer.saveStore('Keyboard_test_keyboard', 'opt4', 'val4'); // ignored since not keyboard-scoped
serializer.saveStore('Keyboard_test_keyboard', 'opt5', 'val5'); // ignored since not in default options
const keyboardInterface = new unitTestEndPoints.CoreKeyboardInterface(serializer);
keyboardInterface['_activeKeyboard'] = mockKeyboard as any;
// Execute
const options = keyboardInterface['loadSerializedOptions']();
// Verify
assert.isArray(options);
assert.lengthOf(options,2);
assert.deepEqual(options[0], { key: 'opt1', value: 'val1', scope: KM_CORE_OPTION_SCOPE.OPT_KEYBOARD });
assert.deepEqual(options[1], { key: 'opt3', value: '', scope: KM_CORE_OPTION_SCOPE.OPT_KEYBOARD });
});
});
});