mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-05 15:17:40 +00:00
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:
commit
dd358c8adc
10 changed files with 165 additions and 66 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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/"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
55
web/src/app/browser/src/viewsAnchorpoint.ts
Normal file
55
web/src/app/browser/src/viewsAnchorpoint.ts
Normal 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 };
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue