forked from MapComplete/MapComplete
		
	
		
			
	
	
		
			114 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			114 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								import BaseUIElement from "../UI/BaseUIElement";
							 | 
						||
| 
								 | 
							
								import {FixedUiElement} from "../UI/Base/FixedUiElement";
							 | 
						||
| 
								 | 
							
								import Combine from "../UI/Base/Combine";
							 | 
						||
| 
								 | 
							
								import {Denomination} from "./Denomination";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export class Unit {
							 | 
						||
| 
								 | 
							
								    public readonly appliesToKeys: Set<string>;
							 | 
						||
| 
								 | 
							
								    public readonly denominations: Denomination[];
							 | 
						||
| 
								 | 
							
								    public readonly denominationsSorted: Denomination[];
							 | 
						||
| 
								 | 
							
								    public readonly defaultDenom: Denomination;
							 | 
						||
| 
								 | 
							
								    public readonly eraseInvalid: boolean;
							 | 
						||
| 
								 | 
							
								    private readonly possiblePostFixes: string[] = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    constructor(appliesToKeys: string[], applicableUnits: Denomination[], eraseInvalid: boolean) {
							 | 
						||
| 
								 | 
							
								        this.appliesToKeys = new Set(appliesToKeys);
							 | 
						||
| 
								 | 
							
								        this.denominations = applicableUnits;
							 | 
						||
| 
								 | 
							
								        this.defaultDenom = applicableUnits.filter(denom => denom.default)[0]
							 | 
						||
| 
								 | 
							
								        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
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            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)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        const possiblePostFixes = new Set<string>()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        function addPostfixesOf(str) {
							 | 
						||
| 
								 | 
							
								            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)
							 | 
						||
| 
								 | 
							
								            denomination.alternativeDenominations.forEach(addPostfixesOf)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        this.possiblePostFixes = Array.from(possiblePostFixes)
							 | 
						||
| 
								 | 
							
								        this.possiblePostFixes.sort((a, b) => b.length - a.length)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    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
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    findDenomination(valueWithDenom: string): [string, Denomination] {
							 | 
						||
| 
								 | 
							
								        if (valueWithDenom === undefined) {
							 | 
						||
| 
								 | 
							
								            return undefined;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        for (const denomination of this.denominationsSorted) {
							 | 
						||
| 
								 | 
							
								            const bare = denomination.StrippedValue(valueWithDenom)
							 | 
						||
| 
								 | 
							
								            if (bare !== null) {
							 | 
						||
| 
								 | 
							
								                return [bare, denomination]
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return [undefined, undefined]
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    asHumanLongValue(value: string): BaseUIElement {
							 | 
						||
| 
								 | 
							
								        if (value === undefined) {
							 | 
						||
| 
								 | 
							
								            return undefined;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        const [stripped, denom] = this.findDenomination(value)
							 | 
						||
| 
								 | 
							
								        const human = denom?.human
							 | 
						||
| 
								 | 
							
								        if (human === undefined) {
							 | 
						||
| 
								 | 
							
								            return new FixedUiElement(stripped ?? value);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        const elems = denom.prefix ? [human, stripped] : [stripped, human];
							 | 
						||
| 
								 | 
							
								        return new Combine(elems)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     *   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;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |