Merge pull request #8905 from keymanapp/chore/developer/web-reintegration

chore(developer, web): developer host page linkage to modularized KMW 🧩
This commit is contained in:
Joshua Horton 2023-06-08 12:59:28 +07:00 committed by GitHub
commit dd358c8adc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 165 additions and 66 deletions

View file

@ -142,6 +142,7 @@ export default class KeyboardProperties implements KeyboardInternalPropertySpec
this.KN = other.KN;
this.KL = other.KL;
this.KLC = other.KLC;
// Do NOT apply fontPath here; the mobile apps will have font issues if you do!
this.KFont = other.KFont;
this.KOskFont = other.KOskFont;
this._displayName = (other instanceof KeyboardProperties) ? other._displayName : other.displayName;

View file

@ -124,20 +124,18 @@ fi
if (( build_keymanweb )); then
pushd "$KEYMAN_ROOT/web/"
./build.sh --no-minify
./build.sh build --debug
popd
fi
if (( copy_keymanweb )); then
WEB_SRC="$KEYMAN_ROOT/web/build/app/web/debug"
UI_SRC="$KEYMAN_ROOT/web/build/app/ui/debug"
WEB_SRC="$KEYMAN_ROOT/web/build/publish/debug"
DST="$(dirname "$THIS_SCRIPT")/src/site/resource"
rm -rf "$DST"
mkdir -p "$DST/osk"
mkdir -p "$DST/ui"
cp "$WEB_SRC/"*.js "$WEB_SRC/"*.js.map "$DST/"
cp "$UI_SRC/"*.js "$UI_SRC/"*.js.map "$DST/"
cp -R "$WEB_SRC/osk/"* "$DST/osk/"
cp -R "$WEB_SRC/ui/"* "$DST/ui/"
cp "$KEYMAN_ROOT/web/LICENSE" "$DST/"

View file

@ -73,17 +73,10 @@
function updateLogCursor() {
var i, selStart, selLength, selDirection;
if(keyman.isPositionSynthesized()) { // this is an internal function
// For touch devices, we need to ask KMW
selStart = 0;
selLength = 0;
selDirection = 'forward';
} else {
// For desktop devices, we use the position reported by the textarea control
selStart = ta1.selectionStart;
selLength = ta1.selectionEnd - ta1.selectionStart;
selDirection = ta1.selectionDirection;
}
// We use the position reported by the textarea control
selStart = ta1.selectionStart;
selLength = ta1.selectionEnd - ta1.selectionStart;
selDirection = ta1.selectionDirection;
selLength = calculateLengthByCodepoint(ta1.value, selStart, selLength);
selStart = calculateLengthByCodepoint(ta1.value, 0, selStart);

View file

@ -221,7 +221,7 @@ window.onload = function() {
// Create a new on screen keyboard view and tell KeymanWeb that
// we are using the targetDevice for context input.
newOSK = new com.keyman.osk.InlinedOSKView(targetDevice, keyman.util.device.coreSpec);
newOSK = new keyman.views.InlinedOSKView(keyman, { device: targetDevice });
keyman.core.contextDevice = targetDevice;
keyman.osk = newOSK;
@ -238,11 +238,10 @@ window.onload = function() {
keyman.addEventListener('keyboardchange', function(keyboardProperties) {
if(newOSK) {
keyman.osk = newOSK;
newOSK.activeKeyboard = keyman.core.activeKeyboard;
newOSK.activeKeyboard = keyman.contextManager.activeKeyboard; // Private API refs on both sides
}
keyboardDropdown.set(keyboardProperties.internalName);
window.sessionStorage.setItem('current-keyboard', keyboardProperties.internalName);
keyman.alignInputs();
});
}

View file

@ -3,9 +3,6 @@ import { Device as DeviceDetector } from 'keyman/engine/device-detect';
import { getAbsoluteY } from 'keyman/engine/dom-utils';
import { OutputTarget } from 'keyman/engine/element-wrappers';
import {
AnchoredOSKView,
FloatingOSKView,
FloatingOSKViewConfiguration,
OSKView,
TwoStateActivator,
VisualKeyboard
@ -13,6 +10,7 @@ import {
import { ErrorStub, KeyboardStub, CloudQueryResult, toPrefixedKeyboardId as prefixed } from 'keyman/engine/package-cache';
import { DeviceSpec, Keyboard, ProcessorInitOptions, extendString } from "@keymanapp/keyboard-processor";
import * as views from './viewsAnchorpoint.js';
import { BrowserConfiguration, BrowserInitOptionDefaults, BrowserInitOptionSpec } from './configuration.js';
import { default as ContextManager } from './contextManager.js';
import DefaultBrowserRules from './defaultBrowserRules.js';
@ -88,6 +86,11 @@ export default class KeymanEngine extends KeymanEngineBase<BrowserConfiguration,
return this._util;
}
public get views() {
// NOT this.views. Just... `views`, the import of viewsAnchorpoint.ts
return views;
}
public get initialized() {
return this._initialized;
}
@ -147,22 +150,14 @@ export default class KeymanEngine extends KeymanEngineBase<BrowserConfiguration,
this.contextManager.initialize(); // will seek to attach to the page, which requires document.body
const oskConfig: FloatingOSKViewConfiguration = {
hostDevice: this.config.hostDevice,
pathConfig: this.config.paths,
predictionContextManager: this.contextManager.predictionContext,
isEmbedded: false
};
// Capture the saved-keyboard string now, before we load any keyboards/stubs
// or do anything that would mutate the value.
const savedKeyboardStr = this.contextManager.getSavedKeyboardRaw();
let osk: OSKView;
if(device.touchable) {
this.osk = new AnchoredOSKView(oskConfig);
this.osk = new views.AnchoredOSKView(this);
} else {
this.osk = new FloatingOSKView(oskConfig);
this.osk = new views.FloatingOSKView(this);
}
setupOskListeners(this, this.osk, this.contextManager);

View file

@ -0,0 +1,55 @@
import {
ViewConfiguration,
AnchoredOSKView,
FloatingOSKView,
FloatingOSKViewConfiguration,
InlinedOSKView
} from "keyman/engine/osk";
import KeymanEngine from "./keymanEngine.js";
function buildBaseOskConfiguration(engine: KeymanEngine) {
return {
hostDevice: engine.config.hostDevice,
pathConfig: engine.config.paths,
predictionContextManager: engine.contextManager.predictionContext,
isEmbedded: false
};
};
class PublishedAnchoredOSKView extends AnchoredOSKView {
constructor(engine: KeymanEngine, config?: ViewConfiguration) {
let finalConfig = {
...buildBaseOskConfiguration(engine),
...(config || {})
};
super(finalConfig);
}
}
class PublishedFloatingOSKView extends FloatingOSKView {
constructor(engine: KeymanEngine, config?: FloatingOSKViewConfiguration) {
let finalConfig: FloatingOSKViewConfiguration = {
...buildBaseOskConfiguration(engine),
...(config || {})
};
super(finalConfig);
}
}
class PublishedInlineOSKView extends InlinedOSKView {
constructor(engine: KeymanEngine, config?: ViewConfiguration) {
let finalConfig: ViewConfiguration = {
...buildBaseOskConfiguration(engine),
...(config || {})
};
super(finalConfig);
}
}
export { PublishedAnchoredOSKView as AnchoredOSKView };
export { PublishedFloatingOSKView as FloatingOSKView };
export { PublishedInlineOSKView as InlinedOSKView };

View file

@ -77,14 +77,19 @@ export default class KeyboardInterface<ContextManagerType extends ContextManager
this.stubNamespacer(Pstub);
}
// Other notes: this is where app-hosted KeymanWeb receives pre-formed stubs.
// This is where app-hosted KeymanWeb receives pre-formed stubs.
// They're specified in the "internal" format (KI, KN, KLC...)
// (SHIFT-CTRL-F @ repo-level: `setKeymanLanguage`)
// (SHIFT-CTRL-F @ repo-level for the mobile apps: `setKeymanLanguage`)
// Keyman Developer may also use this method directly for its test-host page.
//
// It may also be used by documented legacy API:
// https://help.keyman.com/DEVELOPER/ENGINE/WEB/2.0/guide/examples/manual-control
// (See: referenced laokeys_load.js)
const stub = new KeyboardStub(Pstub);
//
// The mobile apps typically have fully-preconfigured paths, but Developer's
// test-host page does not.
const pathConfig = this.engine.config.paths;
const stub = new KeyboardStub(Pstub, pathConfig.keyboards, pathConfig.fonts);
if(this.engine.keyboardRequisitioner?.cache.findMatchingStub(stub)) {
return 1;
}

View file

@ -288,8 +288,11 @@ export default class KeymanEngine<
this.core.keyboardProcessor.layerStore.handler = this.osk.layerChangeHandler;
}
this._osk = value;
this._osk.on('keyEvent', this.keyEventListener);
this.core.keyboardProcessor.layerStore.handler = this.osk.layerChangeHandler;
if(value) {
value.activeKeyboard = this.contextManager.activeKeyboard;
value.on('keyEvent', this.keyEventListener);
this.core.keyboardProcessor.layerStore.handler = value.layerChangeHandler;
}
}
public getDebugInfo(): Record<string, any> {

View file

@ -18,6 +18,36 @@ export type KeyboardAPISpec = (APISimpleKeyboard | APICompoundKeyboard) & {
export interface RawKeyboardStub extends KeyboardStub {};
/*
* Get keyboard path (relative or absolute)
* KeymanWeb 2 revised keyboard location specification:
* (a) absolute URL (includes ':') - load from specified URL
* (b) relative URL (starts with /, ./, ../) - load with respect to current page
* (c) filename only (anything else) - prepend keyboards option to URL
* (e.g. default keyboards option will be set by Cloud)
*
* So, to fully interpret the following regex, it detects the following patterns (at minimum):
* ../file (but not .../file)
* ./file
* /file
* http:// (on the colon)
* hello:world (on the colon) - that one miiiight be less intentional, though. Would 'fall
* over' on attempted use anyway, since it's not a valid path.
*
* Alternative clearer version - '^(\.{0,2}/)|(:)'?
* Unless backslashes should be able to replace dots?
*/
const REGEX_FOR_PRECONFIGURED_PATH=RegExp('^(([\\.]/)|([\\.][\\.]/)|(/))|(:)');
function configureFilePathing(path: string, configurationBasePath: string) {
configurationBasePath = configurationBasePath || '';
if(path && !REGEX_FOR_PRECONFIGURED_PATH.test(path)) {
return configurationBasePath + path;
} else {
return path;
}
}
export default class KeyboardStub extends KeyboardProperties {
KR: string;
KRC: string;
@ -25,7 +55,9 @@ export default class KeyboardStub extends KeyboardProperties {
KP?: string;
public constructor(rawStub: RawKeyboardStub);
// For the first flavor of constructor, note that Developer relies on KMW's path config to complete the paths...
// even though supplying an 'internal'-style stub.
public constructor(rawStub: RawKeyboardStub, keyboardBaseUri?: string, fontBaseUri?: string);
public constructor(apiSpec: APISimpleKeyboard & { filename: string }, keyboardBaseUri?: string, fontBaseUri?: string);
public constructor(kbdId: string, lngId: string);
constructor(arg0: string | RawKeyboardStub | (APISimpleKeyboard & { filename: string }), arg1?: string, arg2?: string) {
@ -34,43 +66,19 @@ export default class KeyboardStub extends KeyboardProperties {
let apiSpec = arg0 as APISimpleKeyboard & { filename: string };
apiSpec.id = prefixed(apiSpec.id);
super(apiSpec, arg2);
this.KF = apiSpec.filename;
this.KF = configureFilePathing(apiSpec.filename, arg1);
this.mapRegion(apiSpec.languages);
/*
* Get keyboard path (relative or absolute)
* KeymanWeb 2 revised keyboard location specification:
* (a) absolute URL (includes ':') - load from specified URL
* (b) relative URL (starts with /, ./, ../) - load with respect to current page
* (c) filename only (anything else) - prepend keyboards option to URL
* (e.g. default keyboards option will be set by Cloud)
*
* So, to fully interpret the following regex, it detects the following patterns (at minimum):
* ../file (but not .../file)
* ./file
* /file
* http:// (on the colon)
* hello:world (on the colon) - that one miiiight be less intentional, though. Would 'fall
* over' on attempted use anyway, since it's not a valid path.
*
* Alternative clearer version - '^(\.{0,2}/)|(:)'?
* Unless backslashes should be able to replace dots?
*/
let rx=RegExp('^(([\\.]/)|([\\.][\\.]/)|(/))|(:)');
arg1 = arg1 || '';
if(this.KF && !rx.test(this.KF)) {
this.KF = arg1 + this.KF;
}
} else {
let rawStub = arg0 as RawKeyboardStub;
rawStub.KI = prefixed(rawStub.KI);
super(rawStub);
super(rawStub, arg2);
this.KF = rawStub.KF;
this.KF = configureFilePathing(rawStub.KF, arg1);
this.KP = rawStub.KP;
this.KR = rawStub.KR;
this.KRC = rawStub.KRC;
return;
}
} else {

View file

@ -38,6 +38,48 @@ describe("KeyboardStub", () => {
};
}
it('construction from internal stub format, partially configured paths', () => {
const rawStub = {
KI: 'dummy',
KN: 'test dummy',
KL: 'English',
KLC: 'en',
KF: 'dummy.js',
// The way font paths are currently handled feels pretty rough and unclear.
// Their paths aren't updated in the same way as the KF entry.
// So... leaving font stuff out of the test for now.
// (Also, Developer doesn't seem to bother specifying font files in its stubs, so it's
// less criitcal.)
};
const stub = new KeyboardStub(rawStub, 'http://localhost/keyboards/', 'http://localhost/fonts/');
assert.equal(stub.KF, 'http://localhost/keyboards/dummy.js');
});
it('construction from internal stub format, pre-configured paths', () => {
// Based on actual font pathing as hosted by the Android app.
const absolutePath = '/data/user/0/com.tavultesoft.kmapro.debug/app_data/packages/dummy/dummy.ttf';
const rawStub = {
KI: 'dummy',
KN: 'test dummy',
KL: 'English',
KLC: 'en',
KF: absolutePath,
// The way font paths are currently handled feels pretty rough and unclear.
// Their paths aren't updated in the same way as the KF entry.
// So... leaving font stuff out of the test for now.
// (Also, Developer doesn't seem to bother specifying font files in its stubs, so it's
// less criitcal.)
};
// These components... are not, but that's OK - the test is to ignore them.
const stub = new KeyboardStub(rawStub, 'http://localhost/keyboards/', 'http://localhost/fonts/');
assert.equal(stub.KF, absolutePath);
});
it('merge(): barebones stub + fetched sil_euro_latin@no', async () => {
const query = performMockedRequest(`${__dirname}/../../resources/query-mock-results/sil_euro_latin@no_sv.js.fixture`);
await query.promise;