| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  | import {Translation} from "../../UI/i18n/Translation"; | 
					
						
							|  |  |  | import UnitConfigJson from "./UnitConfigJson"; | 
					
						
							|  |  |  | import Translations from "../../UI/i18n/Translations"; | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  | import BaseUIElement from "../../UI/BaseUIElement"; | 
					
						
							|  |  |  | import Combine from "../../UI/Base/Combine"; | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | export class Unit { | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |     public readonly appliesToKeys: Set<string>; | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |     public readonly denominations: Denomination[]; | 
					
						
							| 
									
										
										
										
											2021-06-25 18:30:56 +02:00
										 |  |  |     public readonly denominationsSorted: Denomination[]; | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |     public readonly defaultDenom: Denomination; | 
					
						
							| 
									
										
										
										
											2021-06-25 18:30:56 +02:00
										 |  |  |     public readonly eraseInvalid: boolean; | 
					
						
							| 
									
										
										
										
											2021-06-25 20:39:01 +02:00
										 |  |  |     private readonly possiblePostFixes: string[] = [] | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor(appliesToKeys: string[], applicableUnits: Denomination[], eraseInvalid: boolean) { | 
					
						
							|  |  |  |         this.appliesToKeys = new Set(appliesToKeys); | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |         this.denominations = applicableUnits; | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |         this.defaultDenom = applicableUnits.filter(denom => denom.default)[0] | 
					
						
							|  |  |  |         this.eraseInvalid = eraseInvalid | 
					
						
							| 
									
										
										
										
											2021-06-25 18:30:56 +02:00
										 |  |  |          | 
					
						
							|  |  |  |         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 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const duplicate = denomination.alternativeDenominations.filter(denom => seenUnitExtensions.has(denom)) | 
					
						
							|  |  |  |             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) | 
					
						
							| 
									
										
										
										
											2021-06-25 20:39:01 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         const possiblePostFixes = new Set<string>() | 
					
						
							|  |  |  |         function addPostfixesOf(str){ | 
					
						
							| 
									
										
										
										
											2021-07-04 20:36:19 +02:00
										 |  |  |             str = str.toLowerCase() | 
					
						
							| 
									
										
										
										
											2021-06-25 20:39:01 +02:00
										 |  |  |             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) | 
					
						
							|  |  |  |             denomination.alternativeDenominations.forEach(addPostfixesOf) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.possiblePostFixes = Array.from(possiblePostFixes) | 
					
						
							|  |  |  |         this.possiblePostFixes.sort((a, b) => b.length - a .length) | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |     isApplicableToKey(key: string | undefined): boolean { | 
					
						
							|  |  |  |         if (key === undefined) { | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |         return this.appliesToKeys.has(key); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Finds which denomination is applicable and gives the stripped value back | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |     findDenomination(valueWithDenom: string): [string, Denomination] { | 
					
						
							| 
									
										
										
										
											2021-06-25 18:30:56 +02:00
										 |  |  |         if(valueWithDenom === undefined){ | 
					
						
							|  |  |  |             return undefined; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (const denomination of this.denominationsSorted) { | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |             const bare = denomination.StrippedValue(valueWithDenom) | 
					
						
							|  |  |  |             if (bare !== null) { | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |                 return [bare, denomination] | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |         return [undefined, undefined] | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     asHumanLongValue(value: string): BaseUIElement { | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |         if (value === undefined) { | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |             return undefined; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const [stripped, denom] = this.findDenomination(value) | 
					
						
							|  |  |  |         const human = denom.human | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const elems = denom.prefix ? [human, stripped] : [stripped, human]; | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |         return new Combine(elems) | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-06-25 20:39:01 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      *   Returns the value without any (sub)parts of any denomination - usefull as preprocessing step for validating inputs. | 
					
						
							|  |  |  |      *   E.g. | 
					
						
							|  |  |  |      *   if 'megawatt' is a possible denomination, then '5 Meg' will be rewritten to '5' (which can then be validated as a valid pnat) | 
					
						
							|  |  |  |      *    | 
					
						
							|  |  |  |      *   Returns the original string if nothign matches | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     stripUnitParts(str: string) { | 
					
						
							|  |  |  |         if(str === undefined){ | 
					
						
							|  |  |  |             return undefined; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const denominationPart of this.possiblePostFixes) { | 
					
						
							|  |  |  |             if(str.endsWith(denominationPart)){ | 
					
						
							|  |  |  |                 return str.substring(0, str.length - denominationPart.length).trim() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         return str; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class Denomination { | 
					
						
							|  |  |  |     public readonly canonical: string; | 
					
						
							|  |  |  |     readonly default: boolean; | 
					
						
							|  |  |  |     readonly prefix: boolean; | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |     private readonly _human: Translation; | 
					
						
							| 
									
										
										
										
											2021-06-25 18:30:56 +02:00
										 |  |  |     public readonly alternativeDenominations: string []; | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor(json: UnitConfigJson, context: string) { | 
					
						
							|  |  |  |         context = `${context}.unit(${json.canonicalDenomination})` | 
					
						
							|  |  |  |         this.canonical = json.canonicalDenomination.trim() | 
					
						
							| 
									
										
										
										
											2021-06-25 18:30:56 +02:00
										 |  |  |         if (this.canonical === undefined) { | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  |             throw `${context}: this unit has no decent canonical value defined` | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         json.alternativeDenomination.forEach((v, i) => { | 
					
						
							|  |  |  |             if (((v?.trim() ?? "") === "")) { | 
					
						
							|  |  |  |                 throw `${context}.alternativeDenomination.${i}: invalid alternative denomination: undefined, null or only whitespace` | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.alternativeDenominations = json.alternativeDenomination?.map(v => v.trim()) ?? [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.default = json.default ?? false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |         this._human = Translations.T(json.human, context + "human") | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.prefix = json.prefix ?? false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     get human(): Translation { | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |         return this._human.Clone() | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |     public canonicalValue(value: string, actAsDefault?: boolean) { | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |         if (value === undefined) { | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |             return undefined; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const stripped = this.StrippedValue(value, actAsDefault) | 
					
						
							|  |  |  |         if (stripped === null) { | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  |             return null; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-07-03 21:23:11 +02:00
										 |  |  |         return stripped + " " + this.canonical.trim() | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Returns the core value (without unit) if: | 
					
						
							|  |  |  |      * - the value ends with the canonical or an alternative value (or begins with if prefix is set) | 
					
						
							|  |  |  |      * - the value is a Number (without unit) and default is set | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Returns null if it doesn't match this unit | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |     public StrippedValue(value: string, actAsDefault?: boolean): string { | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  |         if (value === undefined) { | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |             return undefined; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-06-22 12:13:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-25 18:30:56 +02:00
										 |  |  |         value = value.toLowerCase() | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  |         if (this.prefix) { | 
					
						
							| 
									
										
										
										
											2021-06-25 20:39:01 +02:00
										 |  |  |             if (value.startsWith(this.canonical) && this.canonical !== "") { | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  |                 return value.substring(this.canonical.length).trim(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             for (const alternativeValue of this.alternativeDenominations) { | 
					
						
							|  |  |  |                 if (value.startsWith(alternativeValue)) { | 
					
						
							|  |  |  |                     return value.substring(alternativeValue.length).trim(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2021-07-04 20:36:19 +02:00
										 |  |  |             if (value.endsWith(this.canonical.toLowerCase()) && this.canonical !== "") { | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  |                 return value.substring(0, value.length - this.canonical.length).trim(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             for (const alternativeValue of this.alternativeDenominations) { | 
					
						
							| 
									
										
										
										
											2021-07-04 20:36:19 +02:00
										 |  |  |                 if (value.endsWith(alternativeValue.toLowerCase())) { | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  |                     return value.substring(0, value.length - alternativeValue.length).trim(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-22 03:16:45 +02:00
										 |  |  |         if (this.default || actAsDefault) { | 
					
						
							| 
									
										
										
										
											2021-06-22 00:29:07 +02:00
										 |  |  |             const parsed = Number(value.trim()) | 
					
						
							|  |  |  |             if (!isNaN(parsed)) { | 
					
						
							|  |  |  |                 return value.trim(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |