More work on OpeningHours picker

This commit is contained in:
Pieter Vander Vennet 2020-09-30 22:22:58 +02:00
parent 4cce18f818
commit 0714327d66
11 changed files with 397 additions and 25 deletions

View file

@ -88,13 +88,13 @@ export class ChangesetHandler {
layout : Layout,
continuation: (changesetId: string) => void) {
const commentExtra = layout.changesetMessage !== undefined? " - "+layout.changesetMessage : "";
const commentExtra = layout.changesetMessage !== undefined ? " - " + layout.changesetMessage : "";
let surveySource = "";
if(State.state.currentGPSLocation.data !== undefined){
if (State.state.currentGPSLocation.data !== undefined) {
surveySource = '<tag k="source" v="survey"/>'
}
this.auth.xhr({
method: 'PUT',
path: '/api/0.6/changeset/create',

View file

@ -1,4 +1,5 @@
import * as $ from "jquery"
import {Utils} from "../../Utils";
export abstract class OsmObject {
@ -40,16 +41,6 @@ export abstract class OsmObject {
abstract SaveExtraData(element);
/**
* Replaces all '"' (double quotes) by '&quot;'
* Bugfix where names containing '"' were not uploaded, such as '"Het Zwin" nature reserve'
* @param string
* @constructor
*/
private static Escape(string: string) {
return string.replace(/"/g, '&quot;')
.replace(/&/g, "&amp;");
}
/**
* Generates the changeset-XML for tags
@ -60,7 +51,7 @@ export abstract class OsmObject {
for (const key in this.tags) {
const v = this.tags[key];
if (v !== "") {
tags += ' <tag k="' + OsmObject.Escape(key) + '" v="' + OsmObject.Escape(this.tags[key]) + '"/>\n'
tags += ' <tag k="' + Utils.EncodeXmlValue(key) + '" v="' + Utils.EncodeXmlValue(this.tags[key]) + '"/>\n'
}
}
return tags;

209
UI/Input/OpeningHours.ts Normal file
View file

@ -0,0 +1,209 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Utils} from "../../Utils";
export interface OpeningHour {
weekdayStart: number, // 0 is monday, 1 is tuesday, ...
weekdayEnd: number,
startHour: number,
startMinutes: number,
endHour: number,
endMinutes: number
}
export default class OpeningHours extends InputElement<OpeningHour[]> {
public readonly IsSelected: UIEventSource<boolean>;
public static readonly days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
private readonly source: UIEventSource<OpeningHour[]>;
constructor(source: UIEventSource<OpeningHour[]> = undefined) {
super();
this.source = source ?? new UIEventSource<OpeningHour[]>([]);
this.IsSelected = new UIEventSource<boolean>(false);
}
InnerRender(): string {
let rows = "";
for (let h = 0; h < 24; h++) {
let hs = "" + h;
if (hs.length == 1) {
hs = "0" + hs;
}
for (let m = 0; m < 60; m += 60) {
let min = "" + m;
const style = "width:0.5em;font-size:small;";
if (m === 0) {
min = "00";
}
rows += `<tr><td class="oh-left-col" rowspan="4" style="${style}">${hs}:${min}</td>` +
Utils.Times('<td class="oh-timecell oh-timecell-full"></td>', 7) +
'</tr><tr>' +
Utils.Times('<td class="oh-timecell"></td>', 7) +
'</tr><tr>' +
Utils.Times('<td class="oh-timecell oh-timecell-half"></td>', 7) +
'</tr><tr>' +
Utils.Times('<td class="oh-timecell"></td>', 7) +
'</tr>';
}
}
let days = OpeningHours.days.join("</th><th>");
return `<table id="oh-table-${this.id}" class="oh-table"><tr><th></th><th>${days}</tr>${rows}</table>`;
}
protected InnerUpdate() {
const self = this;
const table = (document.getElementById(`oh-table-${this.id}`) as HTMLTableElement);
if (table === undefined || table === null) {
return;
}
let mouseIsDown = false;
let selectionStart: [number, number] = undefined;
let selectionEnd: [number, number] = undefined;
function h(timeSegment: number) {
return Math.floor(timeSegment / 4);
}
function m(timeSegment: number) {
return (timeSegment % 4) * 15;
}
function hhmm(timeSegment: number) {
return h(timeSegment) + ":" + m(timeSegment)
}
function startSelection(i: number, j: number, cell: HTMLElement) {
mouseIsDown = true;
selectionStart = [i, j];
selectionEnd = [i, j];
cell.classList.add("oh-timecell-selected")
}
function endSelection() {
if (selectionStart === undefined) {
return;
}
mouseIsDown = false
const dStart = Math.min(selectionStart[1], selectionEnd[1]);
const dEnd = Math.max(selectionStart[1], selectionEnd[1]);
const timeStart = Math.min(selectionStart[0], selectionEnd[0]) - 1;
const timeEnd = Math.max(selectionStart[0], selectionEnd[0]) - 1;
console.log("Selected from day", OpeningHours.days[dStart], "at",
hhmm(timeStart), "till", OpeningHours.days[dEnd], "at", hhmm(timeEnd + 1)
)
const oh: OpeningHour = {
weekdayStart: dStart,
weekdayEnd: dEnd,
startHour: h(timeStart),
startMinutes: m(timeStart),
endHour: h(timeEnd + 1),
endMinutes: m(timeEnd + 1)
}
self.source.data.push(oh);
self.source.ping();
}
table.onmouseup = () => {
endSelection();
};
table.onmouseleave = () => {
endSelection();
};
function selectAllBetween(iEnd, jEnd) {
let iStart = selectionStart[0];
let jStart = selectionStart[1];
if (iStart > iEnd) {
const h = iStart;
iStart = iEnd;
iEnd = h;
}
if (jStart > jEnd) {
const h = jStart;
jStart = jEnd;
jEnd = h;
}
for (let i = 1; i < table.rows.length; i++) {
let row = table.rows[i]
for (let j = 0; j < row.cells.length; j++) {
let cell = row.cells[j]
let offset = 0;
if (i % 4 == 1) {
if (j == 0) {
continue;
}
offset = -1;
}
if (iStart <= i && i <= iEnd &&
jStart <= j + offset && j + offset <= jEnd) {
cell.classList.add("oh-timecell-selected")
} else {
cell.classList.remove("oh-timecell-selected")
}
}
}
}
for (let i = 1; i < table.rows.length; i++) {
let row = table.rows[i]
for (let j = 0; j < row.cells.length; j++) {
let cell = row.cells[j]
let offset = 0;
if (i % 4 == 1) {
if (j == 0) {
continue;
}
offset = -1;
}
cell.onmousedown = (ev) => {
ev.preventDefault();
startSelection(i, j + offset, cell)
}
cell.ontouchstart = (ev) => {
ev.preventDefault();
startSelection(i, j + offset, cell)
}
cell.onmouseenter = () => {
if (mouseIsDown) {
selectionEnd = [i, j + offset];
selectAllBetween(i, j + offset)
}
}
cell.ontouchmove = (ev) => {
ev.preventDefault();
selectionEnd = [i, j + offset];
selectAllBetween(i, j + offset)
}
cell.ontouchend = (ev) => {
ev.preventDefault();
selectionEnd = [i, j + offset];
selectAllBetween(i, j + offset)
}
}
}
}
IsValid(t: OpeningHour[]): boolean {
return true;
}
GetValue(): UIEventSource<OpeningHour[]> {
return this.source;
}
}

View file

@ -2,6 +2,15 @@ import {UIElement} from "./UI/UIElement";
export class Utils {
static EncodeXmlValue(str) {
return str.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
}
/**
* Gives a clean float, or undefined if parsing fails
* @param str
@ -16,9 +25,17 @@ export class Utils {
}
return undefined;
}
public static Upper(str : string){
return str.substr(0,1).toUpperCase() + str.substr(1);
public static Upper(str: string) {
return str.substr(0, 1).toUpperCase() + str.substr(1);
}
public static Times(str: string, count: number): string {
let res = "";
for (let i = 0; i < count; i++) {
res += str;
}
return res;
}
static DoEvery(millis: number, f: (() => void)) {

View file

@ -13,11 +13,11 @@
"nl": "Een <b>Witte Fiets</b> of <b>Spookfiets</b> is een aandenken aan een fietser die bij een verkeersongeval om het leven kwam. Het gaat om een fiets die volledig wit is geschilderd en in de buurt van het ongeval werd geinstalleerd.<br/><br/>Op deze kaart zie je alle witte fietsen die door OpenStreetMap gekend zijn. Ontbreekt er een Witte Fiets of wens je informatie aan te passen? Meld je dan aan met een (gratis) OpenStreetMap account.",
"de": "Ein <b>Geisterrad</b> ist ein Denkmal für einen Radfahrer, der bei einem Verkehrsunfall ums Leben kam, in Form eines weißen Fahrrades, das dauerhaft in der Nähe des Unfallortes aufgestellt ist.<br/><br/> Auf dieser Karte kann man alle Geisterräder sehen, die OpenStreetMap kennt. Fehlt ein Geisterrad? Jeder kann hier Informationen hinzufügen oder aktualisieren - Sie benötigen lediglich einen (kostenlosen) OpenStreetMap-Account."
},
"icon": "./assets/layers/ghost_bike/ghost_bike.svg",
"icon": "./assets/themes/ghost_bike/logo.svg",
"startZoom": 1,
"startLat": 0,
"startLon": 0,
"widenFactor": 0.1,
"layers": ["ghost_bike"],
"defaultBackgroundId": "CartoDB.Positron"
}
}

View file

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg69"
version="1.1"
fill="none"
viewBox="0 0 98 122"
height="122"
width="122"
sodipodi:docname="logo.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1680"
inkscape:window-height="1013"
id="namedview16"
showgrid="false"
inkscape:zoom="1.9344262"
inkscape:cx="61"
inkscape:cy="61"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg69" />
<defs
id="defs17" />
<metadata
id="metadata73">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer3"
transform="translate(0,3.1016949)">
<g
id="g875">
<path
style="fill:#171615"
d="m 53.0445,112.094 c -1.7831,3.887 -7.3059,3.887 -9.089,0 L 13.2124,45.085 C 11.6928,41.7729 14.1129,38 17.7569,38 h 61.4862 c 3.644,0 6.0641,3.7729 4.5445,7.085 z"
id="path2"
inkscape:connector-curvature="0" />
<circle
style="fill:#171615"
cx="49"
cy="49"
r="49"
id="circle4" />
<path
style="stroke:#ffffff;stroke-width:2"
id="path10"
d="M 45.0763,63.8434 H 29.7114 l 8.3809,-18.5979 h 8.3808 6.0529 6.5184 m 0,0 L 56.7164,40 h 3.2592 3.2592 m -4.1904,5.2455 0.9312,2.3844 1.8624,3.8149 5.1216,12.3986 m -7.9152,-18.5979 -4.4232,7.153 -2.654,4.2918 m -1.7693,2.8613 1.7693,-2.8613 M 41.3515,50.4911 46.0075,60.9822 36.6955,40 l 2.7936,5.7224 m 9.312,16.2135 3.1661,-5.2456"
inkscape:connector-curvature="0" />
<path
style="stroke:#ffffff"
id="path12"
d="m 50.0922,63.032 c 0,1.5788 -1.2466,2.8381 -2.7593,2.8381 -1.5126,0 -2.7592,-1.2593 -2.7592,-2.8381 0,-1.5787 1.2466,-2.838 2.7592,-2.838 1.5127,0 2.7593,1.2593 2.7593,2.838 z"
inkscape:connector-curvature="0" />
<path
style="stroke:#ffffff;stroke-width:2"
id="path14"
d="M 40.2801,62.0784 C 40.2801,68.1329 35.494,73 29.6401,73 23.7861,73 19,68.1329 19,62.0784 c 0,-6.0546 4.7861,-10.9217 10.6401,-10.9217 5.8539,0 10.64,4.8671 10.64,10.9217 z"
inkscape:connector-curvature="0" />
<g
id="g20">
<circle
cx="66"
cy="63"
r="11"
id="circle18"
style="stroke:#ffffff;stroke-width:2" />
</g>
<g
id="g8">
<path
d="M 56.4468,12.3002 H 49.8793 V 6.79007 C 49.8793,6.35374 49.5103,6 49.0552,6 H 47.2158 C 46.7606,6 46.3916,6.35374 46.3916,6.79007 V 12.3002 H 39.8242 C 39.369,12.3002 39,12.654 39,13.0903 v 1.7633 c 0,0.4364 0.369,0.7901 0.8242,0.7901 h 6.5674 v 16.5662 c 0,0.4364 0.369,0.7901 0.8242,0.7901 h 1.8394 c 0.4551,0 0.8241,-0.3537 0.8241,-0.7901 V 15.6437 h 6.5675 c 0.4552,0 0.8242,-0.3537 0.8242,-0.7901 v -1.7633 c 0,-0.4363 -0.369,-0.7901 -0.8242,-0.7901 z"
id="path6"
inkscape:connector-curvature="0"
style="fill:#fffcfc" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

43
css/openinghourstable.css Normal file
View file

@ -0,0 +1,43 @@
.oh-table {
width: 15em;
border-collapse: collapse;
}
.oh-table td {
vertical-align: top;
}
.oh-timecell:hover {
background-color: lightsalmon !important;
}
.oh-timecell {
background-color: white;
}
.oh-timecell-selected {
background-color: red;
}
.oh-timecell-half {
border-top: 0.5px solid #eee
}
.oh-timecell-half.oh-timecell-selected {
border-top: 0.5px solid lightsalmon;
}
.oh-timecell-full {
border-top: 1px solid #aaa
}
.oh-timecell-full.oh-timecell-selected {
border-top: 1px solid lightsalmon;
}
.oh-left-col {
/*border-top: 1px solid #aaa;*/
}

View file

@ -71,7 +71,6 @@ body {
.invalid {
box-shadow: 0 0 10px #ff5353;
display: block;
height: min-content;
}

View file

@ -11,6 +11,7 @@
<link rel="stylesheet" href="./css/tabbedComponent.css"/>
<link rel="stylesheet" href="./css/slideshow.css"/>
<link rel="stylesheet" href="./css/mobile.css"/>
<link rel="stylesheet" href="./css/openinghourstable.css"/>
<link rel="manifest" href="./manifest.manifest">
<link rel="icon" href="assets/add.svg" sizes="any" type="image/svg+xml">
@ -63,8 +64,7 @@
<script src="./index.ts"></script>
<script src="./vendor/Leaflet.AccuratePosition.js"></script>
<script data-goatcounter="https://pietervdvn.goatcounter.com/count"
async src="//gc.zgo.at/count.js"></script>
<script data-goatcounter="https://pietervdvn.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
</body>
</html>

View file

@ -5,6 +5,7 @@
<link href="index.css" rel="stylesheet"/>
<link href="css/slideshow.css" rel="stylesheet"/>
<link href="css/tabbedComponent.css" rel="stylesheet"/>
<link href="css/openinghourstable.css" rel="stylesheet"/>
<style>
.tag-input-row {
display: block ruby;

14
test.ts
View file

@ -1,3 +1,13 @@
import {Basemap} from "./Logic/Leaflet/Basemap";
import OpeningHours, {OpeningHour} from "./UI/Input/OpeningHours";
import {VariableUiElement} from "./UI/Base/VariableUIElement";
console.log(Basemap.ProvidedLayer("Stamen.Toner"))
let oh = new OpeningHours();
oh.AttachTo('maindiv');
oh.GetValue().addCallback(data => console.log(data))
new VariableUiElement(oh.GetValue().map(ohs => {
return ohs.map((oh: OpeningHour) => oh.weekdayStart + " " + oh.startHour + ":" + oh.startMinutes + " --> " +
oh.weekdayEnd + " " + oh.endHour + ":" + oh.endMinutes).join(",")
})).AttachTo("extradiv");