forked from MapComplete/MapComplete
		
	Add option to show text field inline with the rendering; add option to fill out a default value
This commit is contained in:
		
							parent
							
								
									29ea0ac925
								
							
						
					
					
						commit
						6e3c39e475
					
				
					 10 changed files with 123 additions and 23 deletions
				
			
		| 
						 | 
					@ -26,6 +26,8 @@ export default class TagRenderingConfig {
 | 
				
			||||||
        readonly key: string,
 | 
					        readonly key: string,
 | 
				
			||||||
        readonly type: string,
 | 
					        readonly type: string,
 | 
				
			||||||
        readonly addExtraTags: TagsFilter[];
 | 
					        readonly addExtraTags: TagsFilter[];
 | 
				
			||||||
 | 
					        readonly inline: boolean,
 | 
				
			||||||
 | 
					        readonly default?: string
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    readonly multiAnswer: boolean;
 | 
					    readonly multiAnswer: boolean;
 | 
				
			||||||
| 
						 | 
					@ -73,6 +75,8 @@ export default class TagRenderingConfig {
 | 
				
			||||||
                type: json.freeform.type ?? "string",
 | 
					                type: json.freeform.type ?? "string",
 | 
				
			||||||
                addExtraTags: json.freeform.addExtraTags?.map((tg, i) =>
 | 
					                addExtraTags: json.freeform.addExtraTags?.map((tg, i) =>
 | 
				
			||||||
                    FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [],
 | 
					                    FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [],
 | 
				
			||||||
 | 
					                inline: json.freeform.inline ?? false,
 | 
				
			||||||
 | 
					                default: json.freeform.default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,19 @@ export interface TagRenderingConfigJson {
 | 
				
			||||||
         **/
 | 
					         **/
 | 
				
			||||||
        addExtraTags?: string[];
 | 
					        addExtraTags?: string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * When set, influences the way a question is asked.
 | 
				
			||||||
 | 
					         * Instead of showing a full-widht text field, the text field will be shown within the rendering of the question.
 | 
				
			||||||
 | 
					         * 
 | 
				
			||||||
 | 
					         * This combines badly with special input elements, as it'll distort the layout.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        inline?: boolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * default value to enter if no previous tagging is present.
 | 
				
			||||||
 | 
					         * Normally undefined (aka do not enter anything)
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        default?: string
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										35
									
								
								UI/Input/InputElementWrapper.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								UI/Input/InputElementWrapper.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					import {InputElement} from "./InputElement";
 | 
				
			||||||
 | 
					import {UIEventSource} from "../../Logic/UIEventSource";
 | 
				
			||||||
 | 
					import BaseUIElement from "../BaseUIElement";
 | 
				
			||||||
 | 
					import {Translation} from "../i18n/Translation";
 | 
				
			||||||
 | 
					import {SubstitutedTranslation} from "../SubstitutedTranslation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class InputElementWrapper<T> extends InputElement<T> {
 | 
				
			||||||
 | 
					    public readonly IsSelected: UIEventSource<boolean>;
 | 
				
			||||||
 | 
					    private readonly _inputElement: InputElement<T>;
 | 
				
			||||||
 | 
					    private readonly _renderElement: BaseUIElement
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(inputElement: InputElement<T>, translation: Translation, key: string, tags: UIEventSource<any>) {
 | 
				
			||||||
 | 
					        super()
 | 
				
			||||||
 | 
					        this._inputElement = inputElement;
 | 
				
			||||||
 | 
					        this.IsSelected = inputElement.IsSelected
 | 
				
			||||||
 | 
					        const mapping = new Map<string, BaseUIElement>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mapping.set(key, inputElement)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this._renderElement = new SubstitutedTranslation(translation, tags, mapping)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    GetValue(): UIEventSource<T> {
 | 
				
			||||||
 | 
					        return this._inputElement.GetValue();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    IsValid(t: T): boolean {
 | 
				
			||||||
 | 
					        return this._inputElement.IsValid(t);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected InnerConstructElement(): HTMLElement {
 | 
				
			||||||
 | 
					        return this._renderElement.ConstructElement();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -36,11 +36,11 @@ export class TextField extends InputElement<string> {
 | 
				
			||||||
        this.SetClass("form-text-field")
 | 
					        this.SetClass("form-text-field")
 | 
				
			||||||
        let inputEl: HTMLElement
 | 
					        let inputEl: HTMLElement
 | 
				
			||||||
        if (options.htmlType === "area") {
 | 
					        if (options.htmlType === "area") {
 | 
				
			||||||
 | 
					            this.SetClass("w-full box-border max-w-full")
 | 
				
			||||||
            const el = document.createElement("textarea")
 | 
					            const el = document.createElement("textarea")
 | 
				
			||||||
            el.placeholder = placeholder
 | 
					            el.placeholder = placeholder
 | 
				
			||||||
            el.rows = options.textAreaRows
 | 
					            el.rows = options.textAreaRows
 | 
				
			||||||
            el.cols = 50
 | 
					            el.cols = 50
 | 
				
			||||||
            el.style.cssText = "max-width: 100%; width: 100%; box-sizing: border-box"
 | 
					 | 
				
			||||||
            inputEl = el;
 | 
					            inputEl = el;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            const el = document.createElement("input")
 | 
					            const el = document.createElement("input")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -282,7 +282,7 @@ export default class ValidatedTextField {
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            unitDropDown.GetValue().setData(unit.defaultDenom)
 | 
					            unitDropDown.GetValue().setData(unit.defaultDenom)
 | 
				
			||||||
            unitDropDown.SetStyle("width: min-content")
 | 
					            unitDropDown.SetClass("w-min")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            input = new CombinedInputElement(
 | 
					            input = new CombinedInputElement(
 | 
				
			||||||
                input,
 | 
					                input,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils";
 | 
				
			||||||
import BaseUIElement from "../BaseUIElement";
 | 
					import BaseUIElement from "../BaseUIElement";
 | 
				
			||||||
import {DropDown} from "../Input/DropDown";
 | 
					import {DropDown} from "../Input/DropDown";
 | 
				
			||||||
import {Unit} from "../../Customizations/JSON/Denomination";
 | 
					import {Unit} from "../../Customizations/JSON/Denomination";
 | 
				
			||||||
 | 
					import InputElementWrapper from "../Input/InputElementWrapper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Shows the question element.
 | 
					 * Shows the question element.
 | 
				
			||||||
| 
						 | 
					@ -128,7 +129,7 @@ export default class TagRenderingQuestion extends Combine {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return Utils.NoNull(configuration.mappings?.map((m,i) => excludeIndex === i ? undefined:  m.ifnot))
 | 
					            return Utils.NoNull(configuration.mappings?.map((m,i) => excludeIndex === i ? undefined:  m.ifnot))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource.data);
 | 
					        const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource);
 | 
				
			||||||
        const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0
 | 
					        const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (mappings.length < 8 || configuration.multiAnswer || hasImages) {
 | 
					        if (mappings.length < 8 || configuration.multiAnswer || hasImages) {
 | 
				
			||||||
| 
						 | 
					@ -289,7 +290,7 @@ export default class TagRenderingQuestion extends Combine {
 | 
				
			||||||
            (t0, t1) => t1.isEquivalent(t0));
 | 
					            (t0, t1) => t1.isEquivalent(t0));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tagsData: any): InputElement<TagsFilter> {
 | 
					    private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tags: UIEventSource<any>): InputElement<TagsFilter> {
 | 
				
			||||||
        const freeform = configuration.freeform;
 | 
					        const freeform = configuration.freeform;
 | 
				
			||||||
        if (freeform === undefined) {
 | 
					        if (freeform === undefined) {
 | 
				
			||||||
            return undefined;
 | 
					            return undefined;
 | 
				
			||||||
| 
						 | 
					@ -328,7 +329,8 @@ export default class TagRenderingQuestion extends Combine {
 | 
				
			||||||
            return undefined;
 | 
					            return undefined;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let input: InputElement<string> = ValidatedTextField.InputForType(configuration.freeform.type, {
 | 
					        const tagsData = tags.data;
 | 
				
			||||||
 | 
					        const input: InputElement<string> = ValidatedTextField.InputForType(configuration.freeform.type, {
 | 
				
			||||||
            isValid: (str) => (str.length <= 255),
 | 
					            isValid: (str) => (str.length <= 255),
 | 
				
			||||||
            country: () => tagsData._country,
 | 
					            country: () => tagsData._country,
 | 
				
			||||||
            location: [tagsData._lat, tagsData._lon],
 | 
					            location: [tagsData._lat, tagsData._lon],
 | 
				
			||||||
| 
						 | 
					@ -336,13 +338,23 @@ export default class TagRenderingQuestion extends Combine {
 | 
				
			||||||
            unit: applicableUnit
 | 
					            unit: applicableUnit
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        input.GetValue().setData(tagsData[configuration.freeform.key]);
 | 
					        input.GetValue().setData(tagsData[freeform.key] ?? freeform.default);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new InputElementMap(
 | 
					        let inputTagsFilter : InputElement<TagsFilter> = new InputElementMap(
 | 
				
			||||||
            input, (a, b) => a === b || (a?.isEquivalent(b) ?? false),
 | 
					            input, (a, b) => a === b || (a?.isEquivalent(b) ?? false),
 | 
				
			||||||
            pickString, toString
 | 
					            pickString, toString
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        if(freeform.inline){
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            inputTagsFilter.SetClass("w-16-imp")
 | 
				
			||||||
 | 
					            inputTagsFilter = new InputElementWrapper(inputTagsFilter, configuration.render, freeform.key, tags)
 | 
				
			||||||
 | 
					            inputTagsFilter.SetClass("block")
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return inputTagsFilter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -379,6 +379,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
 | 
					    static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
 | 
				
			||||||
    private static GenHelpMessage() {
 | 
					    private static GenHelpMessage() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,19 +7,43 @@ import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizatio
 | 
				
			||||||
import {Utils} from "../Utils";
 | 
					import {Utils} from "../Utils";
 | 
				
			||||||
import {VariableUiElement} from "./Base/VariableUIElement";
 | 
					import {VariableUiElement} from "./Base/VariableUIElement";
 | 
				
			||||||
import Combine from "./Base/Combine";
 | 
					import Combine from "./Base/Combine";
 | 
				
			||||||
 | 
					import BaseUIElement from "./BaseUIElement";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SubstitutedTranslation extends VariableUiElement {
 | 
					export class SubstitutedTranslation extends VariableUiElement {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public constructor(
 | 
					    public constructor(
 | 
				
			||||||
        translation: Translation,
 | 
					        translation: Translation,
 | 
				
			||||||
        tagsSource: UIEventSource<any>) {
 | 
					        tagsSource: UIEventSource<any>,
 | 
				
			||||||
 | 
					        mapping: Map<string, BaseUIElement> = undefined) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const extraMappings: SpecialVisualization[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mapping?.forEach((value, key) => {
 | 
				
			||||||
 | 
					            console.log("KV:", key, value)
 | 
				
			||||||
 | 
					            extraMappings.push(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    funcName: key,
 | 
				
			||||||
 | 
					                    constr: (() => {
 | 
				
			||||||
 | 
					                        return value
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                    docs: "Dynamically injected input element",
 | 
				
			||||||
 | 
					                    args: [],
 | 
				
			||||||
 | 
					                    example: ""
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super(
 | 
					        super(
 | 
				
			||||||
            Locale.language.map(language => {
 | 
					            Locale.language.map(language => {
 | 
				
			||||||
                const txt = translation.textFor(language)
 | 
					                let txt = translation.textFor(language);
 | 
				
			||||||
                if (txt === undefined) {
 | 
					                if (txt === undefined) {
 | 
				
			||||||
                    return undefined
 | 
					                    return undefined
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt).map(
 | 
					                mapping?.forEach((_, key) => {
 | 
				
			||||||
 | 
					                    txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`)
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map(
 | 
				
			||||||
                    proto => {
 | 
					                    proto => {
 | 
				
			||||||
                        if (proto.fixed !== undefined) {
 | 
					                        if (proto.fixed !== undefined) {
 | 
				
			||||||
                            return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags)));
 | 
					                            return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags)));
 | 
				
			||||||
| 
						 | 
					@ -36,30 +60,35 @@ export class SubstitutedTranslation extends VariableUiElement {
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.SetClass("w-full")
 | 
					        this.SetClass("w-full")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static ExtractSpecialComponents(template: string): {
 | 
					    public static ExtractSpecialComponents(template: string, extraMappings: SpecialVisualization[] = []): {
 | 
				
			||||||
        fixed?: string, special?: {
 | 
					        fixed?: string,
 | 
				
			||||||
 | 
					        special?: {
 | 
				
			||||||
            func: SpecialVisualization,
 | 
					            func: SpecialVisualization,
 | 
				
			||||||
            args: string[],
 | 
					            args: string[],
 | 
				
			||||||
            style: string
 | 
					            style: string
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }[] {
 | 
					    }[] {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
 | 
					        if (extraMappings.length > 0) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            console.log("Extra mappings are", extraMappings)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | 
					            // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | 
				
			||||||
            const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`);
 | 
					            const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`);
 | 
				
			||||||
            if (matched != null) {
 | 
					            if (matched != null) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // We found a special component that should be brought to live
 | 
					                // We found a special component that should be brought to live
 | 
				
			||||||
                const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1]);
 | 
					                const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1], extraMappings);
 | 
				
			||||||
                const argument = matched[2].trim();
 | 
					                const argument = matched[2].trim();
 | 
				
			||||||
                const style = matched[3]?.substring(1) ?? ""
 | 
					                const style = matched[3]?.substring(1) ?? ""
 | 
				
			||||||
                const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4]);
 | 
					                const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4], extraMappings);
 | 
				
			||||||
                const args = knownSpecial.args.map(arg => arg.defaultValue ?? "");
 | 
					                const args = knownSpecial.args.map(arg => arg.defaultValue ?? "");
 | 
				
			||||||
                if (argument.length > 0) {
 | 
					                if (argument.length > 0) {
 | 
				
			||||||
                    const realArgs = argument.split(",").map(str => str.trim());
 | 
					                    const realArgs = argument.split(",").map(str => str.trim());
 | 
				
			||||||
| 
						 | 
					@ -73,11 +102,13 @@ export class SubstitutedTranslation extends VariableUiElement {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let element;
 | 
					                let element;
 | 
				
			||||||
                element =  {special:{
 | 
					                element = {
 | 
				
			||||||
 | 
					                    special: {
 | 
				
			||||||
                        args: args,
 | 
					                        args: args,
 | 
				
			||||||
                        style: style,
 | 
					                        style: style,
 | 
				
			||||||
                        func: knownSpecial
 | 
					                        func: knownSpecial
 | 
				
			||||||
                }}
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                return [...partBefore, element, ...partAfter]
 | 
					                return [...partBefore, element, ...partAfter]
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -137,7 +137,8 @@
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "freeform": {
 | 
					      "freeform": {
 | 
				
			||||||
        "key": "capacity",
 | 
					        "key": "capacity",
 | 
				
			||||||
        "type": "nat"
 | 
					        "type": "nat",
 | 
				
			||||||
 | 
					        "inline": true
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -105,6 +105,10 @@ a {
 | 
				
			||||||
    width: min-content;
 | 
					    width: min-content;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.w-16-imp {
 | 
				
			||||||
 | 
					    width: 4rem !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.space-between{
 | 
					.space-between{
 | 
				
			||||||
    justify-content: space-between;
 | 
					    justify-content: space-between;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue