Merge develop branch

This commit is contained in:
pietervdvn 2021-05-12 19:13:21 +02:00
commit 8ca24f4a05
13 changed files with 475 additions and 161 deletions

View file

@ -41,7 +41,7 @@ export interface TagRenderingConfigJson {
type?: string,
/**
* If a value is added with the textfield, these extra tag is addded.
* Usefull to add a 'fixme=freeform textfield used - to be checked'
* Useful to add a 'fixme=freeform textfield used - to be checked'
**/
addExtraTags?: string[];
},

View file

@ -88,4 +88,4 @@ Given either a list of geojson features or a single layer name, gives the single
### memberships
Gives a list of {role: string, relation: Relation}-objects, containing all the relations that this feature is part of. For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')`
Gives a list of `{role: string, relation: Relation}`\-objects, containing all the relations that this feature is part of. For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')`

63
Docs/SpecialInputElements Normal file
View file

@ -0,0 +1,63 @@
# Available types for text fields
The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them
## string
A basic string
## text
A string, but allows input of longer strings more comfortably (a text area)
## date
A date
## wikidata
A wikidata identifier, e.g. Q42
## int
A number
## nat
A positive number or zero
## pnat
A strict positive number
## direction
A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)
## float
A decimal
## pfloat
A positive decimal (incl zero)
## email
An email adress
## url
A url
## phone
A phone number
## opening_hours
Has extra elements to easily input when a POI is opened
## color
Shows a color picker

View file

@ -1,7 +1,6 @@
import {GeoOperations} from "./GeoOperations";
import {UIElement} from "../UI/UIElement";
import Combine from "../UI/Base/Combine";
import State from "../State";
import {Relation} from "./Osm/ExtractRelations";
export class ExtraFunction {
@ -65,9 +64,8 @@ Some advanced functions are available on <b>feat</b> as well:
(featuresPerLayer, feature) => {
return (arg0, lat) => {
if (typeof arg0 === "number") {
const lon = arg0
// Feature._lon and ._lat is conveniently place by one of the other metatags
return GeoOperations.distanceBetween([lon, lat], [feature._lon, feature._lat]);
return GeoOperations.distanceBetween([arg0, lat], [feature._lon, feature._lat]);
} else {
// arg0 is probably a feature
return GeoOperations.distanceBetween(GeoOperations.centerpointCoordinates(arg0), [feature._lon, feature._lat])
@ -117,9 +115,11 @@ Some advanced functions are available on <b>feat</b> as well:
private static readonly Memberships = new ExtraFunction(
"memberships",
"Gives a list of {role: string, relation: Relation}-objects, containing all the relations that this feature is part of. \n\nFor example: <code>_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')</code>",
"Gives a list of <code>{role: string, relation: Relation}</code>-objects, containing all the relations that this feature is part of. " +
"\n\n" +
"For example: <code>_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')</code>",
[],
(params, feature) => {
(params, _) => {
return () => params.relations ?? [];
}
)

View file

@ -2,7 +2,7 @@ import { Utils } from "../Utils";
export default class Constants {
public static vNumber = "0.7.1-rc1";
public static vNumber = "0.7.2-dev";
// The user journey states thresholds when a new feature gets unlocked
public static userJourney = {

60
UI/Input/ColorPicker.ts Normal file
View file

@ -0,0 +1,60 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Utils} from "../../Utils";
export default class ColorPicker extends InputElement<string> {
private readonly value: UIEventSource<string>
constructor(
value?: UIEventSource<string>
) {
super();
this.value = value ?? new UIEventSource<string>(undefined);
const self = this;
this.value.addCallbackAndRun(v => {
if(v === undefined){
return;
}
self.SetValue(v);
});
}
InnerRender(): string {
return `<span id="${this.id}"><input type='color' id='color-${this.id}'></span>`;
}
private SetValue(color: string){
const field = document.getElementById("color-" + this.id);
if (field === undefined || field === null) {
return;
}
// @ts-ignore
field.value = color;
}
protected InnerUpdate() {
const field = document.getElementById("color-" + this.id);
if (field === undefined || field === null) {
return;
}
const self = this;
field.oninput = () => {
const hex = field["value"];
self.value.setData(hex);
}
}
GetValue(): UIEventSource<string> {
return this.value;
}
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
IsValid(t: string): boolean {
return false;
}
}

View file

@ -10,6 +10,7 @@ export class TextField extends InputElement<string> {
private readonly _placeholder: UIElement;
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private readonly _htmlType: string;
private readonly _inputMode : string;
private readonly _textAreaRows: number;
private readonly _isValid: (string,country) => boolean;
@ -20,6 +21,7 @@ export class TextField extends InputElement<string> {
value?: UIEventSource<string>,
textArea?: boolean,
htmlType?: string,
inputMode?: string,
label?: UIElement,
textAreaRows?: number,
isValid?: ((s: string, country?: () => string) => boolean)
@ -36,6 +38,7 @@ export class TextField extends InputElement<string> {
this._isValid = options.isValid ?? ((str, country) => true);
this._placeholder = Translations.W(options.placeholder ?? "");
this._inputMode = options.inputMode;
this.ListenTo(this._placeholder._source);
this.onClick(() => {
@ -72,11 +75,15 @@ export class TextField extends InputElement<string> {
if (this._label != undefined) {
label = this._label.Render();
}
let inputMode = ""
if(this._inputMode !== undefined){
inputMode = `inputmode="${this._inputMode}" `
}
return new Combine([
`<span id="${this.id}">`,
`<form onSubmit='return false' class='form-text-field'>`,
label,
`<input type='${this._htmlType}' placeholder='${placeholder}' id='txt-${this.id}'/>`,
`<input type='${this._htmlType}' ${inputMode} placeholder='${placeholder}' id='txt-${this.id}'/>`,
`</form>`,
`</span>`
]).Render();
@ -134,9 +141,6 @@ export class TextField extends InputElement<string> {
}
public SetCursorPosition(i: number) {
if(this._htmlType !== "text" && this._htmlType !== "area"){
return;
}
const field = document.getElementById('txt-' + this.id);
if(field === undefined || field === null){
return;

View file

@ -10,53 +10,36 @@ import CombinedInputElement from "./CombinedInputElement";
import SimpleDatePicker from "./SimpleDatePicker";
import OpeningHoursInput from "../OpeningHours/OpeningHoursInput";
import DirectionInput from "./DirectionInput";
import ColorPicker from "./ColorPicker";
import {Utils} from "../../Utils";
interface TextFieldDef {
name: string,
explanation: string,
isValid: ((s: string, country?:() => string) => boolean),
isValid: ((s: string, country?: () => string) => boolean),
reformat?: ((s: string, country?: () => string) => string),
inputHelper?: (value: UIEventSource<string>, options?: {
location: [number, number]
}) => InputElement<string>,
inputmode?: string
}
export default class ValidatedTextField {
private static tp(name: string,
explanation: string,
isValid?: ((s: string, country?: () => string) => boolean),
reformat?: ((s: string, country?: () => string) => string),
inputHelper?: (value: UIEventSource<string>, options?:{
location: [number, number]
}) => InputElement<string>): TextFieldDef {
if (isValid === undefined) {
isValid = () => true;
}
if (reformat === undefined) {
reformat = (str, _) => str;
}
return {
name: name,
explanation: explanation,
isValid: isValid,
reformat: reformat,
inputHelper: inputHelper
}
}
public static tpList: TextFieldDef[] = [
ValidatedTextField.tp(
"string",
"A basic string"),
ValidatedTextField.tp(
"text",
"A string, but allows input of longer strings more comfortably (a text area)"),
"A string, but allows input of longer strings more comfortably (a text area)",
undefined,
undefined,
undefined,
"text"),
ValidatedTextField.tp(
"date",
"A date",
@ -87,44 +70,63 @@ export default class ValidatedTextField {
(str) => {
str = "" + str;
return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str))
}),
},
undefined,
undefined,
"numeric"),
ValidatedTextField.tp(
"nat",
"A positive number or zero",
(str) => {
str = "" + str;
return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0
}),
},
undefined,
undefined,
"numeric"),
ValidatedTextField.tp(
"pnat",
"A strict positive number",
(str) => {
str = "" + str;
return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0
}),
},
undefined,
undefined,
"numeric"),
ValidatedTextField.tp(
"direction",
"A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)",
(str) => {
str = "" + str;
return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 && Number(str) <= 360
},str => str,
}, str => str,
(value) => {
return new DirectionInput(value);
}
return new DirectionInput(value);
},
"numeric"
),
ValidatedTextField.tp(
"float",
"A decimal",
(str) => !isNaN(Number(str))),
(str) => !isNaN(Number(str)),
undefined,
undefined,
"decimal"),
ValidatedTextField.tp(
"pfloat",
"A positive decimal (incl zero)",
(str) => !isNaN(Number(str)) && Number(str) >= 0),
(str) => !isNaN(Number(str)) && Number(str) >= 0,
undefined,
undefined,
"decimal"),
ValidatedTextField.tp(
"email",
"An email adress",
(str) => EmailValidator.validate(str)),
(str) => EmailValidator.validate(str),
undefined,
undefined,
"email"),
ValidatedTextField.tp(
"url",
"A url",
@ -135,18 +137,19 @@ export default class ValidatedTextField {
} catch (e) {
return false;
}
}, (str) => {
},
(str) => {
try {
const url = new URL(str);
const blacklistedTrackingParams = [
"fbclid",// Oh god, how I hate the fbclid. Let it burn, burn in hell!
"gclid",
"cmpid", "agid", "utm", "utm_source","utm_medium"]
"cmpid", "agid", "utm", "utm_source", "utm_medium"]
for (const dontLike of blacklistedTrackingParams) {
url.searchParams.delete(dontLike)
}
let cleaned = url.toString();
if(cleaned.endsWith("/") && !str.endsWith("/")){
if (cleaned.endsWith("/") && !str.endsWith("/")) {
// Do not add a trailing '/' if it wasn't typed originally
cleaned = cleaned.substr(0, cleaned.length - 1)
}
@ -155,7 +158,9 @@ export default class ValidatedTextField {
console.error(e)
return undefined;
}
}),
},
undefined,
"url"),
ValidatedTextField.tp(
"phone",
"A phone number",
@ -165,26 +170,35 @@ export default class ValidatedTextField {
}
return parsePhoneNumberFromString(str, (country())?.toUpperCase() as any)?.isValid() ?? false
},
(str, country: () => string) => parsePhoneNumberFromString(str, (country())?.toUpperCase() as any).formatInternational()
(str, country: () => string) => parsePhoneNumberFromString(str, (country())?.toUpperCase() as any).formatInternational(),
undefined,
"tel"
),
ValidatedTextField.tp(
"opening_hours",
"Has extra elements to easily input when a POI is opened",
(s, country) => true,
str => str,
() => true,
str => str,
(value) => {
return new OpeningHoursInput(value);
}
),
ValidatedTextField.tp(
"color",
"Shows a color picker",
() => true,
str => str,
(value) => {
return new ColorPicker(value.map(color => {
return Utils.ColourNameToHex(color ?? "");
}, [], str => Utils.HexToColourName(str)))
}
)
]
private static allTypesDict(){
const types = {};
for (const tp of ValidatedTextField.tpList) {
types[tp.name] = tp;
}
return types;
}
/**
* {string (typename) --> TextFieldDef}
*/
public static AllTypes = ValidatedTextField.allTypesDict();
public static TypeDropdown(): DropDown<string> {
const values: { value: string, shown: string }[] = [];
@ -195,15 +209,12 @@ export default class ValidatedTextField {
return new DropDown<string>("", values)
}
/**
* {string (typename) --> TextFieldDef}
*/
public static AllTypes = ValidatedTextField.allTypesDict();
public static InputForType(type: string, options?: {
placeholder?: string | UIElement,
value?: UIEventSource<string>,
textArea?: boolean,
htmlType?: string,
textArea?:boolean,
inputMode?:string,
textAreaRows?: number,
isValid?: ((s: string, country: () => string) => boolean),
country?: () => string,
@ -218,16 +229,16 @@ export default class ValidatedTextField {
if (options.isValid) {
const optValid = options.isValid;
isValid = (str, country) => {
if(str === undefined){
if (str === undefined) {
return false;
}
return isValidTp(str, country ?? options.country) && optValid(str, country ?? options.country);
}
}else{
} else {
isValid = isValidTp;
}
options.isValid = isValid;
options.inputMode = tp.inputmode;
let input: InputElement<string> = new TextField(options);
if (tp.reformat) {
input.GetValue().addCallbackAndRun(str => {
@ -240,7 +251,7 @@ export default class ValidatedTextField {
}
if (tp.inputHelper) {
input = new CombinedInputElement(input, tp.inputHelper(input.GetValue(),{
input = new CombinedInputElement(input, tp.inputHelper(input.GetValue(), {
location: options.location
}));
}
@ -270,7 +281,7 @@ export default class ValidatedTextField {
const textField = ValidatedTextField.InputForType(type);
return new InputElementMap(textField, (n0, n1) => n0 === n1, fromString, toString)
}
public static KeyInput(allowEmpty: boolean = false): InputElement<string> {
function fromString(str) {
@ -299,8 +310,6 @@ export default class ValidatedTextField {
return new InputElementMap(textfield, isSame, fromString, toString);
}
static Mapped<T>(fromString: (str) => T, toString: (T) => string, options?: {
placeholder?: string | UIElement,
type?: string,
@ -323,4 +332,46 @@ export default class ValidatedTextField {
);
}
public static HelpText(): string {
const explanations = ValidatedTextField.tpList.map(type => ["## " + type.name, "", type.explanation].join("\n")).join("\n\n")
return "# Available types for text fields\n\nThe listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them\n\n" + explanations
}
private static tp(name: string,
explanation: string,
isValid?: ((s: string, country?: () => string) => boolean),
reformat?: ((s: string, country?: () => string) => string),
inputHelper?: (value: UIEventSource<string>, options?: {
location: [number, number]
}) => InputElement<string>,
inputmode?: string): TextFieldDef {
if (isValid === undefined) {
isValid = () => true;
}
if (reformat === undefined) {
reformat = (str, _) => str;
}
return {
name: name,
explanation: explanation,
isValid: isValid,
reformat: reformat,
inputHelper: inputHelper,
inputmode: inputmode
}
}
private static allTypesDict() {
const types = {};
for (const tp of ValidatedTextField.tpList) {
types[tp.name] = tp;
}
return types;
}
}

View file

@ -1,6 +1,6 @@
import * as $ from "jquery"
import {type} from "os";
import * as colors from "./assets/colors.json"
export class Utils {
/**
@ -305,6 +305,69 @@ export class Utils {
element.click();
}
public static ColourNameToHex(color: string): string{
return colors[color.toLowerCase()] ?? color;
}
public static HexToColourName(hex : string): string{
hex = hex.toLowerCase()
if(!hex.startsWith("#")){
return hex;
}
const c = Utils.color(hex);
let smallestDiff = Number.MAX_VALUE;
let bestColor = undefined;
for (const color in colors) {
if(!colors.hasOwnProperty(color)){
continue;
}
const foundhex = colors[color];
if(typeof foundhex !== "string"){
continue
}
if(foundhex === hex){
return color
}
const diff = this.colorDiff(Utils.color(foundhex), c)
if(diff > 50){
continue;
}
if(diff < smallestDiff){
smallestDiff = diff;
bestColor = color;
}
}
return bestColor ?? hex;
}
private static colorDiff(c0 : {r: number, g: number, b: number}, c1: {r: number, g: number, b: number}){
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) +Math.abs(c0.b - c1.b) ;
}
private static color(hex: string) : {r: number, g: number, b: number}{
if(hex.startsWith == undefined){
console.trace("WUT?", hex)
throw "wut?"
}
if(!hex.startsWith("#")){
return undefined;
}
if(hex.length === 4){
return {
r : parseInt(hex.substr(1, 1), 16),
g : parseInt(hex.substr(2, 1), 16),
b : parseInt(hex.substr(3, 1), 16),
}
}
return {
r : parseInt(hex.substr(1, 2), 16),
g : parseInt(hex.substr(3, 2), 16),
b : parseInt(hex.substr(5, 2), 16),
}
}
}
export interface TileRange{

150
assets/colors.json Normal file
View file

@ -0,0 +1,150 @@
{
"aliceblue": "#f0f8ff",
"antiquewhite": "#faebd7",
"aqua": "#00ffff",
"aquamarine": "#7fffd4",
"azure": "#f0ffff",
"beige": "#f5f5dc",
"bisque": "#ffe4c4",
"black": "#000000",
"blanchedalmond": "#ffebcd",
"blue": "#0000ff",
"blueviolet": "#8a2be2",
"brown": "#a52a2a",
"burlywood": "#deb887",
"cadetblue": "#5f9ea0",
"chartreuse": "#7fff00",
"chocolate": "#d2691e",
"coral": "#ff7f50",
"cornflowerblue": "#6495ed",
"cornsilk": "#fff8dc",
"crimson": "#dc143c",
"cyan": "#00ffff",
"darkblue": "#00008b",
"darkcyan": "#008b8b",
"darkgoldenrod": "#b8860b",
"darkgray": "#a9a9a9",
"darkgrey": "#a9a9a9",
"darkgreen": "#006400",
"darkkhaki": "#bdb76b",
"darkmagenta": "#8b008b",
"darkolivegreen": "#556b2f",
"darkorange": "#ff8c00",
"darkorchid": "#9932cc",
"darkred": "#8b0000",
"darksalmon": "#e9967a",
"darkseagreen": "#8fbc8f",
"darkslateblue": "#483d8b",
"darkslategray": "#2f4f4f",
"darkslategrey": "#2f4f4f",
"darkturquoise": "#00ced1",
"darkviolet": "#9400d3",
"deeppink": "#ff1493",
"deepskyblue": "#00bfff",
"dimgray": "#696969",
"dimgrey": "#696969",
"dodgerblue": "#1e90ff",
"firebrick": "#b22222",
"floralwhite": "#fffaf0",
"forestgreen": "#228b22",
"fuchsia": "#ff00ff",
"gainsboro": "#dcdcdc",
"ghostwhite": "#f8f8ff",
"gold": "#ffd700",
"goldenrod": "#daa520",
"gray": "#808080",
"grey": "#808080",
"green": "#008000",
"greenyellow": "#adff2f",
"honeydew": "#f0fff0",
"hotpink": "#ff69b4",
"indianred": "#cd5c5c",
"indigo": "#4b0082",
"ivory": "#fffff0",
"khaki": "#f0e68c",
"lavender": "#e6e6fa",
"lavenderblush": "#fff0f5",
"lawngreen": "#7cfc00",
"lemonchiffon": "#fffacd",
"lightblue": "#add8e6",
"lightcoral": "#f08080",
"lightcyan": "#e0ffff",
"lightgoldenrodyellow": "#fafad2",
"lightgray": "#d3d3d3",
"lightgrey": "#d3d3d3",
"lightgreen": "#90ee90",
"lightpink": "#ffb6c1",
"lightsalmon": "#ffa07a",
"lightseagreen": "#20b2aa",
"lightskyblue": "#87cefa",
"lightslategray": "#778899",
"lightslategrey": "#778899",
"lightsteelblue": "#b0c4de",
"lightyellow": "#ffffe0",
"lime": "#00ff00",
"limegreen": "#32cd32",
"linen": "#faf0e6",
"magenta": "#ff00ff",
"maroon": "#800000",
"mediumaquamarine": "#66cdaa",
"mediumblue": "#0000cd",
"mediumorchid": "#ba55d3",
"mediumpurple": "#9370db",
"mediumseagreen": "#3cb371",
"mediumslateblue": "#7b68ee",
"mediumspringgreen": "#00fa9a",
"mediumturquoise": "#48d1cc",
"mediumvioletred": "#c71585",
"midnightblue": "#191970",
"mintcream": "#f5fffa",
"mistyrose": "#ffe4e1",
"moccasin": "#ffe4b5",
"navajowhite": "#ffdead",
"navy": "#000080",
"oldlace": "#fdf5e6",
"olive": "#808000",
"olivedrab": "#6b8e23",
"orange": "#ffa500",
"orangered": "#ff4500",
"orchid": "#da70d6",
"palegoldenrod": "#eee8aa",
"palegreen": "#98fb98",
"paleturquoise": "#afeeee",
"palevioletred": "#db7093",
"papayawhip": "#ffefd5",
"peachpuff": "#ffdab9",
"peru": "#cd853f",
"pink": "#ffc0cb",
"plum": "#dda0dd",
"powderblue": "#b0e0e6",
"purple": "#800080",
"rebeccapurple": "#663399",
"red": "#ff0000",
"rosybrown": "#bc8f8f",
"royalblue": "#4169e1",
"saddlebrown": "#8b4513",
"salmon": "#fa8072",
"sandybrown": "#f4a460",
"seagreen": "#2e8b57",
"seashell": "#fff5ee",
"sienna": "#a0522d",
"silver": "#c0c0c0",
"skyblue": "#87ceeb",
"slateblue": "#6a5acd",
"slategray": "#708090",
"slategrey": "#708090",
"snow": "#fffafa",
"springgreen": "#00ff7f",
"steelblue": "#4682b4",
"tan": "#d2b48c",
"teal": "#008080",
"thistle": "#d8bfd8",
"tomato": "#ff6347",
"turquoise": "#40e0d0",
"violet": "#ee82ee",
"wheat": "#f5deb3",
"white": "#ffffff",
"whitesmoke": "#f5f5f5",
"yellow": "#ffff00",
"yellowgreen": "#9acd32"
}

View file

@ -6,6 +6,7 @@ import {UIElement} from "../UI/UIElement";
import SimpleMetaTagger from "../Logic/SimpleMetaTagger";
import Combine from "../UI/Base/Combine";
import {ExtraFunction} from "../Logic/ExtraFunction";
import ValidatedTextField from "../UI/Input/ValidatedTextField";
@ -19,6 +20,6 @@ function WriteFile(filename, html: UIElement) : void {
WriteFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage)
WriteFile("./Docs/CalculatedTags.md", new Combine([SimpleMetaTagger.HelpText(), ExtraFunction.HelpText()]))
writeFileSync("./Docs/SpecialInputElements", ValidatedTextField.HelpText());
console.log("Generated docs")

View file

@ -18,85 +18,8 @@
<div id="maindiv">'maindiv' not attached</div>
<div id="extradiv">'extradiv' not attached</div>
<script>
const cacheElement = {
"freshness": "2021-04-21T09:50:28.000Z",
feature:
{
"type": "Feature",
"id": "way/912515518",
"properties": {
"id": "way/912515518",
"name": "Speelbos De Reukens",
"playground": "forest",
"leisure": "playground",
"operator": "The world!"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
4.378341436386108,
51.120101600003316
],
[
4.378175139427185,
51.11954264284114
],
[
4.3786633014678955,
51.119963544947566
],
[
4.379317760467529,
51.119525806677146
],
[
4.379017353057861,
51.11997027935011
],
[
4.379714727401733,
51.12028679516189
],
[
4.379006624221802,
51.120313732577664
],
[
4.378706216812134,
51.120744729093836
],
[
4.378384351730347,
51.120306998225196
],
[
4.377686977386475,
51.120306998225196
],
[
4.378341436386108,
51.120101600003316
]
]
]
}
}
}
const cache = [cacheElement]
localStorage.setItem("cached-featuresspeelplekken", JSON.stringify(cache))
</script>
<script src="./test.ts"></script>
<iframe src="http://127.0.0.1:1234/index.html" height="500px" width="600px"></iframe>
<iframe src="http://127.0.0.1:1234/index.html?layout=bookcases&fs-userbadge=false&fs-search=false&fs-welcome-message=false&fs-layers=false&fs-geolocation=false"
height="500px" width="600px"></iframe>
</body>
</html>

View file

@ -1,4 +1,3 @@
import ValidatedTextField from "./UI/Input/ValidatedTextField";
alert("Hello world!")
ValidatedTextField.InputForType("phone").AttachTo("maindiv")