2021-08-07 23:11:34 +02:00
import BaseUIElement from "../UI/BaseUIElement"
import { FixedUiElement } from "../UI/Base/FixedUiElement"
import Combine from "../UI/Base/Combine"
import { Denomination } from "./Denomination"
2021-09-13 01:21:47 +02:00
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
2021-08-07 23:11:34 +02:00
export class Unit {
public readonly appliesToKeys : Set < string >
public readonly denominations : Denomination [ ]
public readonly denominationsSorted : Denomination [ ]
public readonly eraseInvalid : boolean
2022-09-08 21:40:48 +02:00
2022-08-18 19:17:15 +02:00
constructor (
appliesToKeys : string [ ] ,
applicableDenominations : Denomination [ ] ,
eraseInvalid : boolean
) {
2021-08-07 23:11:34 +02:00
this . appliesToKeys = new Set ( appliesToKeys )
2022-08-18 19:17:15 +02:00
this . denominations = applicableDenominations
2021-08-07 23:11:34 +02:00
this . eraseInvalid = eraseInvalid
const seenUnitExtensions = new Set < string > ( )
for ( const denomination of this . denominations ) {
if ( seenUnitExtensions . has ( denomination . canonical ) ) {
throw (
"This canonical unit is already defined in another denomination: " +
denomination . canonical
2022-09-08 21:40:48 +02:00
)
2021-08-07 23:11:34 +02:00
}
const duplicate = denomination . alternativeDenominations . filter ( ( denom ) = >
seenUnitExtensions . has ( denom )
2022-09-08 21:40:48 +02:00
)
2021-08-07 23:11:34 +02:00
if ( duplicate . length > 0 ) {
throw "A denomination is used multiple times: " + duplicate . join ( ", " )
}
seenUnitExtensions . add ( denomination . canonical )
denomination . alternativeDenominations . forEach ( ( d ) = > seenUnitExtensions . add ( d ) )
}
this . denominationsSorted = [ . . . this . denominations ]
this . denominationsSorted . sort ( ( a , b ) = > b . canonical . length - a . canonical . length )
const possiblePostFixes = new Set < string > ( )
function addPostfixesOf ( str ) {
2021-11-07 16:34:51 +01:00
if ( str === undefined ) {
return
}
2021-08-07 23:11:34 +02:00
str = str . toLowerCase ( )
for ( let i = 0 ; i < str . length + 1 ; i ++ ) {
const substr = str . substring ( 0 , i )
possiblePostFixes . add ( substr )
}
}
for ( const denomination of this . denominations ) {
addPostfixesOf ( denomination . canonical )
2021-09-13 02:38:20 +02:00
addPostfixesOf ( denomination . _canonicalSingular )
2021-08-07 23:11:34 +02:00
denomination . alternativeDenominations . forEach ( addPostfixesOf )
}
}
2023-01-02 02:35:40 +01:00
/ * *
*
* // Should detect invalid defaultInput
* let threwError = false
* try {
* Unit . fromJson ( {
* appliesToKey : [ "length" ] ,
* defaultInput : "xcm" ,
* applicableUnits : [
* {
* canonicalDenomination : "m" ,
* useIfNoUnitGiven : true ,
* human : "meter"
* }
* ]
* } , "test" )
* } catch ( e ) {
2023-01-03 00:36:44 +01:00
* threwError = true
2023-01-02 02:35:40 +01:00
* }
2023-01-03 00:36:44 +01:00
* threwError // => true
2023-01-02 02:35:40 +01:00
*
* // Should work
* Unit . fromJson ( {
* appliesToKey : [ "length" ] ,
* defaultInput : "xcm" ,
* applicableUnits : [
* {
* canonicalDenomination : "m" ,
* useIfNoUnitGiven : true ,
* humen : "meter"
* } ,
* {
* canonicalDenomination : "cm" ,
* human : "centimeter"
* }
* ]
* } , "test" )
* /
2021-11-07 16:34:51 +01:00
static fromJson ( json : UnitConfigJson , ctx : string ) {
2021-09-13 01:21:47 +02:00
const appliesTo = json . appliesToKey
for ( let i = 0 ; i < appliesTo . length ; i ++ ) {
let key = appliesTo [ i ]
if ( key . trim ( ) !== key ) {
throw ` ${ ctx } .appliesToKey[ ${ i } ] is invalid: it starts or ends with whitespace `
}
}
if ( ( json . applicableUnits ? ? [ ] ) . length === 0 ) {
throw ` ${ ctx } : define at least one applicable unit `
}
// Some keys do have unit handling
const applicable = json . applicableUnits . map (
2023-01-02 02:35:40 +01:00
( u , i ) = >
new Denomination (
u ,
2023-01-02 21:19:01 +01:00
u . canonicalDenomination === undefined
? undefined
: u . canonicalDenomination . trim ( ) === json . defaultInput ,
2023-01-02 02:35:40 +01:00
` ${ ctx } .units[ ${ i } ] `
)
2022-09-08 21:40:48 +02:00
)
2023-06-11 02:32:14 +02:00
if ( json . defaultInput && ! applicable . some ( denom = > denom . canonical . trim ( ) === json . defaultInput ) ) {
throw ` ${ ctx } : no denomination has the specified default denomination. The default denomination is ' ${ json . defaultInput } ', but the available denominations are ${ applicable . map ( denom = > denom . canonical ) . join ( ", " ) } `
}
2021-09-13 01:21:47 +02:00
return new Unit ( appliesTo , applicable , json . eraseInvalidValues ? ? false )
}
2021-11-07 16:34:51 +01:00
2021-08-07 23:11:34 +02:00
isApplicableToKey ( key : string | undefined ) : boolean {
if ( key === undefined ) {
return false
}
return this . appliesToKeys . has ( key )
}
/ * *
* Finds which denomination is applicable and gives the stripped value back
* /
2022-08-18 19:17:15 +02:00
findDenomination ( valueWithDenom : string , country : ( ) = > string ) : [ string , Denomination ] {
2021-08-07 23:11:34 +02:00
if ( valueWithDenom === undefined ) {
return undefined
}
2022-08-18 19:17:15 +02:00
const defaultDenom = this . getDefaultDenomination ( country )
2021-08-07 23:11:34 +02:00
for ( const denomination of this . denominationsSorted ) {
2022-08-18 19:17:15 +02:00
const bare = denomination . StrippedValue ( valueWithDenom , defaultDenom === denomination )
2021-08-07 23:11:34 +02:00
if ( bare !== null ) {
return [ bare , denomination ]
}
}
return [ undefined , undefined ]
}
2022-08-18 19:17:15 +02:00
asHumanLongValue ( value : string , country : ( ) = > string ) : BaseUIElement {
2021-08-07 23:11:34 +02:00
if ( value === undefined ) {
return undefined
}
2022-08-18 19:17:15 +02:00
const [ stripped , denom ] = this . findDenomination ( value , country )
2021-11-07 16:34:51 +01:00
const human = stripped === "1" ? denom?.humanSingular : denom?.human
2021-08-07 23:11:34 +02:00
if ( human === undefined ) {
return new FixedUiElement ( stripped ? ? value )
}
const elems = denom . prefix ? [ human , stripped ] : [ stripped , human ]
return new Combine ( elems )
}
2022-08-18 19:17:15 +02:00
public getDefaultInput ( country : ( ) = > string | string [ ] ) {
console . log ( "Searching the default denomination for input" , country )
for ( const denomination of this . denominations ) {
if ( denomination . useAsDefaultInput === true ) {
return denomination
}
if (
denomination . useAsDefaultInput === undefined ||
denomination . useAsDefaultInput === false
) {
continue
}
let countries : string | string [ ] = country ( )
if ( typeof countries === "string" ) {
countries = countries . split ( "," )
}
const denominationCountries : string [ ] = denomination . useAsDefaultInput
if ( countries . some ( ( country ) = > denominationCountries . indexOf ( country ) >= 0 ) ) {
return denomination
2021-08-07 23:11:34 +02:00
}
}
2022-08-18 19:17:15 +02:00
return this . denominations [ 0 ]
2021-08-07 23:11:34 +02:00
}
2022-09-08 21:40:48 +02:00
2022-08-18 19:17:15 +02:00
public getDefaultDenomination ( country : ( ) = > string ) {
for ( const denomination of this . denominations ) {
if ( denomination . useIfNoUnitGiven === true || denomination . canonical === "" ) {
return denomination
}
if (
denomination . useIfNoUnitGiven === undefined ||
denomination . useIfNoUnitGiven === false
) {
continue
}
2023-06-11 01:32:30 +02:00
let countries : string | string [ ] = country ( ) ? ? [ ]
2022-08-18 19:17:15 +02:00
if ( typeof countries === "string" ) {
countries = countries . split ( "," )
}
const denominationCountries : string [ ] = denomination . useIfNoUnitGiven
if ( countries . some ( ( country ) = > denominationCountries . indexOf ( country ) >= 0 ) ) {
return denomination
}
}
return this . denominations [ 0 ]
}
2021-08-07 23:11:34 +02:00
}