2021-03-24 02:01:04 +01:00
import Combine from "../UI/Base/Combine"
2021-06-15 00:28:59 +02:00
import BaseUIElement from "../UI/BaseUIElement"
2023-04-15 03:15:17 +02:00
import { existsSync , mkdirSync , writeFile , writeFileSync } from "fs"
2021-11-08 02:36:01 +01:00
import { AllKnownLayouts } from "../Customizations/AllKnownLayouts"
2021-11-30 22:50:48 +01:00
import TableOfContents from "../UI/Base/TableOfContents"
2022-07-16 03:57:13 +02:00
import SimpleMetaTaggers from "../Logic/SimpleMetaTagger"
2022-01-14 19:34:00 +01:00
import SpecialVisualizations from "../UI/SpecialVisualizations"
import { ExtraFunctions } from "../Logic/ExtraFunctions"
2021-11-30 22:50:48 +01:00
import Title from "../UI/Base/Title"
import QueryParameterDocumentation from "../UI/QueryParameterDocumentation"
2022-01-29 02:45:59 +01:00
import ScriptUtils from "./ScriptUtils"
import List from "../UI/Base/List"
2022-02-14 23:42:32 +01:00
import SharedTagRenderings from "../Customizations/SharedTagRenderings"
2022-11-02 13:47:34 +01:00
import Translations from "../UI/i18n/Translations"
2023-02-08 01:14:21 +01:00
import themeOverview from "../assets/generated/theme_overview.json"
2022-12-24 03:44:21 +01:00
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
2023-02-08 01:14:21 +01:00
import bookcases from "../assets/generated/themes/bookcases.json"
import fakedom from "fake-dom"
2022-12-24 03:44:21 +01:00
import Hotkeys from "../UI/Base/Hotkeys"
2023-02-08 01:14:21 +01:00
import { QueryParameters } from "../Logic/Web/QueryParameters"
2023-03-02 05:20:53 +01:00
import Link from "../UI/Base/Link"
import Constants from "../Models/Constants"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import DependencyCalculator from "../Models/ThemeConfig/DependencyCalculator"
import { AllSharedLayers } from "../Customizations/AllSharedLayers"
2023-04-15 03:15:17 +02:00
import ThemeViewState from "../Models/ThemeViewState"
import Validators from "../UI/InputElement/Validators"
2022-04-06 16:12:01 +02:00
function WriteFile (
filename ,
html : BaseUIElement ,
autogenSource : string [ ] ,
options ? : {
noTableOfContents : boolean
2022-09-08 21:40:48 +02:00
}
) : void {
2022-02-15 00:11:11 +01:00
for ( const source of autogenSource ) {
if ( source . indexOf ( "*" ) > 0 ) {
continue
}
if ( ! existsSync ( source ) ) {
throw (
"While creating a documentation file and checking that the generation sources are properly linked: source file " +
source +
" was not found. Typo?"
2022-09-08 21:40:48 +02:00
)
2022-02-15 00:11:11 +01:00
}
}
2022-09-08 21:40:48 +02:00
2022-04-06 16:12:01 +02:00
if ( html instanceof Combine && ! options ? . noTableOfContents ) {
2021-11-30 22:50:48 +01:00
const toc = new TableOfContents ( html )
const els = html . getElements ( )
2022-01-14 19:34:00 +01:00
html = new Combine ( [ els . shift ( ) , toc , . . . els ] ) . SetClass ( "flex flex-col" )
2021-11-30 22:50:48 +01:00
}
2022-01-14 19:34:00 +01:00
let md = new Combine ( [
Translations . W ( html ) ,
2022-02-15 00:00:30 +01:00
"\n\nThis document is autogenerated from " +
autogenSource
. map (
( file ) = >
` [ ${ file } ](https://github.com/pietervdvn/MapComplete/blob/develop/ ${ file } ) `
2022-09-08 21:40:48 +02:00
)
2022-02-15 00:00:30 +01:00
. join ( ", " ) ,
2022-01-14 19:34:00 +01:00
] ) . AsMarkdown ( )
md . replace ( /\n\n\n+/g , "\n\n" )
2022-01-26 21:40:38 +01:00
2023-02-12 22:52:52 +01:00
if ( ! md . endsWith ( "\n" ) ) {
md += "\n"
}
const warnAutomated =
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)"
writeFileSync ( filename , warnAutomated + md )
2021-06-15 00:28:59 +02:00
}
2021-03-13 19:07:11 +01:00
2023-03-02 05:20:53 +01:00
function GenerateDocumentationForTheme ( theme : LayoutConfig ) : BaseUIElement {
return new Combine ( [
new Title (
new Combine ( [
theme . title ,
"(" ,
new Link ( theme . id , "https://mapcomplete.osm.be/" + theme . id ) ,
")" ,
] ) ,
2
) ,
theme . description ,
"This theme contains the following layers:" ,
new List (
theme . layers
. filter ( ( l ) = > ! l . id . startsWith ( "note_import_" ) )
. map ( ( l ) = > new Link ( l . id , "../Layers/" + l . id + ".md" ) )
) ,
"Available languages:" ,
new List ( theme . language . filter ( ( ln ) = > ln !== "_context" ) ) ,
] ) . SetClass ( "flex flex-col" )
}
/ * *
* Generates the documentation for the layers overview page
* @constructor
* /
function GenLayerOverviewText ( ) : BaseUIElement {
for ( const id of Constants . priviliged_layers ) {
if ( ! AllSharedLayers . sharedLayers . has ( id ) ) {
throw "Priviliged layer definition not found: " + id
}
}
const allLayers : LayerConfig [ ] = Array . from ( AllSharedLayers . sharedLayers . values ( ) ) . filter (
2023-03-25 02:48:24 +01:00
( layer ) = > layer . source === null
2023-03-02 05:20:53 +01:00
)
const builtinLayerIds : Set < string > = new Set < string > ( )
allLayers . forEach ( ( l ) = > builtinLayerIds . add ( l . id ) )
const themesPerLayer = new Map < string , string [ ] > ( )
for ( const layout of Array . from ( AllKnownLayouts . allKnownLayouts . values ( ) ) ) {
for ( const layer of layout . layers ) {
if ( ! builtinLayerIds . has ( layer . id ) ) {
continue
}
if ( ! themesPerLayer . has ( layer . id ) ) {
themesPerLayer . set ( layer . id , [ ] )
}
themesPerLayer . get ( layer . id ) . push ( layout . id )
}
}
// Determine the cross-dependencies
const layerIsNeededBy : Map < string , string [ ] > = new Map < string , string [ ] > ( )
for ( const layer of allLayers ) {
for ( const dep of DependencyCalculator . getLayerDependencies ( layer ) ) {
const dependency = dep . neededLayer
if ( ! layerIsNeededBy . has ( dependency ) ) {
layerIsNeededBy . set ( dependency , [ ] )
}
layerIsNeededBy . get ( dependency ) . push ( layer . id )
}
}
return new Combine ( [
new Title ( "Special and other useful layers" , 1 ) ,
"MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here." ,
new Title ( "Priviliged layers" , 1 ) ,
new List ( Constants . priviliged_layers . map ( ( id ) = > "[" + id + "](#" + id + ")" ) ) ,
. . . Constants . priviliged_layers
. map ( ( id ) = > AllSharedLayers . sharedLayers . get ( id ) )
. map ( ( l ) = >
l . GenerateDocumentation (
themesPerLayer . get ( l . id ) ,
layerIsNeededBy ,
DependencyCalculator . getLayerDependencies ( l ) ,
2023-04-15 03:15:17 +02:00
Constants . added_by_default . indexOf ( < any > l . id ) >= 0 ,
Constants . no_include . indexOf ( < any > l . id ) < 0
2023-03-02 05:20:53 +01:00
)
) ,
new Title ( "Normal layers" , 1 ) ,
"The following layers are included in MapComplete:" ,
new List (
Array . from ( AllSharedLayers . sharedLayers . keys ( ) ) . map (
( id ) = > new Link ( id , "./Layers/" + id + ".md" )
)
) ,
] )
}
/ * *
* Generates documentation for the layers .
* Inline layers are included ( if the theme is public )
* @param callback
* @constructor
* /
function GenOverviewsForSingleLayer (
callback : ( layer : LayerConfig , element : BaseUIElement , inlineSource : string ) = > void
) : void {
const allLayers : LayerConfig [ ] = Array . from ( AllSharedLayers . sharedLayers . values ( ) ) . filter (
2023-03-25 02:48:24 +01:00
( layer ) = > layer . source !== null
2023-03-02 05:20:53 +01:00
)
const builtinLayerIds : Set < string > = new Set < string > ( )
allLayers . forEach ( ( l ) = > builtinLayerIds . add ( l . id ) )
const inlineLayers = new Map < string , string > ( )
for ( const layout of Array . from ( AllKnownLayouts . allKnownLayouts . values ( ) ) ) {
if ( layout . hideFromOverview ) {
continue
}
for ( const layer of layout . layers ) {
2023-03-25 02:48:24 +01:00
if ( layer . source === null ) {
2023-03-02 05:20:53 +01:00
continue
}
if ( builtinLayerIds . has ( layer . id ) ) {
continue
}
if ( layer . source . geojsonSource !== undefined ) {
// Not an OSM-source
continue
}
allLayers . push ( layer )
builtinLayerIds . add ( layer . id )
inlineLayers . set ( layer . id , layout . id )
}
}
const themesPerLayer = new Map < string , string [ ] > ( )
for ( const layout of Array . from ( AllKnownLayouts . allKnownLayouts . values ( ) ) ) {
if ( layout . hideFromOverview ) {
continue
}
for ( const layer of layout . layers ) {
if ( ! builtinLayerIds . has ( layer . id ) ) {
// This is an inline layer
continue
}
if ( ! themesPerLayer . has ( layer . id ) ) {
themesPerLayer . set ( layer . id , [ ] )
}
themesPerLayer . get ( layer . id ) . push ( layout . id )
}
}
// Determine the cross-dependencies
const layerIsNeededBy : Map < string , string [ ] > = new Map < string , string [ ] > ( )
for ( const layer of allLayers ) {
for ( const dep of DependencyCalculator . getLayerDependencies ( layer ) ) {
const dependency = dep . neededLayer
if ( ! layerIsNeededBy . has ( dependency ) ) {
layerIsNeededBy . set ( dependency , [ ] )
}
layerIsNeededBy . get ( dependency ) . push ( layer . id )
}
}
allLayers . forEach ( ( layer ) = > {
const element = layer . GenerateDocumentation (
themesPerLayer . get ( layer . id ) ,
layerIsNeededBy ,
DependencyCalculator . getLayerDependencies ( layer )
)
callback ( layer , element , inlineLayers . get ( layer . id ) )
} )
}
2022-10-27 22:16:28 +02:00
/ * *
* The wikitable is updated as some tools show an overview of apps based on the wiki .
* /
2022-11-02 13:47:34 +01:00
function generateWikipage() {
function generateWikiEntry ( layout : {
hideFromOverview : boolean
id : string
shortDescription : any
} ) {
2022-10-27 22:03:41 +02:00
if ( layout . hideFromOverview ) {
2022-11-02 13:47:34 +01:00
return ""
2022-10-27 22:03:41 +02:00
}
2023-01-11 01:32:37 +01:00
const languagesInDescr = Array . from ( Object . keys ( layout . shortDescription ) ) . filter (
( k ) = > k !== "_context"
)
2022-11-02 13:47:34 +01:00
const languages = languagesInDescr . map ( ( ln ) = > ` {{#language: ${ ln } |en}} ` ) . join ( ", " )
let auth = "Yes"
2022-10-27 22:03:41 +02:00
return ` {{service_item
| name = [ https : //mapcomplete.osm.be/${layout.id} ${layout.id}]
| region = Worldwide
| lang = $ { languages }
| descr = A MapComplete theme : $ { Translations . T ( layout . shortDescription )
. textFor ( "en" )
. replace ( "<a href='" , "[[" )
2022-11-02 13:47:34 +01:00
. replace ( /'>.*<\/a>/ , "]]" ) }
2022-10-27 22:03:41 +02:00
| material = { { yes | [ https : //mapcomplete.osm.be/ ${auth}]}}
| image = MapComplete_Screenshot . png
| genre = POI , editor , $ { layout . id }
} } `
}
2022-11-02 13:47:34 +01:00
let wikiPage =
'{|class="wikitable sortable"\n' +
2022-10-27 22:03:41 +02:00
"! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" +
2022-11-02 13:47:34 +01:00
"|-"
2022-10-27 22:03:41 +02:00
2023-02-08 01:14:21 +01:00
for ( const layout of themeOverview ) {
2022-10-27 22:03:41 +02:00
if ( layout . hideFromOverview ) {
2022-11-02 13:47:34 +01:00
continue
2022-10-27 22:03:41 +02:00
}
2022-11-02 13:47:34 +01:00
wikiPage += "\n" + generateWikiEntry ( layout )
2022-10-27 22:03:41 +02:00
}
wikiPage += "\n|}"
writeFile ( "Docs/wikiIndex.txt" , wikiPage , ( err ) = > {
if ( err !== null ) {
2022-11-02 13:47:34 +01:00
console . log ( "Could not save wikiindex" , err )
2022-10-27 22:03:41 +02:00
}
2022-11-02 13:47:34 +01:00
} )
2022-10-27 22:03:41 +02:00
}
2022-01-14 19:34:00 +01:00
console . log ( "Starting documentation generation..." )
2023-01-11 01:32:37 +01:00
ScriptUtils . fixUtils ( )
2022-10-27 22:16:28 +02:00
generateWikipage ( )
2023-03-02 05:20:53 +01:00
GenOverviewsForSingleLayer ( ( layer , element , inlineSource ) = > {
2023-04-15 03:15:17 +02:00
ScriptUtils . erasableLog ( "Exporting layer documentation for" , layer . id )
2022-04-06 16:12:01 +02:00
if ( ! existsSync ( "./Docs/Layers" ) ) {
mkdirSync ( "./Docs/Layers" )
}
let source : string = ` assets/layers/ ${ layer . id } / ${ layer . id } .json `
if ( inlineSource !== undefined ) {
source = ` assets/themes/ ${ inlineSource } / ${ inlineSource } .json `
}
WriteFile ( "./Docs/Layers/" + layer . id + ".md" , element , [ source ] , { noTableOfContents : true } )
2022-01-14 19:34:00 +01:00
} )
2022-11-30 21:38:50 +01:00
2022-12-16 13:40:25 +01:00
Array . from ( AllKnownLayouts . allKnownLayouts . values ( ) ) . map ( ( theme ) = > {
2023-04-15 03:15:17 +02:00
if ( ! existsSync ( "./Docs/Themes" ) ) {
mkdirSync ( "./Docs/Themes" )
}
2023-03-02 05:20:53 +01:00
const docs = GenerateDocumentationForTheme ( theme )
2022-12-16 13:40:25 +01:00
WriteFile (
"./Docs/Themes/" + theme . id + ".md" ,
docs ,
[ ` assets/themes/ ${ theme . id } / ${ theme . id } .json ` ] ,
{ noTableOfContents : true }
)
2022-11-30 21:38:50 +01:00
} )
2022-02-15 00:11:11 +01:00
WriteFile ( "./Docs/SpecialRenderings.md" , SpecialVisualizations . HelpMessage ( ) , [
"UI/SpecialVisualizations.ts" ,
] )
2022-01-14 19:34:00 +01:00
WriteFile (
"./Docs/CalculatedTags.md" ,
new Combine ( [
new Title ( "Metatags" , 1 ) ,
SimpleMetaTaggers . HelpText ( ) ,
ExtraFunctions . HelpText ( ) ,
] ) . SetClass ( "flex-col" ) ,
2022-02-15 00:11:11 +01:00
[ "Logic/SimpleMetaTagger.ts" , "Logic/ExtraFunctions.ts" ]
)
2023-04-15 03:15:17 +02:00
WriteFile ( "./Docs/SpecialInputElements.md" , Validators . HelpText ( ) , [
"UI/InputElement/Validators.ts" ,
2022-02-15 00:11:11 +01:00
] )
2023-03-02 05:20:53 +01:00
WriteFile ( "./Docs/BuiltinLayers.md" , GenLayerOverviewText ( ) , [ "Customizations/AllKnownLayouts.ts" ] )
2022-02-15 00:11:11 +01:00
WriteFile ( "./Docs/BuiltinQuestions.md" , SharedTagRenderings . HelpText ( ) , [
"Customizations/SharedTagRenderings.ts" ,
"assets/tagRenderings/questions.json" ,
2022-09-08 21:40:48 +02:00
] )
2022-01-14 19:34:00 +01:00
2022-01-29 02:45:59 +01:00
{
2022-02-04 01:05:35 +01:00
// Generate the builtinIndex which shows interlayer dependencies
2022-01-29 02:45:59 +01:00
var layers = ScriptUtils . getLayerFiles ( ) . map ( ( f ) = > f . parsed )
var builtinsPerLayer = new Map < string , string [ ] > ( )
var layersUsingBuiltin = new Map < string /* Builtin */ , string [ ] > ( )
for ( const layer of layers ) {
if ( layer . tagRenderings === undefined ) {
continue
}
const usedBuiltins : string [ ] = [ ]
for ( const tagRendering of layer . tagRenderings ) {
if ( typeof tagRendering === "string" ) {
usedBuiltins . push ( tagRendering )
continue
}
if ( tagRendering [ "builtin" ] !== undefined ) {
const builtins = tagRendering [ "builtin" ]
if ( typeof builtins === "string" ) {
usedBuiltins . push ( builtins )
} else {
usedBuiltins . push ( . . . builtins )
}
}
}
for ( const usedBuiltin of usedBuiltins ) {
var using = layersUsingBuiltin . get ( usedBuiltin )
if ( using === undefined ) {
layersUsingBuiltin . set ( usedBuiltin , [ layer . id ] )
} else {
using . push ( layer . id )
}
}
2022-09-08 21:40:48 +02:00
2022-01-29 02:45:59 +01:00
builtinsPerLayer . set ( layer . id , usedBuiltins )
}
2022-09-08 21:40:48 +02:00
2022-01-29 02:45:59 +01:00
const docs = new Combine ( [
new Title ( "Index of builtin TagRendering" , 1 ) ,
new Title ( "Existing builtin tagrenderings" , 2 ) ,
. . . Array . from ( layersUsingBuiltin . entries ( ) ) . map ( ( [ builtin , usedByLayers ] ) = >
new Combine ( [ new Title ( builtin ) , new List ( usedByLayers ) ] ) . SetClass ( "flex flex-col" )
) ,
] ) . SetClass ( "flex flex-col" )
WriteFile ( "./Docs/BuiltinIndex.md" , docs , [ "assets/layers/*.json" ] )
}
2022-01-14 19:34:00 +01:00
2022-02-15 00:11:11 +01:00
WriteFile ( "./Docs/URL_Parameters.md" , QueryParameterDocumentation . GenerateQueryParameterDocs ( ) , [
"Logic/Web/QueryParameters.ts" ,
"UI/QueryParameterDocumentation.ts" ,
] )
2022-12-24 03:44:21 +01:00
if ( fakedom === undefined || window === undefined ) {
throw "FakeDom not initialized"
}
2023-02-08 01:14:21 +01:00
QueryParameters . GetQueryParameter (
"mode" ,
"map" ,
"The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'"
)
2023-04-15 03:15:17 +02:00
{
new ThemeViewState ( new LayoutConfig ( < any > bookcases ) )
WriteFile ( "./Docs/Hotkeys.md" , Hotkeys . generateDocumentation ( ) , [ ] )
}
2021-03-22 01:06:24 +01:00
console . log ( "Generated docs" )