forked from MapComplete/MapComplete
Refactoring: attempting to make State smaller
This commit is contained in:
parent
a6f56acad6
commit
849c61c8a1
28 changed files with 529 additions and 485 deletions
|
@ -11,7 +11,7 @@ export class CenterMessageBox extends UIElement {
|
|||
this.ListenTo(State.state.locationControl);
|
||||
this.ListenTo(State.state.layerUpdater.retries);
|
||||
this.ListenTo(State.state.layerUpdater.runningQuery);
|
||||
this.ListenTo(State.state.layerUpdater.sufficentlyZoomed);
|
||||
this.ListenTo(State.state.layerUpdater.sufficientlyZoomed);
|
||||
}
|
||||
|
||||
private static prep(): { innerHtml: string, done: boolean } {
|
||||
|
@ -27,7 +27,7 @@ export class CenterMessageBox extends UIElement {
|
|||
return {innerHtml: Translations.t.centerMessage.loadingData.Render(), done: false};
|
||||
|
||||
}
|
||||
if (!lu.sufficentlyZoomed.data) {
|
||||
if (!lu.sufficientlyZoomed.data) {
|
||||
return {innerHtml: Translations.t.centerMessage.zoomIn.Render(), done: false};
|
||||
} else {
|
||||
return {innerHtml: Translations.t.centerMessage.ready.Render(), done: true};
|
||||
|
|
|
@ -11,7 +11,7 @@ import {MultiInput} from "../Input/MultiInput";
|
|||
import TagRenderingPanel from "./TagRenderingPanel";
|
||||
import SingleSetting from "./SingleSetting";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import AvailableBaseLayers from "../../Logic/AvailableBaseLayers";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import {DropDown} from "../Input/DropDown";
|
||||
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
|
||||
import Svg from "../../Svg";
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
import {ImageSearcher} from "../../Logic/ImageSearcher";
|
||||
import {ImageSearcher} from "../../Logic/Actors/ImageSearcher";
|
||||
import {SlideShow} from "./SlideShow";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Combine from "../Base/Combine";
|
||||
import DeleteImage from "./DeleteImage";
|
||||
import {WikimediaImage} from "./WikimediaImage";
|
||||
import {ImgurImage} from "./ImgurImage";
|
||||
import {MapillaryImage} from "./MapillaryImage";
|
||||
import {SimpleImageElement} from "./SimpleImageElement";
|
||||
|
||||
|
||||
export class ImageCarousel extends UIElement{
|
||||
|
@ -12,11 +16,11 @@ export class ImageCarousel extends UIElement{
|
|||
|
||||
constructor(tags: UIEventSource<any>, imagePrefix: string = "image", loadSpecial: boolean =true) {
|
||||
super(tags);
|
||||
const searcher : UIEventSource<{url:string}[]> = new ImageSearcher(tags, imagePrefix, loadSpecial);
|
||||
const searcher : UIEventSource<{url:string}[]> = new ImageSearcher(tags, imagePrefix, loadSpecial).images;
|
||||
const uiElements = searcher.map((imageURLS: {key: string, url:string}[]) => {
|
||||
const uiElements: UIElement[] = [];
|
||||
for (const url of imageURLS) {
|
||||
let image = ImageSearcher.CreateImageElement(url.url);
|
||||
let image = ImageCarousel.CreateImageElement(url.url);
|
||||
if(url.key !== undefined){
|
||||
image = new Combine([
|
||||
image,
|
||||
|
@ -31,6 +35,27 @@ export class ImageCarousel extends UIElement{
|
|||
this.slideshow = new SlideShow(uiElements).HideOnEmpty(true);
|
||||
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates either a 'simpleimage' or a 'wikimediaimage' based on the string
|
||||
* @param url
|
||||
* @constructor
|
||||
*/
|
||||
private static CreateImageElement(url: string): UIElement {
|
||||
// @ts-ignore
|
||||
if (url.startsWith("File:")) {
|
||||
return new WikimediaImage(url);
|
||||
} else if (url.toLowerCase().startsWith("https://commons.wikimedia.org/wiki/")) {
|
||||
const commons = url.substr("https://commons.wikimedia.org/wiki/".length);
|
||||
return new WikimediaImage(commons);
|
||||
} else if (url.toLowerCase().startsWith("https://i.imgur.com/")) {
|
||||
return new ImgurImage(url);
|
||||
} else if (url.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) {
|
||||
return new MapillaryImage(url);
|
||||
} else {
|
||||
return new SimpleImageElement(new UIEventSource<string>(url));
|
||||
}
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
return this.slideshow.Render();
|
||||
|
|
|
@ -8,7 +8,7 @@ import {UIElement} from "../UIElement";
|
|||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import CombinedInputElement from "./CombinedInputElement";
|
||||
import SimpleDatePicker from "./SimpleDatePicker";
|
||||
import OpeningHoursInput from "./OpeningHours/OpeningHoursInput";
|
||||
import OpeningHoursInput from "../OpeningHours/OpeningHoursInput";
|
||||
import DirectionInput from "./DirectionInput";
|
||||
|
||||
interface TextFieldDef {
|
||||
|
|
65
UI/Misc/Attribution.ts
Normal file
65
UI/Misc/Attribution.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
import Link from "../Base/Link";
|
||||
import Svg from "../../Svg";
|
||||
import {Basemap} from "../../Logic/Leaflet/Basemap";
|
||||
import Combine from "../Base/Combine";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UserDetails} from "../../Logic/Osm/OsmConnection";
|
||||
import Constants from "../../Models/Constants";
|
||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||
import Loc from "../../Models/Loc";
|
||||
|
||||
export default class Attribution extends UIElement {
|
||||
|
||||
private readonly _location: UIEventSource<Loc>;
|
||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
||||
private readonly _userDetails: UIEventSource<UserDetails>;
|
||||
private readonly _basemap: Basemap;
|
||||
|
||||
constructor(location: UIEventSource<Loc>,
|
||||
userDetails: UIEventSource<UserDetails>,
|
||||
layoutToUse: UIEventSource<LayoutConfig>,
|
||||
basemap: Basemap) {
|
||||
super(location);
|
||||
this._layoutToUse = layoutToUse;
|
||||
this.ListenTo(layoutToUse);
|
||||
this._userDetails = userDetails;
|
||||
this._basemap = basemap;
|
||||
this.ListenTo(userDetails);
|
||||
this._location = location;
|
||||
this.SetClass("map-attribution");
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
const location : Loc = this._location.data;
|
||||
const userDetails = this._userDetails.data;
|
||||
|
||||
const mapComplete = new Link(`Mapcomplete ${Constants.vNumber}`, 'https://github.com/pietervdvn/MapComplete', true);
|
||||
const reportBug = new Link(Svg.bug_img, "https://github.com/pietervdvn/MapComplete/issues", true);
|
||||
|
||||
const layoutId = this._layoutToUse.data.id;
|
||||
const osmChaLink = `https://osmcha.org/?filters=%7B%22comment%22%3A%5B%7B%22label%22%3A%22%23${layoutId}%22%2C%22value%22%3A%22%23${layoutId}%22%7D%5D%2C%22date__gte%22%3A%5B%7B%22label%22%3A%222020-07-05%22%2C%22value%22%3A%222020-07-05%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22MapComplete%22%2C%22value%22%3A%22MapComplete%22%7D%5D%7D`
|
||||
const stats = new Link(Svg.statistics_img, osmChaLink, true)
|
||||
let editHere: (UIElement | string) = "";
|
||||
if (location !== undefined) {
|
||||
const idLink = `https://www.openstreetmap.org/edit?editor=id#map=${location.zoom}/${location.lat}/${location.lon}`
|
||||
editHere = new Link(Svg.pencil_img, idLink, true);
|
||||
}
|
||||
let editWithJosm: (UIElement | string) = ""
|
||||
if (location !== undefined &&
|
||||
this._basemap !== undefined &&
|
||||
userDetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) {
|
||||
const bounds = this._basemap.map.getBounds();
|
||||
const top = bounds.getNorth();
|
||||
const bottom = bounds.getSouth();
|
||||
const right = bounds.getEast();
|
||||
const left = bounds.getWest();
|
||||
|
||||
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
||||
editWithJosm = new Link(Svg.josm_logo_img, josmLink, true);
|
||||
}
|
||||
return new Combine([mapComplete, reportBug, " | ", stats, " | ", editHere, editWithJosm]).Render();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import {UIElement} from "./UIElement";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import opening_hours from "opening_hours";
|
||||
import Combine from "./Base/Combine";
|
||||
import Translations from "./i18n/Translations";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import {OH} from "../Logic/OpeningHours";
|
||||
import State from "../State";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import Combine from "../Base/Combine";
|
||||
import State from "../../State";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import {OH} from "./OpeningHours";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
||||
export default class OpeningHoursVisualization extends UIElement {
|
||||
private readonly _key: string;
|
348
UI/OpeningHours/OpeningHours.ts
Normal file
348
UI/OpeningHours/OpeningHours.ts
Normal file
|
@ -0,0 +1,348 @@
|
|||
import {Utils} from "../../Utils";
|
||||
|
||||
export interface OpeningHour {
|
||||
weekday: number, // 0 is monday, 1 is tuesday, ...
|
||||
startHour: number,
|
||||
startMinutes: number,
|
||||
endHour: number,
|
||||
endMinutes: number
|
||||
}
|
||||
|
||||
export class OH {
|
||||
|
||||
|
||||
private static readonly days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
||||
private static readonly daysIndexed = {
|
||||
mo: 0,
|
||||
tu: 1,
|
||||
we: 2,
|
||||
th: 3,
|
||||
fr: 4,
|
||||
sa: 5,
|
||||
su: 6
|
||||
}
|
||||
|
||||
public static hhmm(h: number, m: number): string {
|
||||
if (h == 24) {
|
||||
return "00:00";
|
||||
}
|
||||
return Utils.TwoDigits(h) + ":" + Utils.TwoDigits(m);
|
||||
}
|
||||
|
||||
public static ToString(ohs: OpeningHour[]) {
|
||||
if (ohs.length == 0) {
|
||||
return "";
|
||||
}
|
||||
const partsPerWeekday: string [][] = [[], [], [], [], [], [], []];
|
||||
|
||||
|
||||
for (const oh of ohs) {
|
||||
partsPerWeekday[oh.weekday].push(OH.hhmm(oh.startHour, oh.startMinutes) + "-" + OH.hhmm(oh.endHour, oh.endMinutes));
|
||||
}
|
||||
|
||||
const stringPerWeekday = partsPerWeekday.map(parts => parts.sort().join(", "));
|
||||
|
||||
const rules = [];
|
||||
|
||||
let rangeStart = 0;
|
||||
let rangeEnd = 0;
|
||||
|
||||
function pushRule(){
|
||||
const rule = stringPerWeekday[rangeStart];
|
||||
if(rule === ""){
|
||||
return;
|
||||
}
|
||||
if (rangeStart == (rangeEnd - 1)) {
|
||||
rules.push(
|
||||
`${OH.days[rangeStart]} ${rule}`
|
||||
);
|
||||
} else {
|
||||
rules.push(
|
||||
`${OH.days[rangeStart]}-${OH.days[rangeEnd-1]} ${rule}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (; rangeEnd < 7; rangeEnd++) {
|
||||
|
||||
if (stringPerWeekday[rangeStart] != stringPerWeekday[rangeEnd]) {
|
||||
pushRule();
|
||||
rangeStart = rangeEnd
|
||||
}
|
||||
|
||||
}
|
||||
pushRule();
|
||||
|
||||
const oh = rules.join("; ")
|
||||
if (oh === "Mo-Su 00:00-00:00") {
|
||||
return "24/7"
|
||||
}
|
||||
return oh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge duplicate opening-hour element in place.
|
||||
* Returns true if something changed
|
||||
* @param ohs
|
||||
* @constructor
|
||||
*/
|
||||
public static MergeTimes(ohs: OpeningHour[]): OpeningHour[] {
|
||||
const queue = [...ohs];
|
||||
const newList = [];
|
||||
while (queue.length > 0) {
|
||||
let maybeAdd = queue.pop();
|
||||
|
||||
let doAddEntry = true;
|
||||
if(maybeAdd.weekday == undefined){
|
||||
doAddEntry = false;
|
||||
}
|
||||
|
||||
for (let i = newList.length - 1; i >= 0 && doAddEntry; i--) {
|
||||
let guard = newList[i];
|
||||
if (maybeAdd.weekday != guard.weekday) {
|
||||
// Not the same day
|
||||
continue
|
||||
}
|
||||
|
||||
if (OH.startTimeLiesInRange(maybeAdd, guard) && OH.endTimeLiesInRange(maybeAdd, guard)) {
|
||||
// Guard fully covers 'maybeAdd': we can safely ignore maybeAdd
|
||||
doAddEntry = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (OH.startTimeLiesInRange(guard, maybeAdd) && OH.endTimeLiesInRange(guard, maybeAdd)) {
|
||||
// 'maybeAdd' fully covers Guard - the guard is killed
|
||||
newList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (OH.startTimeLiesInRange(maybeAdd, guard) || OH.endTimeLiesInRange(maybeAdd, guard)
|
||||
|| OH.startTimeLiesInRange(guard, maybeAdd) || OH.endTimeLiesInRange(guard, maybeAdd)) {
|
||||
// At this point, the maybeAdd overlaps the guard: we should extend the guard and retest it
|
||||
newList.splice(i, 1);
|
||||
let startHour = guard.startHour;
|
||||
let startMinutes = guard.startMinutes;
|
||||
if (OH.startTime(maybeAdd) < OH.startTime(guard)) {
|
||||
startHour = maybeAdd.startHour;
|
||||
startMinutes = maybeAdd.startMinutes;
|
||||
}
|
||||
|
||||
let endHour = guard.endHour;
|
||||
let endMinutes = guard.endMinutes;
|
||||
if (OH.endTime(maybeAdd) > OH.endTime(guard)) {
|
||||
endHour = maybeAdd.endHour;
|
||||
endMinutes = maybeAdd.endMinutes;
|
||||
}
|
||||
|
||||
queue.push({
|
||||
startHour: startHour,
|
||||
startMinutes: startMinutes,
|
||||
endHour:endHour,
|
||||
endMinutes:endMinutes,
|
||||
weekday: guard.weekday
|
||||
});
|
||||
|
||||
doAddEntry = false;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (doAddEntry) {
|
||||
newList.push(maybeAdd);
|
||||
}
|
||||
}
|
||||
|
||||
// New list can only differ from the old list by merging entries
|
||||
// This means that the list is changed only if the lengths are different.
|
||||
// If the lengths are the same, we might just as well return the old list and be a bit more stable
|
||||
if (newList.length !== ohs.length) {
|
||||
return newList;
|
||||
} else {
|
||||
return ohs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static startTime(oh: OpeningHour): number {
|
||||
return oh.startHour + oh.startMinutes / 60;
|
||||
}
|
||||
|
||||
public static endTime(oh: OpeningHour): number {
|
||||
return oh.endHour + oh.endMinutes / 60;
|
||||
}
|
||||
|
||||
public static startTimeLiesInRange(checked: OpeningHour, mightLieIn: OpeningHour) {
|
||||
return OH.startTime(mightLieIn) <= OH.startTime(checked) &&
|
||||
OH.startTime(checked) <= OH.endTime(mightLieIn)
|
||||
}
|
||||
|
||||
public static endTimeLiesInRange(checked: OpeningHour, mightLieIn: OpeningHour) {
|
||||
return OH.startTime(mightLieIn) <= OH.endTime(checked) &&
|
||||
OH.endTime(checked) <= OH.endTime(mightLieIn)
|
||||
}
|
||||
|
||||
private static parseHHMM(hhmm: string): { hours: number, minutes: number } {
|
||||
if(hhmm === undefined || hhmm == null){
|
||||
return null;
|
||||
}
|
||||
const spl = hhmm.trim().split(":");
|
||||
if(spl.length != 2){
|
||||
return null;
|
||||
}
|
||||
return {hours: Number(spl[0].trim()), minutes: Number(spl[1].trim())};
|
||||
}
|
||||
|
||||
public static parseHHMMRange(hhmmhhmm: string): {
|
||||
startHour: number,
|
||||
startMinutes: number,
|
||||
endHour: number,
|
||||
endMinutes: number
|
||||
} {
|
||||
if (hhmmhhmm == "off") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timings = hhmmhhmm.split("-");
|
||||
const start = OH.parseHHMM(timings[0])
|
||||
const end = OH.parseHHMM(timings[1]);
|
||||
return {
|
||||
startHour: start.hours,
|
||||
startMinutes: start.minutes,
|
||||
endHour: end.hours,
|
||||
endMinutes: end.minutes
|
||||
}
|
||||
}
|
||||
|
||||
private static ParseHhmmRanges(hhmms: string): {
|
||||
startHour: number,
|
||||
startMinutes: number,
|
||||
endHour: number,
|
||||
endMinutes: number
|
||||
}[] {
|
||||
if (hhmms === "off") {
|
||||
return [];
|
||||
}
|
||||
return hhmms.split(",")
|
||||
.map(s => s.trim())
|
||||
.filter(str => str !== "")
|
||||
.map(OH.parseHHMMRange)
|
||||
.filter(v => v != null)
|
||||
}
|
||||
|
||||
private static ParseWeekday(weekday: string): number {
|
||||
return OH.daysIndexed[weekday.trim().toLowerCase()];
|
||||
}
|
||||
|
||||
private static ParseWeekdayRange(weekdays: string): number[] {
|
||||
const split = weekdays.split("-");
|
||||
if (split.length == 1) {
|
||||
const parsed = OH.ParseWeekday(weekdays);
|
||||
if(parsed == null){
|
||||
return null;
|
||||
}
|
||||
return [parsed];
|
||||
} else if (split.length == 2) {
|
||||
let start = OH.ParseWeekday(split[0]);
|
||||
let end = OH.ParseWeekday(split[1]);
|
||||
if ((start ?? null) === null || (end ?? null) === null) {
|
||||
return null;
|
||||
}
|
||||
let range = [];
|
||||
for (let i = start; i <= end; i++) {
|
||||
range.push(i);
|
||||
}
|
||||
return range;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static ParseWeekdayRanges(weekdays: string): number[] {
|
||||
let ranges = [];
|
||||
let split = weekdays.split(",");
|
||||
for (const weekday of split) {
|
||||
const parsed = OH.ParseWeekdayRange(weekday)
|
||||
if (parsed === undefined || parsed === null) {
|
||||
return null;
|
||||
}
|
||||
ranges.push(...parsed);
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private static multiply(weekdays: number[], timeranges: { startHour: number, startMinutes: number, endHour: number, endMinutes: number }[]) {
|
||||
if ((weekdays ?? null) == null || (timeranges ?? null) == null) {
|
||||
return null;
|
||||
}
|
||||
const ohs: OpeningHour[] = []
|
||||
for (const timerange of timeranges) {
|
||||
for (const weekday of weekdays) {
|
||||
ohs.push({
|
||||
weekday: weekday,
|
||||
startHour: timerange.startHour, startMinutes: timerange.startMinutes,
|
||||
endHour: timerange.endHour, endMinutes: timerange.endMinutes,
|
||||
});
|
||||
}
|
||||
}
|
||||
return ohs;
|
||||
}
|
||||
|
||||
public static ParseRule(rule: string): OpeningHour[] {
|
||||
try {
|
||||
if (rule.trim() == "24/7") {
|
||||
return OH.multiply([0, 1, 2, 3, 4, 5, 6], [{
|
||||
startHour: 0,
|
||||
startMinutes: 0,
|
||||
endHour: 24,
|
||||
endMinutes: 0
|
||||
}]);
|
||||
}
|
||||
|
||||
const split = rule.trim().replace(/, */g, ",").split(" ");
|
||||
if (split.length == 1) {
|
||||
// First, try to parse this rule as a rule without weekdays
|
||||
let timeranges = OH.ParseHhmmRanges(rule);
|
||||
let weekdays = [0, 1, 2, 3, 4, 5, 6];
|
||||
return OH.multiply(weekdays, timeranges);
|
||||
}
|
||||
|
||||
if (split.length == 2) {
|
||||
const weekdays = OH.ParseWeekdayRanges(split[0]);
|
||||
const timeranges = OH.ParseHhmmRanges(split[1]);
|
||||
return OH.multiply(weekdays, timeranges);
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.log("Could not parse weekday rule ", rule);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static Parse(rules: string) {
|
||||
if (rules === undefined || rules === "") {
|
||||
return []
|
||||
}
|
||||
|
||||
const ohs = []
|
||||
|
||||
const split = rules.split(";");
|
||||
|
||||
for (const rule of split) {
|
||||
if(rule === ""){
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const parsed = OH.ParseRule(rule)
|
||||
if (parsed !== null) {
|
||||
ohs.push(...parsed);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Could not parse ", rule, ": ", e)
|
||||
}
|
||||
}
|
||||
|
||||
return ohs;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
import {InputElement} from "../InputElement";
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import {UIElement} from "../../UIElement";
|
||||
import Combine from "../../Base/Combine";
|
||||
import {OH} from "../../../Logic/OpeningHours";
|
||||
import OpeningHoursPicker from "./OpeningHoursPicker";
|
||||
import {VariableUiElement} from "../../Base/VariableUIElement";
|
||||
import Translations from "../../i18n/Translations";
|
||||
import {FixedUiElement} from "../../Base/FixedUiElement";
|
||||
import PublicHolidayInput from "./PublicHolidayInput";
|
||||
|
||||
|
||||
/**
|
||||
* The full opening hours element, including the table, opening hours picker.
|
||||
* Keeps track of unparsed rules
|
||||
* Exports everything conventiently as a string, for direct use
|
||||
*/
|
||||
import OpeningHoursPicker from "./OpeningHoursPicker";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import Combine from "../Base/Combine";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import {OH} from "./OpeningHours";
|
||||
import {InputElement} from "../Input/InputElement";
|
||||
import PublicHolidayInput from "./PublicHolidayInput";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
||||
|
||||
export default class OpeningHoursInput extends InputElement<string> {
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import {UIElement} from "../../UIElement";
|
||||
import {InputElement} from "../InputElement";
|
||||
import {OpeningHour, OH} from "../../../Logic/OpeningHours";
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import OpeningHoursPickerTable from "./OpeningHoursPickerTable";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import OpeningHoursRange from "./OpeningHoursRange";
|
||||
import Combine from "../../Base/Combine";
|
||||
import Combine from "../Base/Combine";
|
||||
import OpeningHoursPickerTable from "./OpeningHoursPickerTable";
|
||||
import {OH, OpeningHour} from "./OpeningHours";
|
||||
import {InputElement} from "../Input/InputElement";
|
||||
|
||||
export default class OpeningHoursPicker extends InputElement<OpeningHour[]> {
|
||||
private readonly _ohs: UIEventSource<OpeningHour[]>;
|
|
@ -1,15 +1,14 @@
|
|||
import {InputElement} from "../InputElement";
|
||||
import {OpeningHour} from "../../../Logic/OpeningHours";
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import {Utils} from "../../../Utils";
|
||||
import {UIElement} from "../../UIElement";
|
||||
import Translations from "../../i18n/Translations";
|
||||
import {Browser} from "leaflet";
|
||||
|
||||
/**
|
||||
* This is the base-table which is selectable by hovering over it.
|
||||
* It will genarate the currently selected opening hour.
|
||||
*/
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import {Utils} from "../../Utils";
|
||||
import {OpeningHour} from "./OpeningHours";
|
||||
import {InputElement} from "../Input/InputElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
||||
export default class OpeningHoursPickerTable extends InputElement<OpeningHour[]> {
|
||||
public readonly IsSelected: UIEventSource<boolean>;
|
||||
private readonly weekdays: UIEventSource<UIElement[]>;
|
|
@ -1,15 +1,14 @@
|
|||
import {UIElement} from "../../UIElement";
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import {OH, OpeningHour} from "../../../Logic/OpeningHours";
|
||||
import Combine from "../../Base/Combine";
|
||||
import {Utils} from "../../../Utils";
|
||||
import {FixedUiElement} from "../../Base/FixedUiElement";
|
||||
import {VariableUiElement} from "../../Base/VariableUIElement";
|
||||
import Svg from "../../../Svg";
|
||||
|
||||
/**
|
||||
* A single opening hours range, shown on top of the OH-picker table
|
||||
*/
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import Svg from "../../Svg";
|
||||
import {Utils} from "../../Utils";
|
||||
import Combine from "../Base/Combine";
|
||||
import {OH, OpeningHour} from "./OpeningHours";
|
||||
|
||||
export default class OpeningHoursRange extends UIElement {
|
||||
private _oh: UIEventSource<OpeningHour>;
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import {InputElement} from "../InputElement";
|
||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||
import {UIElement} from "../../UIElement";
|
||||
import {DropDown} from "../DropDown";
|
||||
import Translations from "../../i18n/Translations";
|
||||
import Combine from "../../Base/Combine";
|
||||
import {TextField} from "../TextField";
|
||||
import {OH} from "../../../Logic/OpeningHours";
|
||||
|
||||
import {OH} from "./OpeningHours";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import Combine from "../Base/Combine";
|
||||
import {TextField} from "../Input/TextField";
|
||||
import {DropDown} from "../Input/DropDown";
|
||||
import {InputElement} from "../Input/InputElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
||||
export default class PublicHolidayInput extends InputElement<string> {
|
||||
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
136
UI/PersonalLayersPanel.ts
Normal file
136
UI/PersonalLayersPanel.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
import {UIElement} from "../UI/UIElement";
|
||||
import State from "../State";
|
||||
import Translations from "../UI/i18n/Translations";
|
||||
import {UIEventSource} from "./UIEventSource";
|
||||
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
|
||||
import Combine from "../UI/Base/Combine";
|
||||
import CheckBox from "../UI/Input/CheckBox";
|
||||
import * as personal from "../assets/themes/personalLayout/personalLayout.json";
|
||||
import {SubtleButton} from "../UI/Base/SubtleButton";
|
||||
import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
||||
import {Img} from "../UI/Img";
|
||||
import Svg from "../Svg";
|
||||
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
|
||||
|
||||
export class PersonalLayersPanel extends UIElement {
|
||||
private checkboxes: UIElement[] = [];
|
||||
|
||||
constructor() {
|
||||
super(State.state.favouriteLayers);
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
|
||||
this.UpdateView([]);
|
||||
const self = this;
|
||||
State.state.installedThemes.addCallback(extraThemes => {
|
||||
self.UpdateView(extraThemes.map(layout => layout.layout.layoutConfig));
|
||||
self.Update();
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
private UpdateView(extraThemes: LayoutConfig[]) {
|
||||
this.checkboxes = [];
|
||||
const favs = State.state.favouriteLayers.data ?? [];
|
||||
const controls = new Map<string, UIEventSource<boolean>>();
|
||||
const allLayouts = AllKnownLayouts.layoutsList.concat(extraThemes);
|
||||
for (const layout of allLayouts) {
|
||||
if (layout.id === personal.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const header =
|
||||
new Combine([
|
||||
`<img style="max-width: 3em;max-height: 3em; float: left; padding: 0.1em; margin-right: 0.3em;" src='${layout.icon}'>`,
|
||||
"<b>",
|
||||
layout.title,
|
||||
"</b><br/>",
|
||||
layout.shortDescription ?? ""
|
||||
]).SetStyle("background: #eee; display: block; padding: 0.5em; border-radius:0.5em; overflow:auto;")
|
||||
this.checkboxes.push(header);
|
||||
|
||||
for (const layer of layout.layers) {
|
||||
if(layer === undefined){
|
||||
console.warn("Undefined layer for ",layout.id)
|
||||
continue;
|
||||
}
|
||||
if (typeof layer === "string") {
|
||||
continue;
|
||||
}
|
||||
let icon = layer.icon ?? Img.AsData(Svg.checkmark);
|
||||
let iconUnset = layer.icon ?? "";
|
||||
if (layer.icon !== undefined && typeof (layer.icon) !== "string") {
|
||||
icon = layer.icon.GetRenderValue({"id": "node/-123456"}).txt ?? Img.AsData(Svg.checkmark)
|
||||
iconUnset = icon;
|
||||
}
|
||||
|
||||
let name = layer.name ?? layer.id;
|
||||
if (name === undefined) {
|
||||
continue;
|
||||
}
|
||||
const content = new Combine([
|
||||
"<b>",
|
||||
name,
|
||||
"</b> ",
|
||||
layer.description !== undefined ? new Combine(["<br/>", layer.description]) : "",
|
||||
])
|
||||
|
||||
const iconImage = `<img src="${icon}">`;
|
||||
const iconUnsetImage = `<img src="${iconUnset}">`
|
||||
|
||||
const cb = new CheckBox(
|
||||
new SubtleButton(
|
||||
new FixedUiElement(iconImage).SetStyle(""),
|
||||
content),
|
||||
new SubtleButton(
|
||||
new FixedUiElement(iconUnsetImage).SetStyle("opacity:0.1;"),
|
||||
new Combine(["<del>",
|
||||
content,
|
||||
"</del>"
|
||||
])),
|
||||
controls[layer.id] ?? (favs.indexOf(layer.id) >= 0)
|
||||
);
|
||||
cb.SetClass("custom-layer-checkbox");
|
||||
controls[layer.id] = cb.isEnabled;
|
||||
|
||||
cb.isEnabled.addCallback((isEnabled) => {
|
||||
const favs = State.state.favouriteLayers;
|
||||
if (isEnabled) {
|
||||
if(favs.data.indexOf(layer.id)>= 0){
|
||||
return; // Already added
|
||||
}
|
||||
favs.data.push(layer.id);
|
||||
} else {
|
||||
favs.data.splice(favs.data.indexOf(layer.id), 1);
|
||||
}
|
||||
favs.ping();
|
||||
})
|
||||
|
||||
this.checkboxes.push(cb);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
State.state.favouriteLayers.addCallback((layers) => {
|
||||
for (const layerId of layers) {
|
||||
controls[layerId]?.setData(true);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
const t = Translations.t.favourite;
|
||||
const userDetails = State.state.osmConnection.userDetails.data;
|
||||
if(!userDetails.loggedIn){
|
||||
return t.loginNeeded.Render();
|
||||
}
|
||||
|
||||
return new Combine([
|
||||
t.panelIntro,
|
||||
...this.checkboxes
|
||||
]).Render();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import {UIElement} from "./UIElement";
|
||||
import OpeningHoursVisualization from "./OhVisualization";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
|
||||
|
@ -16,6 +15,7 @@ import ReviewElement from "./Reviews/ReviewElement";
|
|||
import MangroveReviews from "../Logic/Web/MangroveReviews";
|
||||
import Translations from "./i18n/Translations";
|
||||
import ReviewForm from "./Reviews/ReviewForm";
|
||||
import OpeningHoursVisualization from "./OpeningHours/OhVisualization";
|
||||
|
||||
export class SubstitutedTranslation extends UIElement {
|
||||
private readonly tags: UIEventSource<any>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue