mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-11 11:25:34 +00:00
Merge pull request #8270 from keymanapp/refactor/web/common-configuration
refactor(web): common config for both types of Keyman Engine for Web 🧩
This commit is contained in:
commit
ba328a7cb1
13 changed files with 310 additions and 16 deletions
|
|
@ -12,8 +12,7 @@ import { spawn } from 'child_process';
|
|||
const commonConfig = {
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
tsconfig: 'tsconfig.json',
|
||||
target: 'es5',
|
||||
format: "esm",
|
||||
// Sets 'common/web' as a root folder for module resolution;
|
||||
// this allows the keyman-version and utils imports to resolve.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
"name": "keyman",
|
||||
"description": "Facilitates text input in any language.",
|
||||
"exports": {
|
||||
"./engine/configuration": {
|
||||
"types": "./build/engine/configuration/obj/index.d.ts",
|
||||
"import": "./build/engine/configuration/obj/index.js"
|
||||
},
|
||||
"./engine/device-detect": {
|
||||
"types": "./build/engine/device-detect/obj/kmwdevice.d.ts",
|
||||
"import": "./build/engine/device-detect/obj/kmwdevice.js"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ cd "$THIS_SCRIPT_PATH"
|
|||
# Definition of global compile constants
|
||||
|
||||
MAIN=engine/main # Covers all engine code, including submodules like those listed below.
|
||||
CONFIGURATION=engine/configuration
|
||||
DEVICEDETECT=engine/device-detect
|
||||
DOMUTILS=engine/dom-utils
|
||||
KEYBOARDCACHE=engine/keyboard-cache
|
||||
|
|
@ -90,6 +91,7 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \
|
|||
"clean" \
|
||||
"configure" \
|
||||
"build" \
|
||||
":configuration Subset used to configure KMW" \
|
||||
":device-detect Subset used for device-detection " \
|
||||
":dom-utils A common subset of function used for DOM calculations, layout, etc" \
|
||||
":element-wrappers Subset used to integrate with website elements" \
|
||||
|
|
@ -102,13 +104,15 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \
|
|||
|
||||
builder_describe_outputs \
|
||||
configure ../../../node_modules \
|
||||
configure:configuration ../../../node_modules \
|
||||
configure:device-detect ../../../node_modules \
|
||||
configure:dom-utils ../../../node_modules \
|
||||
configure:element-wrappers ../../../node_modules \
|
||||
configure:keyboard-cache ../../../node_modules \
|
||||
configure:main ../../../node_modules \
|
||||
configure:osk ../../../node_modules \
|
||||
build:device-detect $(output_path $DEVICEDETECT $OUTPUT_DIR)/index.js \
|
||||
build:configuration $(output_path $CONFIGURATION $OUTPUT_DIR)/index.js \
|
||||
build:device-detect $(output_path $DEVICEDETECT $OUTPUT_DIR)/kmwdevice.js \
|
||||
build:dom-utils $(output_path $DOMUTILS $OUTPUT_DIR)/index.js \
|
||||
build:element-wrappers $(output_path $ELEMENTWRAPPERS $OUTPUT_DIR)/index.js \
|
||||
build:keyboard-cache $(output_path $KEYBOARDCACHE $OUTPUT_DIR)/index.js \
|
||||
|
|
@ -193,6 +197,12 @@ if builder_start_action build:device-detect; then
|
|||
builder_finish_action success build:device-detect
|
||||
fi
|
||||
|
||||
if builder_start_action build:configuration; then
|
||||
compile $CONFIGURATION
|
||||
|
||||
builder_finish_action success build:configuration
|
||||
fi
|
||||
|
||||
if builder_start_action build:dom-utils; then
|
||||
compile $DOMUTILS
|
||||
|
||||
|
|
@ -217,8 +227,8 @@ if builder_start_action build:osk; then
|
|||
builder_finish_action success build:osk
|
||||
fi
|
||||
|
||||
if builder_start_action build:main; then
|
||||
compile $MAIN
|
||||
# if builder_start_action build:main; then
|
||||
# compile $MAIN
|
||||
|
||||
builder_finish_action success build:main
|
||||
fi
|
||||
# builder_finish_action success build:main
|
||||
# fi
|
||||
42
web/src/engine/configuration/src/configuration.ts
Normal file
42
web/src/engine/configuration/src/configuration.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { physicalKeyDeviceAlias } from "keyman/engine/device-detect";
|
||||
import { DeviceSpec, type SpacebarText } from "@keymanapp/keyboard-processor";
|
||||
|
||||
import { OptionSpec } from "./optionSpec.interface.js";
|
||||
import PathConfiguration from "./pathConfiguration.js";
|
||||
|
||||
export default class Configuration {
|
||||
readonly paths: PathConfiguration;
|
||||
readonly activateFirstKeyboard: boolean;
|
||||
readonly defaultSpacebarText: SpacebarText;
|
||||
|
||||
readonly hostDevice: DeviceSpec;
|
||||
readonly embeddingApp: string;
|
||||
|
||||
// sourcePath: see `var sPath =` in kmwbase.ts. It is not obtainable headlessly.
|
||||
constructor(options: Required<OptionSpec>, device: DeviceSpec, sourcePath: string) {
|
||||
this.paths = new PathConfiguration(options, sourcePath);
|
||||
if(typeof options.setActiveOnRegister == 'boolean') {
|
||||
this.activateFirstKeyboard = options.setActiveOnRegister;
|
||||
} else if (typeof options.setActiveOnRegister == 'string') {
|
||||
let str = options.setActiveOnRegister.toLowerCase();
|
||||
this.activateFirstKeyboard = str === 'true';
|
||||
} else {
|
||||
this.activateFirstKeyboard = true;
|
||||
}
|
||||
|
||||
this.defaultSpacebarText = options.spacebarText;
|
||||
this.hostDevice = device;
|
||||
|
||||
if(options.embeddingApp) {
|
||||
this.embeddingApp = options.embeddingApp;
|
||||
}
|
||||
}
|
||||
|
||||
get softDevice(): DeviceSpec {
|
||||
return this.hostDevice;
|
||||
}
|
||||
|
||||
get hardDevice(): DeviceSpec {
|
||||
return physicalKeyDeviceAlias(this.hostDevice);
|
||||
}
|
||||
}
|
||||
3
web/src/engine/configuration/src/index.ts
Normal file
3
web/src/engine/configuration/src/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { default as Configuration } from './configuration.js';
|
||||
export { default as PathConfiguration } from './pathConfiguration.js';
|
||||
export { OptionDefaults, OptionSpec, PathOptionSpec } from './optionSpec.interface.js';
|
||||
57
web/src/engine/configuration/src/optionSpec.interface.ts
Normal file
57
web/src/engine/configuration/src/optionSpec.interface.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { SpacebarText } from "@keymanapp/keyboard-processor";
|
||||
|
||||
export interface PathOptionSpec {
|
||||
/**
|
||||
* If defined, specifies the root path of the default location hosting KMW resources.
|
||||
* Is typically just the protocol + domain name.
|
||||
*/
|
||||
root?: string;
|
||||
|
||||
/**
|
||||
* The base path to prepend on relative paths for other types of resources.
|
||||
*/
|
||||
resources?: string;
|
||||
|
||||
/**
|
||||
* The base path to prepend on relative paths when loading keyboards.
|
||||
*/
|
||||
keyboards?: string;
|
||||
|
||||
/**
|
||||
* The base path to prepend on relative paths when loading fonts.
|
||||
*/
|
||||
fonts?: string;
|
||||
}
|
||||
|
||||
export interface OptionSpec extends PathOptionSpec {
|
||||
/**
|
||||
* May be used to denote the name of the embedding application
|
||||
*/
|
||||
embeddingApp?: string | undefined;
|
||||
|
||||
// ui?: string;
|
||||
|
||||
/**
|
||||
* If set to true || "true" or if left undefined, the engine will automatically select the first available
|
||||
* keyboard for activation.
|
||||
*
|
||||
* Note that keyboards specified locally are synchronously loaded while cloud keyboards are async; as a
|
||||
* result, a locally-specified keyboard will generally be available "sooner", even if added "later".
|
||||
*/
|
||||
setActiveOnRegister?: string | boolean; // TODO: Convert to boolean. Option loader needs to be able to receive this as a string or boolean
|
||||
|
||||
/**
|
||||
* Determines the default text shown on the spacebar. If undefined, uses `LANGUAGE_KEYBOARD`
|
||||
*/
|
||||
spacebarText?: SpacebarText;
|
||||
}
|
||||
|
||||
export const OptionDefaults: Required<OptionSpec> = {
|
||||
embeddingApp: undefined,
|
||||
root: '',
|
||||
resources: '',
|
||||
keyboards: '',
|
||||
fonts: '',
|
||||
setActiveOnRegister: true,
|
||||
spacebarText: SpacebarText.LANGUAGE_KEYBOARD,
|
||||
}
|
||||
71
web/src/engine/configuration/src/pathConfiguration.ts
Normal file
71
web/src/engine/configuration/src/pathConfiguration.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { PathOptionSpec } from "./optionSpec.interface.js";
|
||||
|
||||
export default class PathConfiguration {
|
||||
readonly root: string;
|
||||
readonly resources: string;
|
||||
readonly keyboards: string;
|
||||
readonly fonts: string;
|
||||
readonly protocol: string;
|
||||
|
||||
constructor(pathSpec: Required<PathOptionSpec>, sourcePath: string) {
|
||||
const addDelimiter = (p: string) => {
|
||||
// Add delimiter if missing
|
||||
if(p.substring(p.length-1, p.length) != '/') {
|
||||
return p + '/';
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
sourcePath = addDelimiter(sourcePath);
|
||||
const _rootPath = sourcePath.replace(/(https?:\/\/)([^\/]*)(.*)/,'$1$2/');
|
||||
this.protocol = sourcePath.replace(/(.{3,5}:)(.*)/,'$1');
|
||||
|
||||
// Local function to convert relative to absolute URLs
|
||||
// with respect to the source path, server root and protocol
|
||||
const fixPath = (p: string) => {
|
||||
if(p.length == 0) {
|
||||
return p;
|
||||
}
|
||||
|
||||
p = addDelimiter(p);
|
||||
|
||||
// Absolute
|
||||
if((p.replace(/^(http)s?:.*/,'$1') == 'http') || (p.replace(/^(file):.*/,'$1') == 'file')) {
|
||||
return p;
|
||||
}
|
||||
|
||||
// Absolute (except for protocol)
|
||||
if(p.substring(0,2) == '//') {
|
||||
return this.protocol + p;
|
||||
}
|
||||
|
||||
// Relative to server root
|
||||
if(p.substring(0,1) == '/') {
|
||||
return this.root + p.substring(1);
|
||||
}
|
||||
|
||||
// Otherwise, assume relative to source path
|
||||
return sourcePath + p;
|
||||
}
|
||||
|
||||
// Get default paths and device options
|
||||
this.root = _rootPath;
|
||||
if(pathSpec.root != '') {
|
||||
this.root = fixPath(pathSpec.root);
|
||||
} else {
|
||||
this.root = fixPath(_rootPath);
|
||||
}
|
||||
|
||||
// Resources are located with respect to the engine by default
|
||||
let resources = pathSpec.resources; // avoid mutating the parameter!
|
||||
if(resources == '') {
|
||||
resources = sourcePath;
|
||||
}
|
||||
|
||||
// Convert resource, keyboard and font paths to absolute URLs
|
||||
this.resources = fixPath(resources);
|
||||
this.keyboards = fixPath(pathSpec.keyboards);
|
||||
this.fonts = fixPath(pathSpec.fonts);
|
||||
}
|
||||
}
|
||||
24
web/src/engine/configuration/tsconfig.json
Normal file
24
web/src/engine/configuration/tsconfig.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig-base.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"inlineSources": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"module": "es6",
|
||||
"moduleResolution": "Node16",
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"baseUrl": "./",
|
||||
"outDir": "../../../build/engine/configuration/obj/",
|
||||
"tsBuildInfoFile": "../../../build/engine/configuration/obj/tsconfig.tsbuildinfo",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
|
||||
"include": [ "**/*.ts" ],
|
||||
|
||||
"references": [
|
||||
{ "path": "../../../../common/web/keyman-version" },
|
||||
{ "path": "../../../../common/web/utils" }
|
||||
]
|
||||
}
|
||||
|
|
@ -4,29 +4,34 @@ import { DeviceSpec, Version } from "@keymanapp/web-utils";
|
|||
// The Device object definition -------------------------------------------------
|
||||
|
||||
export class Device {
|
||||
// These correspond directly to the properties & parameters for `DeviceSpec`.
|
||||
touchable: boolean;
|
||||
OS: string;
|
||||
formFactor: string;
|
||||
dyPortrait: number;
|
||||
dyLandscape: number;
|
||||
version: string;
|
||||
orientation: string|number;
|
||||
browser: string;
|
||||
colorScheme: 'light' | 'dark';
|
||||
|
||||
// These components aren't needed for key events. All but `version` could be a sort
|
||||
// of `DeviceStyle`.
|
||||
dyPortrait: number; // Its value is only referenced by an unused method.
|
||||
dyLandscape: number; // Its value is only referenced by an unused method.
|
||||
orientation: string|number; // Appears to be unused as well?
|
||||
colorScheme: 'light' | 'dark'; // Also unused?
|
||||
version: string; // As in, device version; only really persisted for Android.
|
||||
// No real sign of actual use, though.
|
||||
|
||||
private detected: boolean = false;
|
||||
private _styles: StyleConstants;
|
||||
|
||||
// Generates a default Device value.
|
||||
constructor() {
|
||||
this.touchable = !!('ontouchstart' in window);
|
||||
this.OS = '';
|
||||
this.formFactor='desktop';
|
||||
this.browser='';
|
||||
|
||||
this.dyPortrait=0;
|
||||
this.dyLandscape=0;
|
||||
this.version='0';
|
||||
this.orientation=window.orientation;
|
||||
this.browser='';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -200,4 +205,8 @@ export class Device {
|
|||
}
|
||||
}
|
||||
|
||||
export default Device;
|
||||
export default Device;
|
||||
|
||||
export function physicalKeyDeviceAlias(device: DeviceSpec) {
|
||||
return new DeviceSpec(device.browser, DeviceSpec.FormFactor.Desktop, device.OS, false);
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
"allowJs": false,
|
||||
"declaration": true,
|
||||
"inlineSources": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"module": "es6",
|
||||
"moduleResolution": "Node16",
|
||||
"sourceMap": true,
|
||||
|
|
@ -12,7 +13,7 @@
|
|||
"baseUrl": "./",
|
||||
"outDir": "../../../build/engine/device-detect/obj/",
|
||||
"tsBuildInfoFile": "../../../build/engine/device-detect/obj/tsconfig.tsbuildinfo",
|
||||
"rootDir": "."
|
||||
"rootDir": "./src"
|
||||
},
|
||||
|
||||
"include": [ "**/*.ts" ],
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ namespace com.keyman {
|
|||
resources?: string;
|
||||
keyboards?: string;
|
||||
fonts?: string;
|
||||
// attachType and ui are 100% ignored for embedded (app-WebView hosted) contexts.
|
||||
// They should only be expected for website-based KMW use.
|
||||
attachType?: 'auto' | 'manual' | ''; // If blank or undefined, attachType will be assigned to "auto" or "manual"
|
||||
ui?: string;
|
||||
setActiveOnRegister?: string; // TODO: Convert to boolean. Option loader needs to be able to receive this as a string or boolean
|
||||
|
|
@ -47,6 +49,7 @@ namespace com.keyman {
|
|||
spacebarText?: SpacebarText;
|
||||
|
||||
// Determines whether or not KeymanWeb should display its own alert messages
|
||||
// Only relevant for website-based KMW use.
|
||||
useAlerts?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
import { assert } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { OptionDefaults, PathConfiguration } from 'keyman/engine/configuration';
|
||||
|
||||
// Tests the activation-state logic abstraction & implementations used to model and control OSK visibility.
|
||||
|
||||
describe("Path Configuration", () => {
|
||||
it('https://test.site.com/folder/, default options', () => {
|
||||
const paths = new PathConfiguration(OptionDefaults, 'https://test.site.com/folder/');
|
||||
|
||||
assert.equal(paths.protocol, 'https:');
|
||||
assert.equal(paths.root, 'https://test.site.com/');
|
||||
assert.equal(paths.resources, 'https://test.site.com/folder/');
|
||||
assert.equal(paths.keyboards, '');
|
||||
assert.equal(paths.fonts, '');
|
||||
});
|
||||
|
||||
it('https://test.site.com/folder, non-standard option values', () => {
|
||||
const paths = new PathConfiguration({...OptionDefaults,
|
||||
keyboards: 'https://s.keyman.com/keyboard',
|
||||
fonts: '/fonts',
|
||||
resources: 'resources',
|
||||
root: '//something.or.other'
|
||||
}, 'https://test.site.com/folder/');
|
||||
|
||||
assert.equal(paths.protocol, 'https:');
|
||||
assert.equal(paths.root, 'https://something.or.other/');
|
||||
assert.equal(paths.resources, 'https://test.site.com/folder/resources/');
|
||||
assert.equal(paths.keyboards, 'https://s.keyman.com/keyboard/');
|
||||
assert.equal(paths.fonts, 'https://something.or.other/fonts/');
|
||||
});
|
||||
|
||||
it('http://test.site.com/folder, non-standard option values', () => {
|
||||
const paths = new PathConfiguration({...OptionDefaults,
|
||||
keyboards: 'https://s.keyman.com/keyboard',
|
||||
fonts: '/fonts',
|
||||
resources: 'resources',
|
||||
root: '//something.or.other'
|
||||
}, 'http://test.site.com/folder');
|
||||
|
||||
assert.equal(paths.protocol, 'http:');
|
||||
assert.equal(paths.root, 'http://something.or.other/');
|
||||
assert.equal(paths.resources, 'http://test.site.com/folder/resources/');
|
||||
assert.equal(paths.keyboards, 'https://s.keyman.com/keyboard/');
|
||||
assert.equal(paths.fonts, 'http://something.or.other/fonts/');
|
||||
});
|
||||
|
||||
it('http://localhost/keymanweb/src/test/manual/web', () => {
|
||||
const paths = new PathConfiguration(OptionDefaults, 'http://localhost/keymanweb/src/test/manual/web');
|
||||
|
||||
assert.equal(paths.protocol, 'http:');
|
||||
assert.equal(paths.root, 'http://localhost/');
|
||||
assert.equal(paths.resources, 'http://localhost/keymanweb/src/test/manual/web/');
|
||||
assert.equal(paths.keyboards, '');
|
||||
assert.equal(paths.fonts, '');
|
||||
});
|
||||
|
||||
it('file:///C:/keymanapp/keyman/web/src/test/manual/web', () => {
|
||||
const paths = new PathConfiguration({...OptionDefaults,
|
||||
resources: '../../../../build/resources'
|
||||
}, 'file:///C:/keymanapp/keyman/web/src/test/manual/web');
|
||||
|
||||
assert.equal(paths.protocol, 'file:');
|
||||
// Is this proper? We just treat 'root' differently (by default) if being served via file:/// ?
|
||||
assert.equal(paths.root, 'file:///C:/keymanapp/keyman/web/src/test/manual/web/');
|
||||
assert.equal(paths.resources, 'file:///C:/keymanapp/keyman/web/src/test/manual/web/../../../../build/resources/');
|
||||
assert.equal(paths.keyboards, '');
|
||||
assert.equal(paths.fonts, '');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue