diff --git a/common/tools/sourcemap-path-remapper/build.sh b/common/tools/sourcemap-path-remapper/build.sh index 0351211a81..55bc386eab 100755 --- a/common/tools/sourcemap-path-remapper/build.sh +++ b/common/tools/sourcemap-path-remapper/build.sh @@ -7,8 +7,6 @@ # Exit on command failure and when using unset variables: set -eu -# Include some helper functions from resources - ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" diff --git a/package-lock.json b/package-lock.json index a1f7d1dc64..2c75f7b58c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7934,7 +7934,7 @@ "@keymanapp/web-utils": "*", "@types/node": "^10.17.21", "chai": "^4.3.4", - "combine-source-map": "*", + "combine-source-map": "^0.8.0", "es6-shim": "^0.35.5", "karma": "^6.4.1", "karma-browserstack-launcher": "^1.6.0", diff --git a/web/ci.sh b/web/ci.sh index d9dfcbf586..629d61d1a0 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -39,6 +39,10 @@ builder_parse "$@" #### +TIER=`cat ../TIER.md` +BUILD_NUMBER=`cat ../VERSION.md` +S_KEYMAN_COM=../../s.keyman.com + if builder_start_action build; then # Build step: since CI builds start (and should start) from scratch, run the following # three actions: diff --git a/web/src/engine/device-detect/kmwdevice.ts b/web/src/engine/device-detect/kmwdevice.ts index 1d55068160..7a596cd25b 100644 --- a/web/src/engine/device-detect/kmwdevice.ts +++ b/web/src/engine/device-detect/kmwdevice.ts @@ -1,203 +1,201 @@ -// Includes version-related functionality -/// +import StyleConstants from 'utils/styleConstants.js'; +import { DeviceSpec, Version } from "@keymanapp/web-utils/build/obj/index.js"; // The Device object definition ------------------------------------------------- -namespace com.keyman { - export class Device { - touchable: boolean; - OS: string; - formFactor: string; - dyPortrait: number; - dyLandscape: number; - version: string; - orientation: string|number; - browser: string; - colorScheme: 'light' | 'dark'; +export class Device { + touchable: boolean; + OS: string; + formFactor: string; + dyPortrait: number; + dyLandscape: number; + version: string; + orientation: string|number; + browser: string; + colorScheme: 'light' | 'dark'; - private detected: boolean = false; - private _styles: utils.StyleConstants; + private detected: boolean = false; + private _styles: StyleConstants; - // Generates a default Device value. - constructor() { - this.touchable = !!('ontouchstart' in window); - this.OS = ''; - this.formFactor='desktop'; - this.dyPortrait=0; - this.dyLandscape=0; - this.version='0'; - this.orientation=window.orientation; - this.browser=''; - } + // Generates a default Device value. + constructor() { + this.touchable = !!('ontouchstart' in window); + this.OS = ''; + this.formFactor='desktop'; + this.dyPortrait=0; + this.dyLandscape=0; + this.version='0'; + this.orientation=window.orientation; + this.browser=''; + } - /** - * Get device horizontal DPI for touch devices, to set actual size of active regions - * Note that the actual physical DPI may be somewhat different. - * - * @return {number} - */ - getDPI(): number { - var t=document.createElement('DIV') ,s=t.style,dpi=96; - if(document.readyState !== 'complete') { - return dpi; - } - - t.id='calculateDPI'; - s.position='absolute'; s.display='block';s.visibility='hidden'; - s.left='10px'; s.top='10px'; s.width='1in'; s.height='10px'; - document.body.appendChild(t); - dpi=(typeof window.devicePixelRatio == 'undefined') ? t.offsetWidth : t.offsetWidth * window.devicePixelRatio; - document.body.removeChild(t); + /** + * Get device horizontal DPI for touch devices, to set actual size of active regions + * Note that the actual physical DPI may be somewhat different. + * + * @return {number} + */ + getDPI(): number { + var t=document.createElement('DIV') ,s=t.style,dpi=96; + if(document.readyState !== 'complete') { return dpi; } - detect() : void { - var possMacSpoof = false; + t.id='calculateDPI'; + s.position='absolute'; s.display='block';s.visibility='hidden'; + s.left='10px'; s.top='10px'; s.width='1in'; s.height='10px'; + document.body.appendChild(t); + dpi=(typeof window.devicePixelRatio == 'undefined') ? t.offsetWidth : t.offsetWidth * window.devicePixelRatio; + document.body.removeChild(t); + return dpi; + } - if(navigator && navigator.userAgent) { - var agent=navigator.userAgent; + detect() : void { + var possMacSpoof = false; - if(agent.indexOf('iPad') >= 0) { - this.OS='iOS'; - this.formFactor='tablet'; - this.dyPortrait=this.dyLandscape=0; - } else if(agent.indexOf('iPhone') >= 0) { - this.OS='iOS'; - this.formFactor='phone'; - this.dyPortrait=this.dyLandscape=25; - } else if(agent.indexOf('Android') >= 0) { - this.OS='Android'; - this.formFactor='phone'; // form factor may be redefined on initialization - this.dyPortrait=75; - this.dyLandscape=25; - try { - var rx=new RegExp("(?:Android\\s+)(\\d+\\.\\d+\\.\\d+)"); - this.version=agent.match(rx)[1]; - } catch(ex) {} - } else if(agent.indexOf('Linux') >= 0) { - this.OS='Linux'; - } else if(agent.indexOf('Macintosh') >= 0) { - // Starting with 13.1, "Macintosh" can reflect iPads (by default) or iPhones - // (by user setting); a new "Request Desktop Website" setting for Safari will - // change the user agent string to match a desktop Mac. - // - // Firefox uses '.' between version components, while Chrome and Safari use - // '_' instead. So, we have to check for both. Yay. - let regex = /Intel Mac OS X (\d+(?:[_\.]\d+)+)/i; - let results = regex.exec(agent); + if(navigator && navigator.userAgent) { + var agent=navigator.userAgent; - // Match result: a version string with components separated by underscores. - if(!results) { - console.warn("KMW could not properly parse the user agent string." - + "A suboptimal keyboard layout may result."); - this.OS='MacOSX'; - } else if(results.length > 1 && results[1]) { - // Convert version string into a usable form. - let versionString = results[1].replace('_', '.'); - let version = new utils.Version(versionString); + if(agent.indexOf('iPad') >= 0) { + this.OS='iOS'; + this.formFactor='tablet'; + this.dyPortrait=this.dyLandscape=0; + } else if(agent.indexOf('iPhone') >= 0) { + this.OS='iOS'; + this.formFactor='phone'; + this.dyPortrait=this.dyLandscape=25; + } else if(agent.indexOf('Android') >= 0) { + this.OS='Android'; + this.formFactor='phone'; // form factor may be redefined on initialization + this.dyPortrait=75; + this.dyLandscape=25; + try { + var rx=new RegExp("(?:Android\\s+)(\\d+\\.\\d+\\.\\d+)"); + this.version=agent.match(rx)[1]; + } catch(ex) {} + } else if(agent.indexOf('Linux') >= 0) { + this.OS='Linux'; + } else if(agent.indexOf('Macintosh') >= 0) { + // Starting with 13.1, "Macintosh" can reflect iPads (by default) or iPhones + // (by user setting); a new "Request Desktop Website" setting for Safari will + // change the user agent string to match a desktop Mac. + // + // Firefox uses '.' between version components, while Chrome and Safari use + // '_' instead. So, we have to check for both. Yay. + let regex = /Intel Mac OS X (\d+(?:[_\.]\d+)+)/i; + let results = regex.exec(agent); - possMacSpoof = utils.Version.MAC_POSSIBLE_IPAD_ALIAS.compareTo(version) <= 0; - this.OS='MacOSX'; - } - } else if(agent.indexOf('Windows NT') >= 0) { - this.OS='Windows'; - if(agent.indexOf('Touch') >= 0) { - this.formFactor='phone'; // will be redefined as tablet if resolution high enough - } + // Match result: a version string with components separated by underscores. + if(!results) { + console.warn("KMW could not properly parse the user agent string." + + "A suboptimal keyboard layout may result."); + this.OS='MacOSX'; + } else if(results.length > 1 && results[1]) { + // Convert version string into a usable form. + let versionString = results[1].replace('_', '.'); + let version = new Version(versionString); - // Windows Phone and Tablet PC - if(typeof (navigator).msMaxTouchPoints == 'number' && (navigator).msMaxTouchPoints > 0) { - this.touchable=true; - } + possMacSpoof = Version.MAC_POSSIBLE_IPAD_ALIAS.compareTo(version) <= 0; + this.OS='MacOSX'; + } + } else if(agent.indexOf('Windows NT') >= 0) { + this.OS='Windows'; + if(agent.indexOf('Touch') >= 0) { + this.formFactor='phone'; // will be redefined as tablet if resolution high enough + } + + // Windows Phone and Tablet PC + if(typeof (navigator).msMaxTouchPoints == 'number' && (navigator).msMaxTouchPoints > 0) { + this.touchable=true; } } + } - // We look at the screen resolution for Android, because we can't tell from - // the user agent string whether or not this is supposed to be a tablet. - // It seems that there are a handful of older phones out there that report a - // higher resolution than 700px*___px, but it is proving hard to test these, - // and the majority have an aspect ratio <= 0.5625 anyway. - // But we trust what iOS tells us for phone vs tablet. + // We look at the screen resolution for Android, because we can't tell from + // the user agent string whether or not this is supposed to be a tablet. + // It seems that there are a handful of older phones out there that report a + // higher resolution than 700px*___px, but it is proving hard to test these, + // and the majority have an aspect ratio <= 0.5625 anyway. + // But we trust what iOS tells us for phone vs tablet. - const dimMin = Math.min(screen.width,screen.height), dimMax = Math.max(screen.width,screen.height); - const aspect = dimMin / dimMax; + const dimMin = Math.min(screen.width,screen.height), dimMax = Math.max(screen.width,screen.height); + const aspect = dimMin / dimMax; - if(this.OS != 'iOS' && - this.formFactor == 'phone' && - ((dimMin >= 600 && aspect > 0.5625) || // 0.5625 -> 1920x1080 is common phone res - (aspect >= 0.625)) // all reported devices with aspect >= 0.625 are tablets per https://screensiz.es/ - ) { - this.formFactor='tablet'; - } + if(this.OS != 'iOS' && + this.formFactor == 'phone' && + ((dimMin >= 600 && aspect > 0.5625) || // 0.5625 -> 1920x1080 is common phone res + (aspect >= 0.625)) // all reported devices with aspect >= 0.625 are tablets per https://screensiz.es/ + ) { + this.formFactor='tablet'; + } - // Test for potential Chrome emulation on Windows or macOS X (used only in next if-check) - let possibleChromeEmulation = navigator.platform == 'Win32' || navigator.platform == 'MacIntel' + // Test for potential Chrome emulation on Windows or macOS X (used only in next if-check) + let possibleChromeEmulation = navigator.platform == 'Win32' || navigator.platform == 'MacIntel' - // alert(sxx+'->'+device.formFactor); - // Check for phony iOS devices (but don't undo for Chrome emulation used during development) - if(this.OS == 'iOS' && !('ongesturestart' in window) && !possibleChromeEmulation) { - this.OS='Android'; - } + // alert(sxx+'->'+device.formFactor); + // Check for phony iOS devices (but don't undo for Chrome emulation used during development) + if(this.OS == 'iOS' && !('ongesturestart' in window) && !possibleChromeEmulation) { + this.OS='Android'; + } - // Determine application or browser - this.browser='web'; - if(this.OS == 'iOS' || this.OS.toLowerCase() == 'macosx') { + // Determine application or browser + this.browser='web'; + if(this.OS == 'iOS' || this.OS.toLowerCase() == 'macosx') { + this.browser='safari'; + } + + var bMatch=/Firefox|Chrome|OPR|Safari|Edge/; + if(bMatch.test(navigator.userAgent)) { + if((navigator.userAgent.indexOf('Firefox') >= 0) && ('onmozorientationchange' in screen)) { + this.browser='firefox'; + } else if(navigator.userAgent.indexOf('OPR') >= 0) { + this.browser='opera'; + } else if(navigator.userAgent.indexOf(' Edge/') >= 0) { + // Edge is too common a word, so test for Edge/ :) + // Must come before Chrome and Safari test because + // Edge pretends to be both + this.browser='edge'; + } else if(navigator.userAgent.indexOf('Chrome') >= 0) { + // This test must come before Safari test because on macOS, + // Chrome also reports "Safari" + this.browser='chrome'; + } else if(navigator.userAgent.indexOf('Safari') >= 0) { this.browser='safari'; } - - var bMatch=/Firefox|Chrome|OPR|Safari|Edge/; - if(bMatch.test(navigator.userAgent)) { - if((navigator.userAgent.indexOf('Firefox') >= 0) && ('onmozorientationchange' in screen)) { - this.browser='firefox'; - } else if(navigator.userAgent.indexOf('OPR') >= 0) { - this.browser='opera'; - } else if(navigator.userAgent.indexOf(' Edge/') >= 0) { - // Edge is too common a word, so test for Edge/ :) - // Must come before Chrome and Safari test because - // Edge pretends to be both - this.browser='edge'; - } else if(navigator.userAgent.indexOf('Chrome') >= 0) { - // This test must come before Safari test because on macOS, - // Chrome also reports "Safari" - this.browser='chrome'; - } else if(navigator.userAgent.indexOf('Safari') >= 0) { - this.browser='safari'; - } - } - - if(possMacSpoof && this.browser == 'safari') { - // Indistinguishable user agent string! We need a different test; fortunately, true macOS - // Safari doesn't support TouchEvents. (Chrome does, though! Hence the filter above.) - if(window['TouchEvent']) { - this.OS='iOS'; - this.formFactor='tablet'; - this.dyPortrait=this.dyLandscape=0; - - // It's currently impossible to differentiate between iPhone and iPad here - // except for by screen dimensions. - let aspectRatio = screen.height / screen.width; - if(aspectRatio < 1) { - aspectRatio = 1 / aspectRatio; - } - - // iPhones usually have a ratio of 16:9 (or 1.778) or higher, while iPads use 4:3 (or 1.333) - if(aspectRatio > 1.6) { - // Override - we'll treat this device as an iPhone. - this.formFactor = 'phone'; - this.dyPortrait=this.dyLandscape=25; - } - } - } - - this.colorScheme = utils.StyleConstants.prefersDarkMode() ? 'dark' : 'light'; - this.detected = true; } - /** - * Returns a slimmer, web-core compatible version of this object. - */ - public get coreSpec(): utils.DeviceSpec { - return new utils.DeviceSpec(this.browser, this.formFactor, this.OS, this.touchable); + if(possMacSpoof && this.browser == 'safari') { + // Indistinguishable user agent string! We need a different test; fortunately, true macOS + // Safari doesn't support TouchEvents. (Chrome does, though! Hence the filter above.) + if(window['TouchEvent']) { + this.OS='iOS'; + this.formFactor='tablet'; + this.dyPortrait=this.dyLandscape=0; + + // It's currently impossible to differentiate between iPhone and iPad here + // except for by screen dimensions. + let aspectRatio = screen.height / screen.width; + if(aspectRatio < 1) { + aspectRatio = 1 / aspectRatio; + } + + // iPhones usually have a ratio of 16:9 (or 1.778) or higher, while iPads use 4:3 (or 1.333) + if(aspectRatio > 1.6) { + // Override - we'll treat this device as an iPhone. + this.formFactor = 'phone'; + this.dyPortrait=this.dyLandscape=25; + } + } } + + this.colorScheme = StyleConstants.prefersDarkMode() ? 'dark' : 'light'; + this.detected = true; + } + + /** + * Returns a slimmer, web-core compatible version of this object. + */ + public get coreSpec(): DeviceSpec { + return new DeviceSpec(this.browser, this.formFactor, this.OS, this.touchable); } } \ No newline at end of file diff --git a/web/src/engine/device-detect/tsconfig.bundled.json b/web/src/engine/device-detect/tsconfig.bundled.json deleted file mode 100644 index 61ac455342..0000000000 --- a/web/src/engine/device-detect/tsconfig.bundled.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "compilerOptions": { - "outFile": "../../../build/engine/device-detect/obj/index.bundled.js", - }, - - "references": [ - { "path": "../../../../common/web/keyman-version", "prepend": true }, - { "path": "../../../../common/web/utils", "prepend": true } - ] -} diff --git a/web/src/engine/device-detect/tsconfig.json b/web/src/engine/device-detect/tsconfig.json index 471f6f9b90..b63fa2cc79 100644 --- a/web/src/engine/device-detect/tsconfig.json +++ b/web/src/engine/device-detect/tsconfig.json @@ -2,18 +2,19 @@ "extends": "../../../../tsconfig-base.json", "compilerOptions": { - "allowJs": true, + "allowJs": false, "inlineSources": true, - "module": "none", - "outFile": "../../../build/engine/device-detect/obj/index.js", + "module": "es6", + "moduleResolution": "Node", "sourceMap": true, - "target": "es5" + "target": "es5", + "baseUrl": "./", + "outDir": "../../../build/engine/device-detect/obj/", + "tsBuildInfoFile": "../../../build/engine/device-detect/obj/tsconfig.tsbuildinfo", + "rootDir": "." }, - "files": [ - "kmwdevice.ts", - "utils/styleConstants.ts", // goes with kmwdevice.ts - ], + "include": [ "**/*.ts" ], "references": [ { "path": "../../../../common/web/keyman-version" }, diff --git a/web/src/engine/device-detect/utils/styleConstants.ts b/web/src/engine/device-detect/utils/styleConstants.ts index 0ea2e3be22..90aa2fe307 100644 --- a/web/src/engine/device-detect/utils/styleConstants.ts +++ b/web/src/engine/device-detect/utils/styleConstants.ts @@ -1,34 +1,31 @@ -// Includes Device definitions, which may play a role in constant logic. -/// +import { DeviceSpec } from "@keymanapp/web-utils/build/obj/index.js"; /* * This file is intended for CSS-styling constants that see use with the OSK. */ -namespace com.keyman.utils { - /** - * Defines device-level constants used for CSS styling. - */ - export class StyleConstants { - constructor(device: com.keyman.utils.DeviceSpec) { - // popupCanvasBackgroundColor - if(device.OS == utils.OperatingSystem.Android) { - this.popupCanvasBackgroundColor = '#999'; - } else { - this.popupCanvasBackgroundColor = StyleConstants.prefersDarkMode() ? '#0f1319' : '#ffffff'; - } +/** + * Defines device-level constants used for CSS styling. + */ +export default class StyleConstants { + constructor(device: DeviceSpec) { + // popupCanvasBackgroundColor + if(device.OS == DeviceSpec.OperatingSystem.Android) { + this.popupCanvasBackgroundColor = '#999'; + } else { + this.popupCanvasBackgroundColor = StyleConstants.prefersDarkMode() ? '#0f1319' : '#ffffff'; } - - /** - * Checks is a user's browser is in dark mode, if the feature is supported. Returns false otherwise. - * - * Thanks to https://stackoverflow.com/a/57795518 for this code. - */ - static prefersDarkMode(): boolean { - // Ensure the detector exists (otherwise, returns false) - return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; - } - - public readonly popupCanvasBackgroundColor: string; } + + /** + * Checks is a user's browser is in dark mode, if the feature is supported. Returns false otherwise. + * + * Thanks to https://stackoverflow.com/a/57795518 for this code. + */ + static prefersDarkMode(): boolean { + // Ensure the detector exists (otherwise, returns false) + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + public readonly popupCanvasBackgroundColor: string; } \ No newline at end of file