Themes: add currently open badge
This commit is contained in:
parent
56a23deb2d
commit
d9d330e912
7 changed files with 210 additions and 85 deletions
|
@ -67,6 +67,14 @@
|
|||
],
|
||||
"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",
|
||||
"labels": [
|
||||
|
|
|
@ -745,6 +745,10 @@ video {
|
|||
top: 2.5rem;
|
||||
}
|
||||
|
||||
.left-1\/4 {
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
.isolate {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
@ -1088,6 +1092,10 @@ video {
|
|||
height: 2.75rem;
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.h-48 {
|
||||
height: 12rem;
|
||||
}
|
||||
|
@ -1198,6 +1206,14 @@ video {
|
|||
width: 50%;
|
||||
}
|
||||
|
||||
.w-14 {
|
||||
width: 3.5rem;
|
||||
}
|
||||
|
||||
.w-5 {
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.w-10 {
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
@ -1289,8 +1305,8 @@ video {
|
|||
appearance: none;
|
||||
}
|
||||
|
||||
.grid-cols-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
.grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.grid-cols-1 {
|
||||
|
@ -1453,10 +1469,6 @@ video {
|
|||
justify-self: end;
|
||||
}
|
||||
|
||||
.justify-self-center {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.overflow-auto {
|
||||
overflow: auto;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import { ImmutableStore } from "../../Logic/UIEventSource";
|
||||
import { GeoOperations } from "../../Logic/GeoOperations";
|
||||
import Center from "../../assets/svg/Center.svelte";
|
||||
import { Utils } from "../../Utils";
|
||||
|
||||
export let feature: Feature;
|
||||
let properties: Record<string, string> = feature.properties;
|
||||
|
@ -16,49 +15,49 @@
|
|||
const favLayer = state.layerState.filteredLayers.get("favourite");
|
||||
const favConfig = favLayer.layerDef;
|
||||
const titleConfig = favConfig.title;
|
||||
|
||||
function center(){
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
|
||||
function center() {
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature);
|
||||
state.mapProperties.location.setData(
|
||||
{lon, lat}
|
||||
)
|
||||
state.guistate.menuIsOpened.setData(false)
|
||||
{ lon, lat }
|
||||
);
|
||||
state.guistate.menuIsOpened.setData(false);
|
||||
}
|
||||
|
||||
function select(){
|
||||
state.selectedLayer.setData(favConfig)
|
||||
state.selectedElement.setData(feature)
|
||||
center()
|
||||
|
||||
function select() {
|
||||
state.selectedLayer.setData(favConfig);
|
||||
state.selectedElement.setData(feature);
|
||||
center();
|
||||
}
|
||||
|
||||
const coord = GeoOperations.centerpointCoordinates(feature)
|
||||
const distance = state.mapProperties.location.stabilized(500).mapD(({lon, lat}) => {
|
||||
let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat]))
|
||||
|
||||
if(meters < 1000){
|
||||
return meters +"m"
|
||||
|
||||
const coord = GeoOperations.centerpointCoordinates(feature);
|
||||
const distance = state.mapProperties.location.stabilized(500).mapD(({ lon, lat }) => {
|
||||
let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat]));
|
||||
|
||||
if (meters < 1000) {
|
||||
return meters + "m";
|
||||
}
|
||||
|
||||
meters = Math.round(meters / 100)
|
||||
const kmStr = ""+meters
|
||||
|
||||
|
||||
return kmStr.substring(0, kmStr.length - 1)+"."+kmStr.substring(kmStr.length-1) +"km"
|
||||
})
|
||||
const titleIconBlacklist = ["osmlink","sharelink","favourite_title_icon"]
|
||||
|
||||
meters = Math.round(meters / 100);
|
||||
const kmStr = "" + meters;
|
||||
|
||||
|
||||
return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km";
|
||||
});
|
||||
const titleIconBlacklist = ["osmlink", "sharelink", "favourite_title_icon"];
|
||||
|
||||
</script>
|
||||
|
||||
<div class="px-1 my-1 border-2 border-dashed border-gray-300 rounded grid grid-cols-3 items-center no-weblate">
|
||||
<button on:click={() => select()} class="cursor-pointer ml-1 m-0 link justify-self-start">
|
||||
<TagRenderingAnswer extraClasses="underline" config={titleConfig} layer={favConfig} selectedElement={feature} {tags} />
|
||||
<div class="px-1 my-1 border-2 border-dashed border-gray-300 rounded grid grid-cols-2 items-center no-weblate">
|
||||
<button class="cursor-pointer ml-1 m-0 link justify-self-start" on:click={() => select()}>
|
||||
<TagRenderingAnswer config={titleConfig} extraClasses="underline" layer={favConfig} selectedElement={feature}
|
||||
{tags} />
|
||||
</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">
|
||||
{#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"}>
|
||||
<TagRenderingAnswer
|
||||
config={titleIconConfig}
|
||||
|
@ -71,7 +70,12 @@
|
|||
</div>
|
||||
{/if}
|
||||
{/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>
|
||||
|
|
50
src/UI/OpeningHours/NextChangeViz.svelte
Normal file
50
src/UI/OpeningHours/NextChangeViz.svelte
Normal 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}
|
|
@ -1,5 +1,6 @@
|
|||
import { Utils } from "../../Utils"
|
||||
import opening_hours from "opening_hours"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
|
||||
export interface OpeningHour {
|
||||
weekday: number // 0 is monday, 1 is tuesday, ...
|
||||
|
@ -494,10 +495,48 @@ This list will be sorted
|
|||
|
||||
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(
|
||||
tags: Record<string, string> & { _lat: number; _lon: number; _country?: string },
|
||||
textToParse: string
|
||||
textToParse: string,
|
||||
country?: string
|
||||
) {
|
||||
// noinspection JSPotentiallyInvalidConstructorUsage
|
||||
return new opening_hours(
|
||||
|
@ -506,7 +545,7 @@ This list will be sorted
|
|||
lat: tags._lat,
|
||||
lon: tags._lon,
|
||||
address: {
|
||||
country_code: tags._country?.toLowerCase(),
|
||||
country_code: country.toLowerCase(),
|
||||
state: undefined,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,7 +3,6 @@ import Combine from "../Base/Combine"
|
|||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import { OH } from "./OpeningHours"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Constants from "../../Models/Constants"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
|
@ -30,48 +29,20 @@ export default class OpeningHoursVisualization extends Toggle {
|
|||
prefix = "",
|
||||
postfix = ""
|
||||
) {
|
||||
const country = tags.map((tags) => tags._country)
|
||||
const openingHoursStore = OH.CreateOhObjectStore(tags, key, prefix, postfix)
|
||||
const ohTable = new VariableUiElement(
|
||||
tags
|
||||
.map((tags) => {
|
||||
const value: string = tags[key]
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if (value.startsWith(prefix) && value.endsWith(postfix)) {
|
||||
return value.substring(prefix.length, value.length - postfix.length).trim()
|
||||
}
|
||||
return value
|
||||
}) // This mapping will absorb all other changes to tags in order to prevent regeneration
|
||||
.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]
|
||||
)
|
||||
openingHoursStore.map((opening_hours_obj) => {
|
||||
if (opening_hours_obj === undefined) {
|
||||
return new FixedUiElement("No opening hours defined with key " + key).SetClass(
|
||||
"alert"
|
||||
)
|
||||
}
|
||||
|
||||
if (opening_hours_obj === "error") {
|
||||
return Translations.t.general.opening_hours.error_loading
|
||||
}
|
||||
return OpeningHoursVisualization.CreateFullVisualisation(opening_hours_obj)
|
||||
})
|
||||
)
|
||||
|
||||
super(
|
||||
|
|
|
@ -81,6 +81,7 @@ import LogoutButton from "./Base/LogoutButton.svelte"
|
|||
import OpenJosm from "./Base/OpenJosm.svelte"
|
||||
import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte"
|
||||
import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte"
|
||||
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
|
||||
|
||||
class NearbyImageVis implements SpecialVisualization {
|
||||
// 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",
|
||||
needsUrls: [],
|
||||
|
|
Loading…
Reference in a new issue