forked from MapComplete/MapComplete
		
	Move MoreScreen to svelte
This commit is contained in:
		
							parent
							
								
									227551c7cb
								
							
						
					
					
						commit
						1bf1700bab
					
				
					 9 changed files with 419 additions and 205 deletions
				
			
		|  | @ -5,14 +5,14 @@ | |||
|   import Img from "./Img" | ||||
|   import Translations from "../i18n/Translations" | ||||
| 
 | ||||
|   export let imageUrl: string | BaseUIElement | ||||
|   export let message: string | BaseUIElement | ||||
|   export let imageUrl: string | BaseUIElement = undefined | ||||
|   export let message: string | BaseUIElement = undefined | ||||
|   export let options: { | ||||
|     url?: string | Store<string> | ||||
|     newTab?: boolean | ||||
|     imgSize?: string | ||||
|     extraClasses?: string | ||||
|   } | ||||
|   } = {} | ||||
| 
 | ||||
|   let href = typeof options?.url == "string" ? options.url : "" | ||||
| 
 | ||||
|  | @ -27,7 +27,7 @@ | |||
|     } | ||||
| 
 | ||||
|     // Image | ||||
|     if (imgElem != undefined) { | ||||
|     if (imgElem && imageUrl) { | ||||
|       let img: BaseUIElement | ||||
| 
 | ||||
|       const imgClasses = "block justify-center flex-none mr-4 " + (options?.imgSize ?? "h-11 w-11") | ||||
|  | @ -43,7 +43,7 @@ | |||
|     } | ||||
| 
 | ||||
|     // Message | ||||
|     if (msgElem != undefined) { | ||||
|     if (msgElem && message) { | ||||
|       let msg = Translations.W(message)?.SetClass("block text-ellipsis no-images flex-shrink") | ||||
|       msgElem.replaceWith(msg.ConstructElement()) | ||||
|     } | ||||
|  | @ -74,5 +74,9 @@ | |||
|     :global(img) { | ||||
|       @apply block justify-center flex-none mr-4 h-11 w-11; | ||||
|     } | ||||
| 
 | ||||
|     :global(span) { | ||||
|       @apply block text-ellipsis flex-shrink; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
							
								
								
									
										38
									
								
								UI/BigComponents/CustomGeneratorButton.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								UI/BigComponents/CustomGeneratorButton.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| <script lang="ts"> | ||||
|   import UserDetails from "../../Logic/Osm/OsmConnection" | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import Constants from "../../Models/Constants" | ||||
|   import Svg from "../../Svg" | ||||
|   import SubtleButton from "../Base/SubtleButton.svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
| 
 | ||||
|   export let userDetails: UIEventSource<UserDetails> | ||||
|   const t = Translations.t.general.morescreen | ||||
| 
 | ||||
|   console.log($userDetails.csCount < 50) | ||||
| </script> | ||||
| 
 | ||||
| <div> | ||||
|   {#if $userDetails.csCount < Constants.userJourney.themeGeneratorReadOnlyUnlock} | ||||
|     <SubtleButton | ||||
|       options={{ | ||||
|         url: "https://github.com/pietervdvn/MapComplete/issues", | ||||
|         newTab: true, | ||||
|       }} | ||||
|     > | ||||
|       <span slot="message">{t.requestATheme.toString()}</span> | ||||
|     </SubtleButton> | ||||
|   {:else} | ||||
|     <SubtleButton | ||||
|       options={{ | ||||
|         url: "https://pietervdvn.github.io/mc/legacy/070/customGenerator.html", | ||||
|       }} | ||||
|     > | ||||
|       <span slot="image"> | ||||
|         <ToSvelte construct={Svg.pencil_ui()} /> | ||||
|       </span> | ||||
|       <span slot="message">{t.createYourOwnTheme.toString()}</span> | ||||
|     </SubtleButton> | ||||
|   {/if} | ||||
| </div> | ||||
							
								
								
									
										37
									
								
								UI/BigComponents/HiddenThemeList.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								UI/BigComponents/HiddenThemeList.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| <script lang="ts"> | ||||
|   import { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import type Loc from "../../Models/Loc" | ||||
|   import * as themeOverview from "../../assets/generated/theme_overview.json" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import ThemesList, { type Theme } from "./ThemesList.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
| 
 | ||||
|   export let search: UIEventSource<string> | ||||
|   export let state: { osmConnection: OsmConnection; locationControl?: UIEventSource<Loc> } | ||||
|   export let onMainScreen: boolean = true | ||||
| 
 | ||||
|   const prefix = "mapcomplete-hidden-theme-" | ||||
|   const hiddenThemes: Theme[] = themeOverview["default"].filter((layout) => layout.hideFromOverview) | ||||
|   const userPreferences = state.osmConnection.preferencesHandler.preferences | ||||
|   const t = Translations.t.general.morescreen | ||||
| 
 | ||||
|   $: knownThemesId = Utils.NoNull( | ||||
|     Object.keys($userPreferences) | ||||
|       .filter((key) => key.startsWith(prefix)) | ||||
|       .map((key) => key.substring(prefix.length, key.length - "-enabled".length)) | ||||
|   ) | ||||
|   $: knownThemes = hiddenThemes.filter((theme) => knownThemesId.includes(theme.id)) | ||||
| </script> | ||||
| 
 | ||||
| <ThemesList {search} {state} {onMainScreen} themes={knownThemes} isCustom={true} hideThemes={false}> | ||||
|   <svelte:fragment slot="title"> | ||||
|     <h3>{t.previouslyHiddenTitle.toString()}</h3> | ||||
|     <p> | ||||
|       {t.hiddenExplanation.Subs({ | ||||
|         hidden_discovered: knownThemes.length.toString(), | ||||
|         total_hidden: hiddenThemes.length.toString(), | ||||
|       })} | ||||
|     </p> | ||||
|   </svelte:fragment> | ||||
| </ThemesList> | ||||
|  | @ -1,24 +1,23 @@ | |||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import Svg from "../../Svg" | ||||
| import Combine from "../Base/Combine" | ||||
| import { SubtleButton } from "../Base/SubtleButton" | ||||
| import Translations from "../i18n/Translations" | ||||
| import personal from "../../assets/themes/personal/personal.json" | ||||
| import Constants from "../../Models/Constants" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||
| import { ImmutableStore, Store, Stores, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import Loc from "../../Models/Loc" | ||||
| import { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
| import UserRelatedState from "../../Logic/State/UserRelatedState" | ||||
| import Toggle from "../Input/Toggle" | ||||
| import { Utils } from "../../Utils" | ||||
| import Title from "../Base/Title" | ||||
| import themeOverview from "../../assets/generated/theme_overview.json" | ||||
| import { Translation } from "../i18n/Translation" | ||||
| import { TextField } from "../Input/TextField" | ||||
| import FilteredCombine from "../Base/FilteredCombine" | ||||
| import Locale from "../i18n/Locale" | ||||
| import SvelteUIElement from "../Base/SvelteUIElement" | ||||
| import ThemesList from "./ThemesList.svelte" | ||||
| import HiddenThemeList from "./HiddenThemeList.svelte" | ||||
| import UnofficialThemeList from "./UnofficialThemeList.svelte" | ||||
| 
 | ||||
| export default class MoreScreen extends Combine { | ||||
|     private static readonly officialThemes: { | ||||
|  | @ -40,13 +39,6 @@ export default class MoreScreen extends Combine { | |||
|         onMainScreen: boolean = false | ||||
|     ) { | ||||
|         const tr = Translations.t.general.morescreen | ||||
|         let themeButtonStyle = "" | ||||
|         let themeListStyle = "" | ||||
|         if (onMainScreen) { | ||||
|             themeButtonStyle = "h-32 min-h-32 max-h-32 text-ellipsis overflow-hidden" | ||||
|             themeListStyle = | ||||
|                 "md:grid md:grid-flow-row md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-g4 gap-4" | ||||
|         } | ||||
| 
 | ||||
|         const search = new TextField({ | ||||
|             placeholder: tr.searchForATheme, | ||||
|  | @ -107,38 +99,26 @@ export default class MoreScreen extends Combine { | |||
| 
 | ||||
|         super([ | ||||
|             new Combine([searchBar]).SetClass("flex justify-center"), | ||||
|             MoreScreen.createOfficialThemesList( | ||||
|             new SvelteUIElement(ThemesList, { | ||||
|                 state, | ||||
|                 themeButtonStyle, | ||||
|                 themeListStyle, | ||||
|                 search.GetValue() | ||||
|             ), | ||||
|             MoreScreen.createPreviouslyVistedHiddenList( | ||||
|                 onMainScreen, | ||||
|                 search: search.GetValue(), | ||||
|                 themes: MoreScreen.officialThemes, | ||||
|             }), | ||||
|             new SvelteUIElement(HiddenThemeList, { | ||||
|                 state, | ||||
|                 themeButtonStyle, | ||||
|                 themeListStyle, | ||||
|                 search.GetValue() | ||||
|             ), | ||||
|             MoreScreen.createUnofficialThemeList( | ||||
|                 themeButtonStyle, | ||||
|                 onMainScreen, | ||||
|                 search: search.GetValue(), | ||||
|             }), | ||||
|             new SvelteUIElement(UnofficialThemeList, { | ||||
|                 state, | ||||
|                 themeListStyle, | ||||
|                 search.GetValue() | ||||
|             ), | ||||
|                 onMainScreen, | ||||
|                 search: search.GetValue(), | ||||
|             }), | ||||
|             tr.streetcomplete.Clone().SetClass("block text-base mx-10 my-3 mb-10"), | ||||
|         ]) | ||||
|     } | ||||
| 
 | ||||
|     private static NothingFound(search: UIEventSource<string>): BaseUIElement { | ||||
|         const t = Translations.t.general.morescreen | ||||
|         return new Combine([ | ||||
|             new Title(t.noMatchingThemes, 5).SetClass("w-max font-bold"), | ||||
|             new SubtleButton(Svg.search_disable_ui(), t.noSearch, { imgSize: "h-6" }) | ||||
|                 .SetClass("h-12 w-max") | ||||
|                 .onClick(() => search.setData("")), | ||||
|         ]).SetClass("flex flex-col items-center w-full") | ||||
|     } | ||||
| 
 | ||||
|     private static createUrlFor( | ||||
|         layout: { id: string; definition?: string }, | ||||
|         isCustom: boolean, | ||||
|  | @ -239,102 +219,6 @@ export default class MoreScreen extends Combine { | |||
|             new SubtleButton(undefined, t.button, { url: "./professional.html" }), | ||||
|         ]).SetClass("flex flex-col border border-gray-300 p-2 rounded-lg") | ||||
|     } | ||||
|     private static createUnofficialThemeList( | ||||
|         buttonClass: string, | ||||
|         state: UserRelatedState, | ||||
|         themeListClasses: string, | ||||
|         search: UIEventSource<string> | ||||
|     ): BaseUIElement { | ||||
|         var currentIds: Store<string[]> = state.installedUserThemes | ||||
| 
 | ||||
|         var stableIds = Stores.ListStabilized<string>(currentIds) | ||||
|         return new VariableUiElement( | ||||
|             stableIds.map((ids) => { | ||||
|                 const allThemes: { element: BaseUIElement; predicate?: (s: string) => boolean }[] = | ||||
|                     [] | ||||
|                 for (const id of ids) { | ||||
|                     const themeInfo = state.GetUnofficialTheme(id) | ||||
|                     if (themeInfo === undefined) { | ||||
|                         continue | ||||
|                     } | ||||
|                     const link = MoreScreen.createLinkButton(state, themeInfo, true) | ||||
|                     if (link !== undefined) { | ||||
|                         allThemes.push({ | ||||
|                             element: link.SetClass(buttonClass), | ||||
|                             predicate: (s) => id.toLowerCase().indexOf(s) >= 0, | ||||
|                         }) | ||||
|                     } | ||||
|                 } | ||||
|                 if (allThemes.length <= 0) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 return new Combine([ | ||||
|                     Translations.t.general.customThemeIntro, | ||||
|                     new FilteredCombine(allThemes, search, { | ||||
|                         innerClasses: themeListClasses, | ||||
|                         onEmpty: MoreScreen.NothingFound(search), | ||||
|                     }), | ||||
|                 ]) | ||||
|             }) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     private static createPreviouslyVistedHiddenList( | ||||
|         state: UserRelatedState, | ||||
|         buttonClass: string, | ||||
|         themeListStyle: string, | ||||
|         search: UIEventSource<string> | ||||
|     ): BaseUIElement { | ||||
|         const t = Translations.t.general.morescreen | ||||
|         const prefix = "mapcomplete-hidden-theme-" | ||||
|         const hiddenThemes = themeOverview.filter((layout) => layout.hideFromOverview) | ||||
|         const hiddenTotal = hiddenThemes.length | ||||
| 
 | ||||
|         return new Toggle( | ||||
|             new VariableUiElement( | ||||
|                 state.osmConnection.preferencesHandler.preferences.map((allPreferences) => { | ||||
|                     const knownThemes: Set<string> = new Set( | ||||
|                         Utils.NoNull( | ||||
|                             Object.keys(allPreferences) | ||||
|                                 .filter((key) => key.startsWith(prefix)) | ||||
|                                 .map((key) => | ||||
|                                     key.substring(prefix.length, key.length - "-enabled".length) | ||||
|                                 ) | ||||
|                         ) | ||||
|                     ) | ||||
| 
 | ||||
|                     if (knownThemes.size === 0) { | ||||
|                         return undefined | ||||
|                     } | ||||
| 
 | ||||
|                     const knownThemeDescriptions = hiddenThemes | ||||
|                         .filter((theme) => knownThemes.has(theme.id)) | ||||
|                         .map((theme) => ({ | ||||
|                             element: MoreScreen.createLinkButton(state, theme)?.SetClass( | ||||
|                                 buttonClass | ||||
|                             ), | ||||
|                             predicate: MoreScreen.MatchesLayoutFunc(theme), | ||||
|                         })) | ||||
| 
 | ||||
|                     const knownLayouts = new FilteredCombine(knownThemeDescriptions, search, { | ||||
|                         innerClasses: themeListStyle, | ||||
|                         onEmpty: MoreScreen.NothingFound(search), | ||||
|                     }) | ||||
| 
 | ||||
|                     return new Combine([ | ||||
|                         new Title(t.previouslyHiddenTitle), | ||||
|                         t.hiddenExplanation.Subs({ | ||||
|                             hidden_discovered: "" + knownThemes.size, | ||||
|                             total_hidden: "" + hiddenTotal, | ||||
|                         }), | ||||
|                         knownLayouts, | ||||
|                     ]) | ||||
|                 }) | ||||
|             ).SetClass("flex flex-col"), | ||||
|             undefined, | ||||
|             state.osmConnection.isLoggedIn | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     private static MatchesLayoutFunc(layout: { | ||||
|         id: string | ||||
|  | @ -365,70 +249,4 @@ export default class MoreScreen extends Combine { | |||
|             return false | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static createOfficialThemesList( | ||||
|         state: { osmConnection: OsmConnection; locationControl?: UIEventSource<Loc> }, | ||||
|         buttonClass: string, | ||||
|         themeListStyle: string, | ||||
|         search: UIEventSource<string> | ||||
|     ): BaseUIElement { | ||||
|         let buttons: { element: BaseUIElement; predicate?: (s: string) => boolean }[] = | ||||
|             MoreScreen.officialThemes.map((layout) => { | ||||
|                 if (layout === undefined) { | ||||
|                     console.trace("Layout is undefined") | ||||
|                     return undefined | ||||
|                 } | ||||
|                 if (layout.hideFromOverview) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 const button = MoreScreen.createLinkButton(state, layout)?.SetClass(buttonClass) | ||||
|                 if (layout.id === personal.id) { | ||||
|                     const element = new VariableUiElement( | ||||
|                         state.osmConnection.userDetails | ||||
|                             .map((userdetails) => userdetails.csCount) | ||||
|                             .map((csCount) => { | ||||
|                                 if (csCount < Constants.userJourney.personalLayoutUnlock) { | ||||
|                                     return undefined | ||||
|                                 } else { | ||||
|                                     return button | ||||
|                                 } | ||||
|                             }) | ||||
|                     ) | ||||
|                     return { element } | ||||
|                 } | ||||
| 
 | ||||
|                 return { element: button, predicate: MoreScreen.MatchesLayoutFunc(layout) } | ||||
|             }) | ||||
| 
 | ||||
|         const professional = MoreScreen.CreateProffessionalSerivesButton() | ||||
|         const customGeneratorLink = MoreScreen.createCustomGeneratorButton(state) | ||||
|         buttons.splice(0, 0, { element: customGeneratorLink }, { element: professional }) | ||||
|         return new FilteredCombine(buttons, search, { | ||||
|             innerClasses: themeListStyle, | ||||
|             onEmpty: MoreScreen.NothingFound(search), | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      * Returns either a link to the issue tracker or a link to the custom generator, depending on the achieved number of changesets | ||||
|      * */ | ||||
|     private static createCustomGeneratorButton(state: { | ||||
|         osmConnection: OsmConnection | ||||
|     }): VariableUiElement { | ||||
|         const tr = Translations.t.general.morescreen | ||||
|         return new VariableUiElement( | ||||
|             state.osmConnection.userDetails.map((userDetails) => { | ||||
|                 if (userDetails.csCount < Constants.userJourney.themeGeneratorReadOnlyUnlock) { | ||||
|                     return new SubtleButton(null, tr.requestATheme.Clone(), { | ||||
|                         url: "https://github.com/pietervdvn/MapComplete/issues", | ||||
|                         newTab: true, | ||||
|                     }) | ||||
|                 } | ||||
|                 return new SubtleButton(Svg.pencil_ui(), tr.createYourOwnTheme.Clone(), { | ||||
|                     url: "https://pietervdvn.github.io/mc/legacy/070/customGenerator.html", | ||||
|                     newTab: false, | ||||
|                 }) | ||||
|             }) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										65
									
								
								UI/BigComponents/NoThemeResultButton.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								UI/BigComponents/NoThemeResultButton.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | |||
| <script lang="ts" context="module"> | ||||
|   export interface Theme { | ||||
|     id: string | ||||
|     icon: string | ||||
|     title: any | ||||
|     shortDescription: any | ||||
|     definition?: any | ||||
|     mustHaveLanguage?: boolean | ||||
|     hideFromOverview: boolean | ||||
|     keywords?: any[] | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import Svg from "../../Svg" | ||||
|   import SubtleButton from "../Base/SubtleButton.svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
| 
 | ||||
|   export let search: UIEventSource<string> | ||||
| 
 | ||||
|   const t = Translations.t.general.morescreen | ||||
| </script> | ||||
| 
 | ||||
| <span> | ||||
|   <h5>{t.noMatchingThemes.toString()}</h5> | ||||
|   <button | ||||
|     on:click={() => { | ||||
|       search.setData("") | ||||
|     }} | ||||
|   > | ||||
|     <span> | ||||
|       <SubtleButton> | ||||
|         <span slot="image"> | ||||
|           <ToSvelte construct={Svg.search_disable_ui()} /> | ||||
|         </span> | ||||
|         <span slot="message">{t.noSearch.toString()}</span> | ||||
|       </SubtleButton> | ||||
|     </span> | ||||
|   </button> | ||||
| </span> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
|   span { | ||||
|     @apply flex flex-col items-center w-full; | ||||
| 
 | ||||
|     h5 { | ||||
|       @apply w-max font-bold; | ||||
|     } | ||||
| 
 | ||||
|     // SubtleButton | ||||
|     button { | ||||
|       @apply h-12; | ||||
| 
 | ||||
|       span { | ||||
|         @apply w-max; | ||||
| 
 | ||||
|         :global(img) { | ||||
|           @apply h-6; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										24
									
								
								UI/BigComponents/ProfessionalServicesButton.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								UI/BigComponents/ProfessionalServicesButton.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| <script lang="ts"> | ||||
|   import SubtleButton from "../Base/SubtleButton.svelte" | ||||
|   import Title from "../Base/Title" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
| 
 | ||||
|   const t = Translations.t.professional.indexPage | ||||
| </script> | ||||
| 
 | ||||
| <div> | ||||
|   <ToSvelte construct={new Title(t.hook, 4)} /> | ||||
|   <span> | ||||
|     {t.hookMore.toString()} | ||||
|   </span> | ||||
|   <SubtleButton options={{ url: "./professional.html" }}> | ||||
|     <span slot="message">{t.button.toString()}</span> | ||||
|   </SubtleButton> | ||||
| </div> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
|   div { | ||||
|     @apply flex flex-col border border-gray-300 p-2 rounded-lg; | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										110
									
								
								UI/BigComponents/ThemeButton.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								UI/BigComponents/ThemeButton.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,110 @@ | |||
| <script lang="ts"> | ||||
|   import SubtleButton from "../Base/SubtleButton.svelte" | ||||
|   import { Translation } from "../i18n/Translation" | ||||
|   import type { Theme } from "./ThemesList.svelte" | ||||
|   import * as personal from "../../assets/themes/personal/personal.json" | ||||
|   import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
|   import Constants from "../../Models/Constants" | ||||
|   import type Loc from "../../Models/Loc" | ||||
| 
 | ||||
|   export let theme: Theme | ||||
|   export let isCustom: boolean = false | ||||
|   export let userDetails: UIEventSource<UserDetails> | ||||
|   export let state: { osmConnection: OsmConnection; locationControl?: UIEventSource<Loc> } | ||||
| 
 | ||||
|   $: title = new Translation( | ||||
|     theme.title, | ||||
|     !isCustom && !theme.mustHaveLanguage ? "themes:" + theme.id + ".title" : undefined | ||||
|   ).toString() | ||||
|   $: description = new Translation(theme.shortDescription).toString() | ||||
| 
 | ||||
|   // TODO: Improve this function | ||||
|   function createUrl( | ||||
|     layout: { id: string; definition?: string }, | ||||
|     isCustom: boolean, | ||||
|     state?: { locationControl?: UIEventSource<{ lat; lon; zoom }>; layoutToUse?: { id } } | ||||
|   ): Store<string> { | ||||
|     if (layout === undefined) { | ||||
|       return undefined | ||||
|     } | ||||
|     if (layout.id === undefined) { | ||||
|       console.error("ID is undefined for layout", layout) | ||||
|       return undefined | ||||
|     } | ||||
| 
 | ||||
|     if (layout.id === state?.layoutToUse?.id) { | ||||
|       return undefined | ||||
|     } | ||||
| 
 | ||||
|     const currentLocation = state?.locationControl | ||||
| 
 | ||||
|     let path = window.location.pathname | ||||
|     // Path starts with a '/' and contains everything, e.g. '/dir/dir/page.html' | ||||
|     path = path.substr(0, path.lastIndexOf("/")) | ||||
|     // Path will now contain '/dir/dir', or empty string in case of nothing | ||||
|     if (path === "") { | ||||
|       path = "." | ||||
|     } | ||||
| 
 | ||||
|     let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?` | ||||
|     if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { | ||||
|       linkPrefix = `${path}/theme.html?layout=${layout.id}&` | ||||
|     } | ||||
| 
 | ||||
|     if (isCustom) { | ||||
|       linkPrefix = `${path}/theme.html?userlayout=${layout.id}&` | ||||
|     } | ||||
| 
 | ||||
|     let hash = "" | ||||
|     if (layout.definition !== undefined) { | ||||
|       hash = "#" + btoa(JSON.stringify(layout.definition)) | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       currentLocation?.map((currentLocation) => { | ||||
|         const params = [ | ||||
|           ["z", currentLocation?.zoom], | ||||
|           ["lat", currentLocation?.lat], | ||||
|           ["lon", currentLocation?.lon], | ||||
|         ] | ||||
|           .filter((part) => part[1] !== undefined) | ||||
|           .map((part) => part[0] + "=" + part[1]) | ||||
|           .join("&") | ||||
|         return `${linkPrefix}${params}${hash}` | ||||
|       }) ?? new ImmutableStore<string>(`${linkPrefix}`) | ||||
|     ) | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| {#if theme.id !== personal.id || $userDetails.csCount > Constants.userJourney.personalLayoutUnlock} | ||||
|   <div> | ||||
|     <SubtleButton options={{ url: createUrl(theme, isCustom, state) }}> | ||||
|       <img slot="image" src={theme.icon} alt="" /> | ||||
|       <span slot="message" class="message"> | ||||
|         <span> | ||||
|           <span>{title}</span> | ||||
|           <span>{description}</span> | ||||
|         </span> | ||||
|       </span> | ||||
|     </SubtleButton> | ||||
|   </div> | ||||
| {/if} | ||||
| 
 | ||||
| <style lang="scss"> | ||||
|   div { | ||||
|     @apply h-32 min-h-[8rem] max-h-32 text-ellipsis overflow-hidden; | ||||
| 
 | ||||
|     span.message { | ||||
|       @apply flex flex-col justify-center h-24; | ||||
| 
 | ||||
|       & > span { | ||||
|         @apply flex flex-col overflow-hidden; | ||||
| 
 | ||||
|         span:nth-child(2) { | ||||
|           @apply text-[#999]; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										83
									
								
								UI/BigComponents/ThemesList.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								UI/BigComponents/ThemesList.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,83 @@ | |||
| <script lang="ts" context="module"> | ||||
|   export interface Theme { | ||||
|     id: string | ||||
|     icon: string | ||||
|     title: any | ||||
|     shortDescription: any | ||||
|     definition?: any | ||||
|     mustHaveLanguage?: boolean | ||||
|     hideFromOverview?: boolean | ||||
|     keywords?: any[] | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|   import NoThemeResultButton from "./NoThemeResultButton.svelte" | ||||
| 
 | ||||
|   import { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import type Loc from "../../Models/Loc" | ||||
|   import Locale from "../i18n/Locale" | ||||
|   import CustomGeneratorButton from "./CustomGeneratorButton.svelte" | ||||
|   import ProfessionalServicesButton from "./ProfessionalServicesButton.svelte" | ||||
|   import ThemeButton from "./ThemeButton.svelte" | ||||
| 
 | ||||
|   export let search: UIEventSource<string> | ||||
|   export let themes: Theme[] | ||||
|   export let state: { osmConnection: OsmConnection; locationControl?: UIEventSource<Loc> } | ||||
|   export let isCustom: boolean = false | ||||
|   export let onMainScreen: boolean = true | ||||
|   export let hideThemes: boolean = true | ||||
| 
 | ||||
|   // Filter theme based on search value | ||||
|   $: filteredThemes = themes.filter((theme) => { | ||||
|     if ($search === undefined || $search === "") return true | ||||
| 
 | ||||
|     const srch = $search.toLocaleLowerCase() | ||||
|     if (theme.id.toLowerCase().indexOf(srch) >= 0) { | ||||
|       return true | ||||
|     } | ||||
|     const entitiesToSearch = [theme.shortDescription, theme.title, ...(theme.keywords ?? [])] | ||||
|     for (const entity of entitiesToSearch) { | ||||
|       if (entity === undefined) { | ||||
|         continue | ||||
|       } | ||||
|       const term = entity["*"] ?? entity[Locale.language.data] | ||||
|       if (term?.toLowerCase()?.indexOf(search) >= 0) { | ||||
|         return true | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return false | ||||
|   }) | ||||
| </script> | ||||
| 
 | ||||
| <section> | ||||
|   <slot name="title" /> | ||||
|   <div class:gridview={onMainScreen}> | ||||
|     {#if ($search === undefined || $search === "") && !isCustom} | ||||
|       <CustomGeneratorButton userDetails={state.osmConnection.userDetails} /> | ||||
|       <ProfessionalServicesButton /> | ||||
|     {/if} | ||||
| 
 | ||||
|     {#each filteredThemes as theme} | ||||
|       {#if theme !== undefined && !(hideThemes && theme?.hideFromOverview)} | ||||
|         <ThemeButton {theme} {isCustom} userDetails={state.osmConnection.userDetails} {state} /> | ||||
|       {/if} | ||||
|     {/each} | ||||
|   </div> | ||||
| 
 | ||||
|   {#if filteredThemes.length == 0} | ||||
|     <NoThemeResultButton {search} /> | ||||
|   {/if} | ||||
| </section> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
|   section { | ||||
|     @apply flex flex-col; | ||||
| 
 | ||||
|     div.gridview { | ||||
|       @apply md:grid md:grid-flow-row md:grid-cols-2 lg:grid-cols-3 gap-4; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										35
									
								
								UI/BigComponents/UnofficialThemeList.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								UI/BigComponents/UnofficialThemeList.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| <script lang="ts"> | ||||
|   import { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
|   import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import type Loc from "../../Models/Loc" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import ThemesList from "./ThemesList.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import UserRelatedState from "../../Logic/State/UserRelatedState" | ||||
| 
 | ||||
|   export let search: UIEventSource<string> | ||||
|   export let state: UserRelatedState & { | ||||
|     osmConnection: OsmConnection | ||||
|     locationControl?: UIEventSource<Loc> | ||||
|   } | ||||
|   export let onMainScreen: boolean = true | ||||
| 
 | ||||
|   const t = Translations.t.general | ||||
|   const currentIds: Store<string[]> = state.installedUserThemes | ||||
|   const stableIds = Stores.ListStabilized<string>(currentIds) | ||||
|   $: customThemes = Utils.NoNull($stableIds.map((id) => state.GetUnofficialTheme(id))) | ||||
| </script> | ||||
| 
 | ||||
| <ThemesList | ||||
|   {search} | ||||
|   {state} | ||||
|   {onMainScreen} | ||||
|   themes={customThemes} | ||||
|   isCustom={true} | ||||
|   hideThemes={false} | ||||
| > | ||||
|   <svelte:fragment slot="title"> | ||||
|     <!-- TODO: Change string to exclude html --> | ||||
|     {@html t.customThemeIntro.toString()} | ||||
|   </svelte:fragment> | ||||
| </ThemesList> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue