2020-10-09 20:10:21 +02:00
import { UIElement } from "./UIElement" ;
import OpeningHoursVisualization from "./OhVisualization" ;
import { UIEventSource } from "../Logic/UIEventSource" ;
2020-10-12 01:25:27 +02:00
import { VariableUiElement } from "./Base/VariableUIElement" ;
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" ;
2020-10-14 12:15:09 +02:00
import { ImageCarousel } from "./Image/ImageCarousel" ;
import Combine from "./Base/Combine" ;
import { FixedUiElement } from "./Base/FixedUiElement" ;
import Locale from "../UI/i18n/Locale" ;
import { ImageUploadFlow } from "./Image/ImageUploadFlow" ;
2020-11-06 01:58:26 +01:00
import { Translation } from "./i18n/Translation" ;
2020-11-21 16:44:48 +01:00
import State from "../State" ;
2020-11-22 03:50:09 +01:00
import ShareButton from "./ShareButton" ;
import Svg from "../Svg" ;
2020-12-08 23:44:34 +01:00
import ReviewElement from "./Reviews/ReviewElement" ;
import MangroveReviews from "../Logic/Web/MangroveReviews" ;
import Translations from "./i18n/Translations" ;
import ReviewForm from "./Reviews/ReviewForm" ;
2020-10-14 12:15:09 +02:00
export class SubstitutedTranslation extends UIElement {
private readonly tags : UIEventSource < any > ;
private readonly translation : Translation ;
2020-10-17 02:37:53 +02:00
private content : UIElement [ ] ;
2020-10-14 12:15:09 +02:00
constructor (
translation : Translation ,
tags : UIEventSource < any > ) {
super ( tags ) ;
this . translation = translation ;
this . tags = tags ;
const self = this ;
2020-12-27 21:44:49 +01:00
tags . addCallbackAndRun ( ( ) = > {
self . content = self . CreateContent ( ) ;
self . Update ( ) ;
} ) ;
Locale . language . addCallback ( ( ) = > {
2020-10-14 12:15:09 +02:00
self . content = self . CreateContent ( ) ;
self . Update ( ) ;
2020-10-17 02:37:53 +02:00
} ) ;
2020-11-02 12:38:04 +01:00
2020-10-14 12:15:09 +02:00
}
InnerRender ( ) : string {
2020-10-17 02:37:53 +02:00
return new Combine ( this . content ) . Render ( ) ;
2020-10-14 12:15:09 +02:00
}
2020-11-02 18:59:21 +01:00
2020-10-17 02:37:53 +02:00
private CreateContent ( ) : UIElement [ ] {
2020-10-14 12:15:09 +02:00
let txt = this . translation ? . txt ;
if ( txt === undefined ) {
2020-10-17 02:37:53 +02:00
return [ ]
2020-10-14 12:15:09 +02:00
}
const tags = this . tags . data ;
2020-11-17 02:22:48 +01:00
txt = SubstitutedTranslation . SubstituteKeys ( txt , tags ) ;
return this . EvaluateSpecialComponents ( txt ) ;
}
public static SubstituteKeys ( txt : string , tags : any ) {
2020-10-14 12:15:09 +02:00
for ( const key in tags ) {
// Poor mans replace all
txt = txt . split ( "{" + key + "}" ) . join ( tags [ key ] ) ;
}
2020-11-17 02:22:48 +01:00
return txt ;
2020-10-14 12:15:09 +02:00
}
2020-10-17 02:37:53 +02:00
private EvaluateSpecialComponents ( template : string ) : UIElement [ ] {
2020-10-14 12:15:09 +02:00
for ( const knownSpecial of SpecialVisualizations . specialVisualizations ) {
2020-11-27 13:39:00 +01:00
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
2020-10-14 12:15:09 +02:00
const matched = template . match ( ` (.*){ ${ knownSpecial . funcName } \\ ((.*?) \\ )}(.*) ` ) ;
if ( matched != null ) {
// We found a special component that should be brought to live
const partBefore = this . EvaluateSpecialComponents ( matched [ 1 ] ) ;
2020-10-17 02:37:53 +02:00
const argument = matched [ 2 ] . trim ( ) ;
2020-10-14 12:15:09 +02:00
const partAfter = this . EvaluateSpecialComponents ( matched [ 3 ] ) ;
try {
2020-10-17 02:37:53 +02:00
const args = knownSpecial . args . map ( arg = > arg . defaultValue ? ? "" ) ;
if ( argument . length > 0 ) {
const realArgs = argument . split ( "," ) . map ( str = > str . trim ( ) ) ;
for ( let i = 0 ; i < realArgs . length ; i ++ ) {
if ( args . length <= i ) {
args . push ( realArgs [ i ] ) ;
} else {
args [ i ] = realArgs [ i ] ;
}
}
}
2020-10-14 12:15:09 +02:00
const element = knownSpecial . constr ( this . tags , args ) ;
return [ . . . partBefore , element , . . . partAfter ]
} catch ( e ) {
console . error ( e ) ;
2020-11-02 18:59:21 +01:00
return [ . . . partBefore , new FixedUiElement ( ` Failed loading ${ knownSpecial . funcName } ( ${ matched [ 2 ] } ): ${ e } ` ) , . . . partAfter ]
2020-10-14 12:15:09 +02:00
}
}
}
// IF we end up here, no changes have to be made
return [ new FixedUiElement ( template ) ] ;
}
}
2020-10-09 20:10:21 +02:00
export default class SpecialVisualizations {
2020-10-11 22:37:55 +02:00
public static specialVisualizations : {
funcName : string ,
constr : ( ( tagSource : UIEventSource < any > , argument : string [ ] ) = > UIElement ) ,
docs : string ,
2020-10-17 02:37:53 +02:00
example? : string ,
2020-10-12 01:25:27 +02:00
args : { name : string , defaultValue? : string , doc : string } [ ]
2020-10-11 22:37:55 +02:00
} [ ] =
2020-10-09 20:10:21 +02:00
2020-11-17 16:29:51 +01:00
[ {
funcName : "all_tags" ,
docs : "Prints all key-value pairs of the object - used for debugging" ,
args : [ ] ,
constr : ( ( tags : UIEventSource < any > ) = > {
return new VariableUiElement ( tags . map ( tags = > {
const parts = [ ] ;
for ( const key in tags ) {
parts . push ( key + "=" + tags [ key ] ) ;
}
return parts . join ( "<br/>" )
} ) ) . SetStyle ( "border: 1px solid black; border-radius: 1em;padding:1em;display:block;" )
} )
} ,
2020-12-08 23:44:34 +01:00
2020-10-14 12:15:09 +02:00
{
funcName : "image_carousel" ,
docs : "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)" ,
args : [ {
2020-10-17 02:37:53 +02:00
name : "image key/prefix" ,
defaultValue : "image" ,
doc : "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... "
} ,
{
name : "smart search" ,
defaultValue : "true" ,
doc : "Also include images given via 'Wikidata', 'wikimedia_commons' and 'mapillary"
} ] ,
2020-10-14 12:15:09 +02:00
constr : ( tags , args ) = > {
2020-10-17 02:37:53 +02:00
return new ImageCarousel ( tags , args [ 0 ] , args [ 1 ] . toLowerCase ( ) === "true" ) ;
2020-10-11 22:37:55 +02:00
}
2020-10-14 12:15:09 +02:00
} ,
{
funcName : "image_upload" ,
docs : "Creates a button where a user can upload an image to IMGUR" ,
args : [ {
2020-10-17 02:37:53 +02:00
name : "image-key" ,
2020-10-14 12:15:09 +02:00
doc : "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)" ,
2020-10-17 02:37:53 +02:00
defaultValue : "image"
2020-10-14 12:15:09 +02:00
} ] ,
constr : ( tags , args ) = > {
2020-10-17 02:37:53 +02:00
return new ImageUploadFlow ( tags , args [ 0 ] )
2020-10-14 12:15:09 +02:00
}
} ,
2020-12-08 23:44:34 +01:00
{
funcName : "reviews" ,
docs : "Adds an overview of the mangrove-reviews of this object. IMPORTANT: the _name_ of the object should be defined for this to work!" ,
2020-12-31 21:09:22 +01:00
args : [ { name : "subject" , doc : "The identifier used for this value; by default the name of the reviewed object" } ] ,
2020-12-08 23:44:34 +01:00
constr : ( tags , args ) = > {
const tgs = tags . data ;
2020-12-31 21:09:22 +01:00
const subject = args [ 0 ] ? ? tgs . name ? ? "" ;
if ( subject === "" ) {
2020-12-08 23:44:34 +01:00
return Translations . t . reviews . name_required ;
}
2020-12-31 21:09:22 +01:00
const mangrove = MangroveReviews . Get ( Number ( tgs . _lon ) , Number ( tgs . _lat ) , subject ,
2020-12-08 23:44:34 +01:00
State . state . mangroveIdentity ,
State . state . osmConnection . _dryRun
) ;
2020-12-11 15:27:52 +01:00
const form = new ReviewForm ( ( r , whenDone ) = > mangrove . AddReview ( r , whenDone ) , State . state . osmConnection . userDetails ) ;
2020-12-08 23:44:34 +01:00
return new ReviewElement ( mangrove . GetSubjectUri ( ) , mangrove . GetReviews ( ) , form ) ;
}
} ,
2020-10-14 12:15:09 +02:00
{
funcName : "opening_hours_table" ,
docs : "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'." ,
args : [ {
name : "key" ,
defaultValue : "opening_hours" ,
2020-10-17 02:37:53 +02:00
doc : "The tagkey from which the table is constructed."
2020-10-14 12:15:09 +02:00
} ] ,
constr : ( tagSource : UIEventSource < any > , args ) = > {
let keyname = args [ 0 ] ;
if ( keyname === undefined || keyname === "" ) {
keyname = keyname ? ? "opening_hours"
}
return new OpeningHoursVisualization ( tagSource , keyname )
}
} ,
2020-10-11 22:37:55 +02:00
2020-10-12 01:25:27 +02:00
{
funcName : "live" ,
docs : "Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}" ,
2020-10-17 02:37:53 +02:00
example : "{live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt,hour)}" ,
2020-10-12 01:25:27 +02:00
args : [ {
name : "Url" , doc : "The URL to load"
} , {
name : "Shorthands" ,
doc : "A list of shorthands, of the format 'shorthandname:path.path.path'. Seperated by ;"
} , {
name : "path" , doc : "The path (or shorthand) that should be returned"
} ] ,
constr : ( tagSource : UIEventSource < any > , args ) = > {
const url = args [ 0 ] ;
const shorthands = args [ 1 ] ;
const neededValue = args [ 2 ] ;
const source = LiveQueryHandler . FetchLiveData ( url , shorthands . split ( ";" ) ) ;
return new VariableUiElement ( source . map ( data = > data [ neededValue ] ? ? "Loading..." ) ) ;
}
2020-10-19 12:08:42 +02:00
} ,
2020-11-21 16:44:48 +01:00
{
funcName : "share_link" ,
docs : "Creates a link that (attempts to) open the native 'share'-screen" ,
example : "{share_link()} to share the current page, {share_link(<some_url>)} to share the given url" ,
args : [
{
name : "url" ,
2020-11-23 02:55:18 +01:00
doc : "The url to share (defualt: current URL)" ,
2020-11-21 16:44:48 +01:00
}
] ,
constr : ( tagSource : UIEventSource < any > , args ) = > {
2020-11-24 12:52:01 +01:00
if ( window . navigator . share ) {
2020-11-22 03:50:09 +01:00
const title = State . state . layoutToUse . data . title . txt ;
let name = tagSource . data . name ;
2020-11-23 12:54:10 +01:00
if ( name ) {
2020-11-23 02:55:18 +01:00
name = ` ${ name } ( ${ title } ) `
2020-11-23 12:54:10 +01:00
} else {
2020-11-22 03:50:09 +01:00
name = title ;
}
2020-11-23 12:54:10 +01:00
let url = args [ 0 ] ? ? ""
if ( url === "" ) {
url = window . location . href
}
return new ShareButton ( Svg . share_svg ( ) , {
title : name ,
url : url ,
text : State.state.layoutToUse.data.shortDescription.txt
} )
2020-11-21 16:44:48 +01:00
} else {
2020-11-23 02:55:18 +01:00
return new FixedUiElement ( "" )
2020-11-21 16:44:48 +01:00
}
2020-11-17 16:29:51 +01:00
2020-11-21 16:44:48 +01:00
}
}
2020-10-12 01:25:27 +02:00
2020-10-11 22:37:55 +02:00
]
2020-10-17 02:37:53 +02:00
static HelpMessage : UIElement = SpecialVisualizations . GenHelpMessage ( ) ;
private static GenHelpMessage() {
const helpTexts =
SpecialVisualizations . specialVisualizations . map ( viz = > new Combine (
[
` <h3> ${ viz . funcName } </h3> ` ,
viz . docs ,
"<ol>" ,
. . . viz . args . map ( arg = > new Combine ( [
"<li>" ,
"<b>" + arg . name + "</b>: " ,
arg . doc ,
arg . defaultValue === undefined ? "" : ( " Default: <span class='literal-code'>" + arg . defaultValue + "</span>" ) ,
"</li>"
] ) ) ,
"</ol>" ,
"<b>Example usage: </b>" ,
new FixedUiElement (
viz . example ? ? "{" + viz . funcName + "(" + viz . args . map ( arg = > arg . defaultValue ) . join ( "," ) + ")}"
) . SetClass ( "literal-code" ) ,
]
) ) ;
return new Combine ( [
"In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's." ,
. . . helpTexts
]
) ;
}
2020-10-09 20:10:21 +02:00
}