Themes: add currently open badge

This commit is contained in:
Pieter Vander Vennet 2023-12-04 03:32:25 +01:00
parent 56a23deb2d
commit d9d330e912
7 changed files with 210 additions and 85 deletions

View file

@ -67,6 +67,14 @@
], ],
"metacondition": "__showTimeSensitiveIcons!=no" "metacondition": "__showTimeSensitiveIcons!=no"
}, },
{"id":"open_until",
"labels": ["defaults","in_favourite"],
"#":"Titleicon showing 'open until 17:00'",
"icon": {
"class": "w-20 mx-1 flex items-center"
},
"render": "{opening_hours_state()}"
},
{ {
"id": "phonelink", "id": "phonelink",
"labels": [ "labels": [

View file

@ -745,6 +745,10 @@ video {
top: 2.5rem; top: 2.5rem;
} }
.left-1\/4 {
left: 25%;
}
.isolate { .isolate {
isolation: isolate; isolation: isolate;
} }
@ -1088,6 +1092,10 @@ video {
height: 2.75rem; height: 2.75rem;
} }
.h-5 {
height: 1.25rem;
}
.h-48 { .h-48 {
height: 12rem; height: 12rem;
} }
@ -1198,6 +1206,14 @@ video {
width: 50%; width: 50%;
} }
.w-14 {
width: 3.5rem;
}
.w-5 {
width: 1.25rem;
}
.w-10 { .w-10 {
width: 2.5rem; width: 2.5rem;
} }
@ -1289,8 +1305,8 @@ video {
appearance: none; appearance: none;
} }
.grid-cols-3 { .grid-cols-2 {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.grid-cols-1 { .grid-cols-1 {
@ -1453,10 +1469,6 @@ video {
justify-self: end; justify-self: end;
} }
.justify-self-center {
justify-self: center;
}
.overflow-auto { .overflow-auto {
overflow: auto; overflow: auto;
} }

View file

@ -6,7 +6,6 @@
import { ImmutableStore } from "../../Logic/UIEventSource"; import { ImmutableStore } from "../../Logic/UIEventSource";
import { GeoOperations } from "../../Logic/GeoOperations"; import { GeoOperations } from "../../Logic/GeoOperations";
import Center from "../../assets/svg/Center.svelte"; import Center from "../../assets/svg/Center.svelte";
import { Utils } from "../../Utils";
export let feature: Feature; export let feature: Feature;
let properties: Record<string, string> = feature.properties; let properties: Record<string, string> = feature.properties;
@ -16,49 +15,49 @@
const favLayer = state.layerState.filteredLayers.get("favourite"); const favLayer = state.layerState.filteredLayers.get("favourite");
const favConfig = favLayer.layerDef; const favConfig = favLayer.layerDef;
const titleConfig = favConfig.title; const titleConfig = favConfig.title;
function center(){ function center() {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature) const [lon, lat] = GeoOperations.centerpointCoordinates(feature);
state.mapProperties.location.setData( state.mapProperties.location.setData(
{lon, lat} { lon, lat }
) );
state.guistate.menuIsOpened.setData(false) state.guistate.menuIsOpened.setData(false);
} }
function select(){ function select() {
state.selectedLayer.setData(favConfig) state.selectedLayer.setData(favConfig);
state.selectedElement.setData(feature) state.selectedElement.setData(feature);
center() center();
} }
const coord = GeoOperations.centerpointCoordinates(feature) const coord = GeoOperations.centerpointCoordinates(feature);
const distance = state.mapProperties.location.stabilized(500).mapD(({lon, lat}) => { const distance = state.mapProperties.location.stabilized(500).mapD(({ lon, lat }) => {
let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat])) let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat]));
if(meters < 1000){ if (meters < 1000) {
return meters +"m" return meters + "m";
} }
meters = Math.round(meters / 100) meters = Math.round(meters / 100);
const kmStr = ""+meters const kmStr = "" + meters;
return kmStr.substring(0, kmStr.length - 1)+"."+kmStr.substring(kmStr.length-1) +"km" return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km";
}) });
const titleIconBlacklist = ["osmlink","sharelink","favourite_title_icon"] const titleIconBlacklist = ["osmlink", "sharelink", "favourite_title_icon"];
</script> </script>
<div class="px-1 my-1 border-2 border-dashed border-gray-300 rounded grid grid-cols-3 items-center no-weblate"> <div class="px-1 my-1 border-2 border-dashed border-gray-300 rounded grid grid-cols-2 items-center no-weblate">
<button on:click={() => select()} class="cursor-pointer ml-1 m-0 link justify-self-start"> <button class="cursor-pointer ml-1 m-0 link justify-self-start" on:click={() => select()}>
<TagRenderingAnswer extraClasses="underline" config={titleConfig} layer={favConfig} selectedElement={feature} {tags} /> <TagRenderingAnswer config={titleConfig} extraClasses="underline" layer={favConfig} selectedElement={feature}
{tags} />
</button> </button>
{$distance}
<div class="flex items-center justify-self-end title-icons links-as-button gap-x-0.5 p-1 pt-0.5 sm:pt-1"> <div class="flex items-center justify-self-end title-icons links-as-button gap-x-0.5 p-1 pt-0.5 sm:pt-1">
{#each favConfig.titleIcons as titleIconConfig} {#each favConfig.titleIcons as titleIconConfig}
{#if (titleIconBlacklist.indexOf(titleIconConfig.id) < 0) && (titleIconConfig.condition?.matchesProperties(properties) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ...properties, ...state.userRelatedState.preferencesAsTags.data } ) ?? true) && titleIconConfig.IsKnown(properties)} {#if (titleIconBlacklist.indexOf(titleIconConfig.id) < 0) && (titleIconConfig.condition?.matchesProperties(properties) ?? true) && (titleIconConfig.metacondition?.matchesProperties({ ...properties, ...state.userRelatedState.preferencesAsTags.data }) ?? true) && titleIconConfig.IsKnown(properties)}
<div class={titleIconConfig.renderIconClass ?? "flex h-8 w-8 items-center"}> <div class={titleIconConfig.renderIconClass ?? "flex h-8 w-8 items-center"}>
<TagRenderingAnswer <TagRenderingAnswer
config={titleIconConfig} config={titleIconConfig}
@ -71,7 +70,12 @@
</div> </div>
{/if} {/if}
{/each} {/each}
<button on:click={() => center()} class="p-1" ><Center class="w-6 h-6"/></button> <button class="p-1" on:click={() => center()}>
<Center class="w-6 h-6" />
</button>
<div class="w-14">
{$distance}
</div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,50 @@
<script lang="ts">/**
* Simple visualisation which shows when the POI opens/closes next.
*/
import type { SpecialVisualizationState } from "../SpecialVisualization";
import { Store, Stores } from "../../Logic/UIEventSource";
import { OH } from "./OpeningHours";
import opening_hours from "opening_hours";
import Clock from "../../assets/svg/Clock.svelte";
import { Utils } from "../../Utils";
import Circle from "../../assets/svg/Circle.svelte";
import Ring from "../../assets/svg/Ring.svelte";
import { twMerge } from "tailwind-merge";
export let state: SpecialVisualizationState;
export let tags: Store<Record<string, string>>;
export let keyToUse: string = "opening_hours";
export let prefix: string = undefined;
export let postfix: string = undefined;
let oh: Store<opening_hours | "error" | undefined> = OH.CreateOhObjectStore(tags, keyToUse, prefix, postfix);
let currentState = oh.mapD(oh => typeof oh === "string" ? undefined : oh.getState());
let tomorrow = new Date();
tomorrow.setTime(tomorrow.getTime() + 24 * 60 * 60 * 1000);
let nextChange = oh
.mapD(oh => typeof oh === "string" ? undefined : oh.getNextChange(new Date(), tomorrow), [Stores.Chronic(5 * 60 * 1000)])
.mapD(date => Utils.TwoDigits(date.getHours()) + ":" + Utils.TwoDigits(date.getMinutes()));
let size = nextChange.map(change => change === undefined ? "absolute h-7 w-7" : "absolute h-5 w-5 top-0 left-1/4");
</script>
{#if $currentState !== undefined}
<div class="relative h-8 w-8">
{#if $currentState === true}
<Ring class={$size} color="#0f0" style="z-index: 0" />
<Clock class={$size} color="#0f0" style="z-index: 0" />
{:else if $currentState === false}
<Circle class={$size} color="#f00" style="z-index: 0" />
<Clock class={$size} color="#fff" style="z-index: 0" />
{/if}
{#if $nextChange !== undefined}
<span class="absolute bottom-0 font-bold text-sm" style="z-index: 1; background-color: #ffffff88; margin-top: 3px">
{$nextChange}
</span>
{/if}
</div>
{/if}

View file

@ -1,5 +1,6 @@
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import opening_hours from "opening_hours" import opening_hours from "opening_hours"
import { Store } from "../../Logic/UIEventSource"
export interface OpeningHour { export interface OpeningHour {
weekday: number // 0 is monday, 1 is tuesday, ... weekday: number // 0 is monday, 1 is tuesday, ...
@ -494,10 +495,48 @@ This list will be sorted
return [changeHours, changeHourText] return [changeHours, changeHourText]
} }
public static CreateOhObjectStore(
tags: Store<Record<string, string>>,
key: string = "opening_hours",
prefixToIgnore?: string,
postfixToIgnore?: string
): Store<opening_hours | undefined | "error"> {
prefixToIgnore ??= ""
postfixToIgnore ??= ""
const country = tags.map((tags) => tags._country)
return tags
.mapD((tags) => {
const value: string = tags[key]
if (value === undefined) {
return undefined
}
if (
(prefixToIgnore || postfixToIgnore) &&
value.startsWith(prefixToIgnore) &&
value.endsWith(postfixToIgnore)
) {
return value
.substring(prefixToIgnore.length, value.length - postfixToIgnore.length)
.trim()
}
return value
})
.mapD(
(ohtext) => {
try {
return OH.CreateOhObject(<any>tags.data, ohtext, country.data)
} catch (e) {
return "error"
}
},
[country]
)
}
public static CreateOhObject( public static CreateOhObject(
tags: Record<string, string> & { _lat: number; _lon: number; _country?: string }, tags: Record<string, string> & { _lat: number; _lon: number; _country?: string },
textToParse: string textToParse: string,
country?: string
) { ) {
// noinspection JSPotentiallyInvalidConstructorUsage // noinspection JSPotentiallyInvalidConstructorUsage
return new opening_hours( return new opening_hours(
@ -506,7 +545,7 @@ This list will be sorted
lat: tags._lat, lat: tags._lat,
lon: tags._lon, lon: tags._lon,
address: { address: {
country_code: tags._country?.toLowerCase(), country_code: country.toLowerCase(),
state: undefined, state: undefined,
}, },
}, },

View file

@ -3,7 +3,6 @@ import Combine from "../Base/Combine"
import { FixedUiElement } from "../Base/FixedUiElement" import { FixedUiElement } from "../Base/FixedUiElement"
import { OH } from "./OpeningHours" import { OH } from "./OpeningHours"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Constants from "../../Models/Constants"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
import { VariableUiElement } from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
@ -30,48 +29,20 @@ export default class OpeningHoursVisualization extends Toggle {
prefix = "", prefix = "",
postfix = "" postfix = ""
) { ) {
const country = tags.map((tags) => tags._country) const openingHoursStore = OH.CreateOhObjectStore(tags, key, prefix, postfix)
const ohTable = new VariableUiElement( const ohTable = new VariableUiElement(
tags openingHoursStore.map((opening_hours_obj) => {
.map((tags) => { if (opening_hours_obj === undefined) {
const value: string = tags[key] return new FixedUiElement("No opening hours defined with key " + key).SetClass(
if (value === undefined) { "alert"
return undefined )
} }
if (value.startsWith(prefix) && value.endsWith(postfix)) {
return value.substring(prefix.length, value.length - postfix.length).trim() if (opening_hours_obj === "error") {
} return Translations.t.general.opening_hours.error_loading
return value }
}) // This mapping will absorb all other changes to tags in order to prevent regeneration return OpeningHoursVisualization.CreateFullVisualisation(opening_hours_obj)
.map( })
(ohtext) => {
if (ohtext === undefined) {
return new FixedUiElement(
"No opening hours defined with key " + key
).SetClass("alert")
}
try {
return OpeningHoursVisualization.CreateFullVisualisation(
OH.CreateOhObject(<any>tags.data, ohtext)
)
} catch (e) {
console.warn(e, e.stack)
return new Combine([
Translations.t.general.opening_hours.error_loading,
new Toggle(
new FixedUiElement(e).SetClass("subtle"),
undefined,
state?.osmConnection?.userDetails.map(
(userdetails) =>
userdetails.csCount >=
Constants.userJourney.tagsVisibleAndWikiLinked
)
),
])
}
},
[country]
)
) )
super( super(

View file

@ -81,6 +81,7 @@ import LogoutButton from "./Base/LogoutButton.svelte"
import OpenJosm from "./Base/OpenJosm.svelte" import OpenJosm from "./Base/OpenJosm.svelte"
import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte" import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte"
import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte" import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte"
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
class NearbyImageVis implements SpecialVisualization { class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -827,6 +828,46 @@ export default class SpecialVisualizations {
) )
}, },
}, },
{
funcName: "opening_hours_state",
docs: "A small element, showing if the POI is currently open and when the next change is",
args: [
{
name: "key",
defaultValue: "opening_hours",
doc: "The tagkey from which the opening hours are read.",
},
{
name: "prefix",
defaultValue: "",
doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__",
},
{
name: "postfix",
defaultValue: "",
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
},
],
needsUrls: [],
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const keyToUse = args[0]
const prefix = args[1]
const postfix = args[2]
return new SvelteUIElement(NextChangeViz, {
state,
keyToUse,
tags,
prefix,
postfix,
})
},
},
{ {
funcName: "canonical", funcName: "canonical",
needsUrls: [], needsUrls: [],