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"
|
"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": [
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
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 { 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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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: [],
|
||||||
|
|
Loading…
Reference in a new issue