forked from MapComplete/MapComplete
		
	Experimenting with Svelte: build a wrapper to convert 'old' components into Svelte, add a community index overview
This commit is contained in:
		
							parent
							
								
									dfc7ba2114
								
							
						
					
					
						commit
						02da80c311
					
				
					 11 changed files with 250 additions and 55 deletions
				
			
		
							
								
								
									
										17
									
								
								UI/Base/ToSvelte.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								UI/Base/ToSvelte.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| <script lang="ts"> | ||||
|   import BaseUIElement from "../BaseUIElement.js"; | ||||
|   import { onMount } from "svelte"; | ||||
| 
 | ||||
|   export let construct: BaseUIElement | (() => BaseUIElement); | ||||
|   let elem: HTMLElement; | ||||
|   onMount(() => { | ||||
|     let html: HTMLElement | ||||
|     if (typeof construct === "function") { | ||||
|       html = construct().ConstructElement(); | ||||
|     } else { | ||||
|       html = construct.ConstructElement(); | ||||
|     } | ||||
|     elem.appendChild(html) | ||||
|   }); | ||||
| </script> | ||||
| <span bind:this={elem}></span> | ||||
|  | @ -1,14 +1,44 @@ | |||
| <script lang="ts"> | ||||
|      import {BBox} from "../../Logic/BBox"; | ||||
|      import {Store} from "../../Logic/UIEventSource"; | ||||
|      import Loc from "../../Models/Loc"; | ||||
|   import { Store, UIEventSource } from "../../Logic/UIEventSource"; | ||||
|   import { Tiles } from "../../Models/TileRange"; | ||||
|   import { Utils } from "../../Utils"; | ||||
|   import global_community from "../../assets/community_index_global_resources.json"; | ||||
|   import ContactLink from "./ContactLink.svelte"; | ||||
|   import { GeoOperations } from "../../Logic/GeoOperations"; | ||||
|   import Translations from "../i18n/Translations"; | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte"; | ||||
|   import { ImmutableStore } from "../../Logic/UIEventSource.js"; | ||||
| 
 | ||||
|   export let locationControl: Store<{ lat: number, lon: number }>; | ||||
|   const tileToFetch: Store<string> = locationControl.mapD(l => { | ||||
|     const t = Tiles.embedded_tile(l.lat, l.lon, 6); | ||||
|     return `https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/community_index/tile_${t.z}_${t.x}_${t.y}.geojson`; | ||||
|   }); | ||||
|   const t = Translations.t.communityIndex | ||||
|   const resources = new UIEventSource<[]>([]); | ||||
| 
 | ||||
|   tileToFetch.addCallbackAndRun(async url => { | ||||
|       const data = await Utils.downloadJsonCached(url, 24 * 60 * 60); | ||||
|       if (data === undefined) { | ||||
|         return; | ||||
|       } | ||||
|       resources.setData(data.features); | ||||
|     } | ||||
|   ); | ||||
| 
 | ||||
|   const filteredResources = resources.map(features => features.filter(f => { | ||||
|       return GeoOperations.inside([locationControl.data.lon, locationControl.data.lat], f) | ||||
|     }), | ||||
|     [locationControl]); | ||||
| 
 | ||||
| 
 | ||||
|      export let bbox: Store<BBox> | ||||
|      export let currentLocation: Store<Loc> | ||||
|      bbox.mapD(bbox => { | ||||
|          if(currentLocation.data.zoom <= 6){ | ||||
|              // only return the global data | ||||
|          } | ||||
|          return bbox.expandToTileBounds(6); | ||||
|      }, [currentLocation]) | ||||
| </script> | ||||
| 
 | ||||
| 
 | ||||
| <div> | ||||
|   <ToSvelte construct={t.intro} /> | ||||
|   {#each $filteredResources as feature} | ||||
|     <ContactLink country={feature.properties} /> | ||||
|   {/each} | ||||
|   <ContactLink country={{resources:global_community, nameEn: "Global resources"}} /> | ||||
| </div> | ||||
|  |  | |||
|  | @ -1,34 +1,46 @@ | |||
| <script lang="ts"> | ||||
|     import {Store} from "../../Logic/UIEventSource"; | ||||
|     // A contact link indicates how a mapper can contact their local community   | ||||
|     // The _properties_ of a community feature | ||||
|     export let country: Store<{ resources; nameEn: string }> | ||||
|     let resources : Store<{ id: string, resolved: Record<string, string> }[]> = country.map(country => { | ||||
|         if(country === undefined){ | ||||
|             return [] | ||||
|         } | ||||
|         return Array.from(Object.values(country?.resources ?? {})) | ||||
|     }) | ||||
|   // A contact link indicates how a mapper can contact their local community   | ||||
|   // The _properties_ of a community feature | ||||
|   import Locale from "../i18n/Locale.js"; | ||||
|   import Translations from "../i18n/Translations"; | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte"; | ||||
|   import * as native from "../../assets/language_native.json"; | ||||
|   import { TypedTranslation } from "../i18n/Translation"; | ||||
| 
 | ||||
|   const availableTranslationTyped: TypedTranslation<{ native: string }> = Translations.t.communityIndex.available; | ||||
|   const availableTranslation = availableTranslationTyped.OnEveryLanguage((s, ln) => s.replace("{native}", native[ln] ?? ln)); | ||||
|   export let country: { resources; nameEn: string }; | ||||
|   let resources: { id: string, resolved: Record<string, string>, languageCodes: string[] }[] = [] | ||||
|   $: resources = Array.from(Object.values(country?.resources ?? {})); | ||||
|    | ||||
|   const language = Locale.language; | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <div> | ||||
|     {#if $country?.nameEn} | ||||
|         <h3>{$country?.nameEn}</h3> | ||||
|     {/if} | ||||
|     {#each $resources as resource} | ||||
|         <div class="flex link-underline items-center"> | ||||
|             <img | ||||
|                     class="w-8 h-8 m-2" | ||||
|                     src={"https://raw.githubusercontent.com/osmlab/osm-community-index/main/dist/img/" + | ||||
|   {#if country?.nameEn} | ||||
|     <h3>{country?.nameEn}</h3> | ||||
|   {/if} | ||||
|   {#each resources as resource} | ||||
|     <div class="flex link-underline items-center my-4"> | ||||
|       <img | ||||
|         class="w-8 h-8 m-2" | ||||
|         src={"https://raw.githubusercontent.com/osmlab/osm-community-index/main/dist/img/" + | ||||
|       resource.type + | ||||
|       ".svg"} | ||||
|             /> | ||||
|             <div class="flex flex-col"> | ||||
|                 <a href={resource.resolved.url} target="_blank" class="font-bold"> | ||||
|                     {resource.resolved.name ?? resource.resolved.url} | ||||
|                 </a> | ||||
|                 {resource.resolved?.description} | ||||
|             </div> | ||||
|         </div> | ||||
|     {/each} | ||||
|       /> | ||||
|       <div class="flex flex-col"> | ||||
|         <a href={resource.resolved.url} target="_blank" rel="noreferrer nofollow" class="font-bold"> | ||||
|           {resource.resolved.name ?? resource.resolved.url} | ||||
|         </a> | ||||
|         {resource.resolved?.description} | ||||
|         {#if (resource.languageCodes?.indexOf($language) >= 0)} | ||||
|           <span class="border-2 rounded-full border-lime-500 text-sm w-fit px-2"> | ||||
|           <ToSvelte construct={() => availableTranslation.Clone()} /> | ||||
|           </span> | ||||
|         {/if} | ||||
| 
 | ||||
|       </div> | ||||
|     </div> | ||||
|   {/each} | ||||
| </div> | ||||
|  |  | |||
|  | @ -34,6 +34,8 @@ import { GeoLocationState } from "../Logic/State/GeoLocationState" | |||
| import Hotkeys from "./Base/Hotkeys" | ||||
| import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers" | ||||
| import CopyrightPanel from "./BigComponents/CopyrightPanel" | ||||
| import SvelteUIElement from "./Base/SvelteUIElement" | ||||
| import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" | ||||
| 
 | ||||
| /** | ||||
|  * The default MapComplete GUI initializer | ||||
|  | @ -237,6 +239,20 @@ export default class DefaultGUI { | |||
|         const welcomeMessageMapControl = Toggle.If(state.featureSwitchWelcomeMessage, () => | ||||
|             self.InitWelcomeMessage() | ||||
|         ) | ||||
| 
 | ||||
|         const communityIndex = Toggle.If(state.featureSwitchCommunityIndex, () => { | ||||
|             const communityIndexControl = new MapControlButton(Svg.community_svg()) | ||||
|             const communityIndex = new ScrollableFullScreen( | ||||
|                 () => Translations.t.communityIndex.title, | ||||
|                 () => new SvelteUIElement(CommunityIndexView, { ...state }), | ||||
|                 "community_index" | ||||
|             ) | ||||
|             communityIndexControl.onClick(() => { | ||||
|                 communityIndex.Activate() | ||||
|             }) | ||||
|             return communityIndexControl | ||||
|         }) | ||||
| 
 | ||||
|         const testingBadge = Toggle.If(state.featureSwitchIsTesting, () => | ||||
|             new FixedUiElement("TESTING").SetClass("alert m-2 border-2 border-black") | ||||
|         ) | ||||
|  | @ -253,6 +269,7 @@ export default class DefaultGUI { | |||
|             welcomeMessageMapControl, | ||||
|             userInfoMapControl, | ||||
|             copyright, | ||||
|             communityIndex, | ||||
|             extraLink, | ||||
|             testingBadge, | ||||
|         ]) | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ import Locale from "./Locale" | |||
| import { Utils } from "../../Utils" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
| import LinkToWeblate from "../Base/LinkToWeblate" | ||||
| import { SvelteComponent } from "svelte" | ||||
| 
 | ||||
| export class Translation extends BaseUIElement { | ||||
|     public static forcedLanguage = undefined | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue