From c6c65f9e8273a5bc1b2ac1155d66003d2b98591c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Mon, 4 Oct 2021 17:40:59 +0200 Subject: {.js => .ts} on relavant files. --- static/clock.js | 98 ----------- static/clock.ts | 98 +++++++++++ static/dragable.js | 43 ----- static/dragable.ts | 43 +++++ static/globals.js | 337 -------------------------------------- static/globals.ts | 337 ++++++++++++++++++++++++++++++++++++++ static/jcal.js | 174 -------------------- static/jcal.ts | 174 ++++++++++++++++++++ static/lib.js | 205 ----------------------- static/lib.ts | 205 +++++++++++++++++++++++ static/popup.js | 105 ------------ static/popup.ts | 105 ++++++++++++ static/rrule.js | 100 ------------ static/rrule.ts | 100 ++++++++++++ static/script.js | 414 ----------------------------------------------- static/script.ts | 414 +++++++++++++++++++++++++++++++++++++++++++++++ static/server_connect.js | 108 ------------- static/server_connect.ts | 108 +++++++++++++ static/types.js | 109 ------------- static/types.ts | 109 +++++++++++++ static/vevent.js | 214 ------------------------ static/vevent.ts | 214 ++++++++++++++++++++++++ 22 files changed, 1907 insertions(+), 1907 deletions(-) delete mode 100644 static/clock.js create mode 100644 static/clock.ts delete mode 100644 static/dragable.js create mode 100644 static/dragable.ts delete mode 100644 static/globals.js create mode 100644 static/globals.ts delete mode 100644 static/jcal.js create mode 100644 static/jcal.ts delete mode 100644 static/lib.js create mode 100644 static/lib.ts delete mode 100644 static/popup.js create mode 100644 static/popup.ts delete mode 100644 static/rrule.js create mode 100644 static/rrule.ts delete mode 100644 static/script.js create mode 100644 static/script.ts delete mode 100644 static/server_connect.js create mode 100644 static/server_connect.ts delete mode 100644 static/types.js create mode 100644 static/types.ts delete mode 100644 static/vevent.js create mode 100644 static/vevent.ts diff --git a/static/clock.js b/static/clock.js deleted file mode 100644 index d33d603a..00000000 --- a/static/clock.js +++ /dev/null @@ -1,98 +0,0 @@ - -class Clock { - update(now) { - } -} - - -class Timebar extends Clock { - - constructor(start_time, end_time) { - super(); - this.start_time = start_time; - this.end_time = end_time; - this.bar_object = false - } - - - update(now) { - // if (! (this.start_time <= now.getTime() && now.getTime() < this.end_time)) - // return; - - var event_area = document.getElementById(now.format("~Y-~m-~d")) - - if (event_area) { - if (this.bar_object) { - this.bar_object.parentNode.removeChild(this.bar_object) - } else { - this.bar_object = makeElement ('div', { - id: 'bar', - className: 'eventlike current-time', - }); - } - - this.bar_object.style.top = date_to_percent(now) + "%"; - event_area.append(this.bar_object) - } - } -} - -class SmallcalCellHighlight extends Clock { - constructor(small_cal) { - super(); - this.small_cal = small_cal; - this.current_cell = false - } - - update(now) { - if (this.current_cell) { - this.current_cell.style.border = ""; - } - - /* This is expeced to fail if the current date is not - currently on screen. */ - this.current_cell = this.small_cal.querySelector( - "time[datetime='" + now.format("~Y-~m-~d") + "']"); - - if (this.current_cell) { - this.current_cell.style.border = "1px solid black"; - } - } -} - -/* -------------------------------------------------- */ - -class ClockElement extends HTMLElement { - constructor () { - super(); - } - - connectedCallback () { - let interval = this.hasAttribute('interval') ? +this.getAttribute('img') : 60; - interval *= 1000 /* ms */ - - this.timer_id = window.setInterval(() => this.update(new Date), interval) - this.update(new Date) - } - - static get observedAttributes () { - return ['timer_id'] - } - - update (now) { /* noop */ } -} - -class TodayButton extends ClockElement { - update (now) { - this.querySelector('a').href = now.format("~Y-~m-~d.html") - } -} -customElements.define('today-button', TodayButton) - - -class CurrentTime extends ClockElement { - update (now) { - this.innerHTML = now.format('~H:~M:~S') - } -} -customElements.define('current-time', CurrentTime) diff --git a/static/clock.ts b/static/clock.ts new file mode 100644 index 00000000..d33d603a --- /dev/null +++ b/static/clock.ts @@ -0,0 +1,98 @@ + +class Clock { + update(now) { + } +} + + +class Timebar extends Clock { + + constructor(start_time, end_time) { + super(); + this.start_time = start_time; + this.end_time = end_time; + this.bar_object = false + } + + + update(now) { + // if (! (this.start_time <= now.getTime() && now.getTime() < this.end_time)) + // return; + + var event_area = document.getElementById(now.format("~Y-~m-~d")) + + if (event_area) { + if (this.bar_object) { + this.bar_object.parentNode.removeChild(this.bar_object) + } else { + this.bar_object = makeElement ('div', { + id: 'bar', + className: 'eventlike current-time', + }); + } + + this.bar_object.style.top = date_to_percent(now) + "%"; + event_area.append(this.bar_object) + } + } +} + +class SmallcalCellHighlight extends Clock { + constructor(small_cal) { + super(); + this.small_cal = small_cal; + this.current_cell = false + } + + update(now) { + if (this.current_cell) { + this.current_cell.style.border = ""; + } + + /* This is expeced to fail if the current date is not + currently on screen. */ + this.current_cell = this.small_cal.querySelector( + "time[datetime='" + now.format("~Y-~m-~d") + "']"); + + if (this.current_cell) { + this.current_cell.style.border = "1px solid black"; + } + } +} + +/* -------------------------------------------------- */ + +class ClockElement extends HTMLElement { + constructor () { + super(); + } + + connectedCallback () { + let interval = this.hasAttribute('interval') ? +this.getAttribute('img') : 60; + interval *= 1000 /* ms */ + + this.timer_id = window.setInterval(() => this.update(new Date), interval) + this.update(new Date) + } + + static get observedAttributes () { + return ['timer_id'] + } + + update (now) { /* noop */ } +} + +class TodayButton extends ClockElement { + update (now) { + this.querySelector('a').href = now.format("~Y-~m-~d.html") + } +} +customElements.define('today-button', TodayButton) + + +class CurrentTime extends ClockElement { + update (now) { + this.innerHTML = now.format('~H:~M:~S') + } +} +customElements.define('current-time', CurrentTime) diff --git a/static/dragable.js b/static/dragable.js deleted file mode 100644 index 6eb0b999..00000000 --- a/static/dragable.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - Apply to a given component to make it draggable. - Drag area (usually a title bar) should be be the only argument. - It is REQUIRED that the object which should be moved have the class - 'popup-container'; -*/ - - -/* - Given the navbar of a popup, make it dragable. - */ -function bind_popup_control (nav) { - - if (! nav.closest('.popup-container')) { - throw TypeError('not a popup container'); - } - - nav.onmousedown = function (e) { - /* Ignore mousedown on children */ - if (e.target != nav) return; - nav.style.cursor = "grabbing"; - nav.dataset.grabbed = "true"; - nav.dataset.grabPoint = e.clientX + ";" + e.clientY; - // let popup = nav.closest(".popup-container"); - let popup = nav.closest("popup-element"); - nav.dataset.startPoint = popup.offsetLeft + ";" + popup.offsetTop; - } - window.addEventListener('mousemove', function (e) { - if (nav.dataset.grabbed) { - let [x, y] = nav.dataset.grabPoint.split(";").map(Number); - let [startX, startY] = nav.dataset.startPoint.split(";").map(Number); - // let popup = nav.closest(".popup-container"); - let popup = nav.closest("popup-element"); - - popup.style.left = startX + (e.clientX - x) + "px"; - popup.style.top = startY + (e.clientY - y) + "px"; - } - }); - window.addEventListener('mouseup', function () { - nav.dataset.grabbed = ""; - nav.style.cursor = ""; - }); -} diff --git a/static/dragable.ts b/static/dragable.ts new file mode 100644 index 00000000..6eb0b999 --- /dev/null +++ b/static/dragable.ts @@ -0,0 +1,43 @@ +/* + Apply to a given component to make it draggable. + Drag area (usually a title bar) should be be the only argument. + It is REQUIRED that the object which should be moved have the class + 'popup-container'; +*/ + + +/* + Given the navbar of a popup, make it dragable. + */ +function bind_popup_control (nav) { + + if (! nav.closest('.popup-container')) { + throw TypeError('not a popup container'); + } + + nav.onmousedown = function (e) { + /* Ignore mousedown on children */ + if (e.target != nav) return; + nav.style.cursor = "grabbing"; + nav.dataset.grabbed = "true"; + nav.dataset.grabPoint = e.clientX + ";" + e.clientY; + // let popup = nav.closest(".popup-container"); + let popup = nav.closest("popup-element"); + nav.dataset.startPoint = popup.offsetLeft + ";" + popup.offsetTop; + } + window.addEventListener('mousemove', function (e) { + if (nav.dataset.grabbed) { + let [x, y] = nav.dataset.grabPoint.split(";").map(Number); + let [startX, startY] = nav.dataset.startPoint.split(";").map(Number); + // let popup = nav.closest(".popup-container"); + let popup = nav.closest("popup-element"); + + popup.style.left = startX + (e.clientX - x) + "px"; + popup.style.top = startY + (e.clientY - y) + "px"; + } + }); + window.addEventListener('mouseup', function () { + nav.dataset.grabbed = ""; + nav.style.cursor = ""; + }); +} diff --git a/static/globals.js b/static/globals.js deleted file mode 100644 index 86368e9a..00000000 --- a/static/globals.js +++ /dev/null @@ -1,337 +0,0 @@ -"use strict"; - -const vcal_objects = {}; - -class ComponentVEvent extends HTMLElement { - constructor () { - super (); - this.template = document.getElementById(this.tagName); - - let uid; - if ((uid = this.dataset.uid)) { - vcal_objects[uid].register(this); - } - - /* We DON'T have a redraw here in the general case, since the - HTML rendered server-side should be fine enough for us. - Those that need a direct rerendering (such as the edit tabs) - should take care of that some other way */ - } - - connectedCallback () { - let uid; - if ((uid = this.dataset.uid)) { - this.redraw (vcal_objects[uid]); - } - } - - redraw (data) { - // update ourselves from template - - if (! this.template) { - throw "Something"; - } - - let body = this.template.content.cloneNode(true).firstElementChild; - - for (let el of body.getElementsByClassName("bind")) { - let p = el.dataset.property; - let d, fmt; - if ((d = data.getProperty(p))) { - if ((fmt = el.dataset.fmt)) { - el.innerHTML = d.format(fmt); - } else { - el.innerHTML = d; - } - } - } - - this.replaceChildren(body); - } - -} - -class ComponentDescription extends ComponentVEvent { - constructor () { - super() ; - } - -} - -class ComponentEdit extends ComponentVEvent { - constructor () { - super(); - - this.firstTime = true; - } - - connectedCallback() { - - /* Edit tab is rendered here. It's left blank server-side, since - it only makes sense to have something here if we have javascript */ - - let data = vcal_objects[this.dataset.uid] - - if (! data) { - throw `Data missing for uid ${this.dataset.uid}.` - } - - this.redraw(data); - - for (let el of this.getElementsByClassName("interactive")) { - el.addEventListener('input', () => { - vcal_objects[this.dataset.uid].setProperty( - el.dataset.property, - el.value) - }); - } - } - - redraw (data) { - // update ourselves from template - - if (! this.template) { - throw "Something"; - } - - let body; - if (this.firstTime) { - body = this.template.content.cloneNode(true).firstElementChild; - } else { - body = this; - } - - for (let el of body.getElementsByClassName("interactive")) { - let p = el.dataset.property; - let d; - if ((d = data.getProperty(p))) { - /* - https://stackoverflow.com/questions/57157830/how-can-i-specify-the-sequence-of-running-nested-web-components-constructors - */ - window.setTimeout (() => { - /* NOTE Some specific types might require special formatting - here. But due to my custom components implementing custom - `.value' procedures, we might not need any special cases - here */ - el.value = d; - }); - } - } - - if (this.firstTime) { - this.replaceChildren(body); - this.firstTime = false; - } - } - -} - -function find_popup (uid) { - // for (let el of vcal_objects[uid].registered) { - // if (el.tagName === 'popup-element') { - // return el; - // } - // } - // throw 'Popup not fonud'; - return document.querySelector(`popup-element[data-uid="${uid}"]`) -} - -function find_block (uid) { - for (let el of vcal_objects[uid].registered) { - if (el.tagName === 'vevent-block') { - return el; - } - } - throw 'Popup not fonud'; -} - -class ComponentBlock extends ComponentVEvent { - constructor () { - super(); - - this.addEventListener('click', () => { - toggle_popup(find_popup(this.dataset.uid)); - }); - } - - redraw (data) { - super.redraw(data); - - let p; - if ((p = data.getProperty('dtstart'))) { - this.style.top = date_to_percent(to_local(p), 1) + "%"; - // console.log('dtstart', p); - } - if ((p = data.getProperty('dtend'))) { - this.style.height = 'unset'; - // console.log('dtend', p); - this.style.bottom = (100 - date_to_percent(to_local(p), 1)) + "%"; - } - } -} - -window.addEventListener('load', function () { - - // let json_objects_el = document.getElementById('json-objects'); - let div = document.getElementById('xcal-data'); - let vevents = div.firstElementChild.childNodes; - - for (let vevent of vevents) { - let ev = xml_to_vcal(vevent); - vcal_objects[ev.getProperty('uid')] = ev - } - - /* - - .popup - - .block - - .list - */ - /* - let vevent_els = document.getElementsByClassName('vevent') - for (let el of vevent_els) { - try { - vcal_objects[el.dataset.uid].register(el); - } catch { - console.error("Invalid something, uid = ", el.dataset.uid, - "el = ", el - ); - } - } - */ - - customElements.define('vevent-description', ComponentDescription); - customElements.define('vevent-edit', ComponentEdit); - customElements.define('vevent-block', ComponentBlock); -}) - - - -class DateTimeInput extends HTMLElement { - constructor () { - super(); - this.innerHTML = '' - } - - static get observedAttributes () { - return [ 'dateonly' ] - } - - attributeChangedCallback (name, from, to) { - console.log(this, name, boolean(from), boolean(to)); - switch (name) { - case 'dateonly': - this.querySelector('[type="time"]').disabled = boolean(to) - break; - } - } - - get dateonly () { - return boolean(this.getAttribute('dateonly')); - } - - set dateonly (bool) { - this.setAttribute ('dateonly', bool); - } - - get value () { - - let dt; - let date = this.querySelector("[type='date']").value; - if (boolean(this.getAttribute('dateonly'))) { - dt = parseDate(date); - dt.type = 'date'; - } else { - let time = this.querySelector("[type='time']").value; - dt = parseDate(date + 'T' + time) - dt.type = 'date-time'; - } - return dt; - } - - set value (new_value) { - let date, time; - if (new_value instanceof Date) { - date = new_value.format("~L~Y-~m-~d"); - time = new_value.format("~L~H:~M:~S"); - } else { - [date, time] = new_value.split('T') - } - this.querySelector("[type='date']").value = date; - this.querySelector("[type='time']").value = time; - } - - addEventListener(type, proc) { - if (type != 'input') throw "Only input supported"; - - this.querySelector("[type='date']").addEventListener(type, proc); - this.querySelector("[type='time']").addEventListener(type, proc); - } -} - -customElements.define('date-time-input', DateTimeInput) - -class PopupElement extends HTMLElement { - constructor () { - super(); - - /* TODO populate remaining */ - // this.id = 'popup' + this.dataset.uid - } - - redraw () { - console.log('IMPLEMENT ME'); - } - - connectedCallback() { - let body = document.getElementById('popup-template').content.cloneNode(true).firstElementChild; - - let uid = this.dataset.uid - // console.log(uid); - - body.getElementsByClassName('populate-with-uid') - .forEach((e) => e.setAttribute('data-uid', uid)); - - /* tabs */ - let tabgroup_id = gensym(); - for (let tab of body.querySelectorAll(".tabgroup .tab")) { - let new_id = gensym(); - let input = tab.querySelector("input"); - input.id = new_id; - input.name = tabgroup_id; - tab.querySelector("label").setAttribute('for', new_id); - } - /* end tabs */ - - /* nav bar */ - let nav = body.getElementsByClassName("popup-control")[0]; - bind_popup_control(nav); - - let btn = body.querySelector('.popup-control .close-tooltip') - btn.addEventListener('click', () => { - close_popup(this); - }); - /* end nav bar */ - - this.replaceChildren(body); - - let that = this; - this.getElementsByClassName("calendar-selection") - .addEventListener('change', function () { - let uid = that.closest('[data-uid]').dataset.uid - let obj = vcal_objects[uid] - this.value; - // event.properties.calendar = this.value; - }); - - } -} - -window.addEventListener('load', function () { - customElements.define('popup-element', PopupElement) -}); - -function wholeday_checkbox (box) { - box.closest('.timeinput') - .getElementsByTagName('date-time-input') - .forEach(el => el.dateonly = box.checked); -} diff --git a/static/globals.ts b/static/globals.ts new file mode 100644 index 00000000..86368e9a --- /dev/null +++ b/static/globals.ts @@ -0,0 +1,337 @@ +"use strict"; + +const vcal_objects = {}; + +class ComponentVEvent extends HTMLElement { + constructor () { + super (); + this.template = document.getElementById(this.tagName); + + let uid; + if ((uid = this.dataset.uid)) { + vcal_objects[uid].register(this); + } + + /* We DON'T have a redraw here in the general case, since the + HTML rendered server-side should be fine enough for us. + Those that need a direct rerendering (such as the edit tabs) + should take care of that some other way */ + } + + connectedCallback () { + let uid; + if ((uid = this.dataset.uid)) { + this.redraw (vcal_objects[uid]); + } + } + + redraw (data) { + // update ourselves from template + + if (! this.template) { + throw "Something"; + } + + let body = this.template.content.cloneNode(true).firstElementChild; + + for (let el of body.getElementsByClassName("bind")) { + let p = el.dataset.property; + let d, fmt; + if ((d = data.getProperty(p))) { + if ((fmt = el.dataset.fmt)) { + el.innerHTML = d.format(fmt); + } else { + el.innerHTML = d; + } + } + } + + this.replaceChildren(body); + } + +} + +class ComponentDescription extends ComponentVEvent { + constructor () { + super() ; + } + +} + +class ComponentEdit extends ComponentVEvent { + constructor () { + super(); + + this.firstTime = true; + } + + connectedCallback() { + + /* Edit tab is rendered here. It's left blank server-side, since + it only makes sense to have something here if we have javascript */ + + let data = vcal_objects[this.dataset.uid] + + if (! data) { + throw `Data missing for uid ${this.dataset.uid}.` + } + + this.redraw(data); + + for (let el of this.getElementsByClassName("interactive")) { + el.addEventListener('input', () => { + vcal_objects[this.dataset.uid].setProperty( + el.dataset.property, + el.value) + }); + } + } + + redraw (data) { + // update ourselves from template + + if (! this.template) { + throw "Something"; + } + + let body; + if (this.firstTime) { + body = this.template.content.cloneNode(true).firstElementChild; + } else { + body = this; + } + + for (let el of body.getElementsByClassName("interactive")) { + let p = el.dataset.property; + let d; + if ((d = data.getProperty(p))) { + /* + https://stackoverflow.com/questions/57157830/how-can-i-specify-the-sequence-of-running-nested-web-components-constructors + */ + window.setTimeout (() => { + /* NOTE Some specific types might require special formatting + here. But due to my custom components implementing custom + `.value' procedures, we might not need any special cases + here */ + el.value = d; + }); + } + } + + if (this.firstTime) { + this.replaceChildren(body); + this.firstTime = false; + } + } + +} + +function find_popup (uid) { + // for (let el of vcal_objects[uid].registered) { + // if (el.tagName === 'popup-element') { + // return el; + // } + // } + // throw 'Popup not fonud'; + return document.querySelector(`popup-element[data-uid="${uid}"]`) +} + +function find_block (uid) { + for (let el of vcal_objects[uid].registered) { + if (el.tagName === 'vevent-block') { + return el; + } + } + throw 'Popup not fonud'; +} + +class ComponentBlock extends ComponentVEvent { + constructor () { + super(); + + this.addEventListener('click', () => { + toggle_popup(find_popup(this.dataset.uid)); + }); + } + + redraw (data) { + super.redraw(data); + + let p; + if ((p = data.getProperty('dtstart'))) { + this.style.top = date_to_percent(to_local(p), 1) + "%"; + // console.log('dtstart', p); + } + if ((p = data.getProperty('dtend'))) { + this.style.height = 'unset'; + // console.log('dtend', p); + this.style.bottom = (100 - date_to_percent(to_local(p), 1)) + "%"; + } + } +} + +window.addEventListener('load', function () { + + // let json_objects_el = document.getElementById('json-objects'); + let div = document.getElementById('xcal-data'); + let vevents = div.firstElementChild.childNodes; + + for (let vevent of vevents) { + let ev = xml_to_vcal(vevent); + vcal_objects[ev.getProperty('uid')] = ev + } + + /* + - .popup + - .block + - .list + */ + /* + let vevent_els = document.getElementsByClassName('vevent') + for (let el of vevent_els) { + try { + vcal_objects[el.dataset.uid].register(el); + } catch { + console.error("Invalid something, uid = ", el.dataset.uid, + "el = ", el + ); + } + } + */ + + customElements.define('vevent-description', ComponentDescription); + customElements.define('vevent-edit', ComponentEdit); + customElements.define('vevent-block', ComponentBlock); +}) + + + +class DateTimeInput extends HTMLElement { + constructor () { + super(); + this.innerHTML = '' + } + + static get observedAttributes () { + return [ 'dateonly' ] + } + + attributeChangedCallback (name, from, to) { + console.log(this, name, boolean(from), boolean(to)); + switch (name) { + case 'dateonly': + this.querySelector('[type="time"]').disabled = boolean(to) + break; + } + } + + get dateonly () { + return boolean(this.getAttribute('dateonly')); + } + + set dateonly (bool) { + this.setAttribute ('dateonly', bool); + } + + get value () { + + let dt; + let date = this.querySelector("[type='date']").value; + if (boolean(this.getAttribute('dateonly'))) { + dt = parseDate(date); + dt.type = 'date'; + } else { + let time = this.querySelector("[type='time']").value; + dt = parseDate(date + 'T' + time) + dt.type = 'date-time'; + } + return dt; + } + + set value (new_value) { + let date, time; + if (new_value instanceof Date) { + date = new_value.format("~L~Y-~m-~d"); + time = new_value.format("~L~H:~M:~S"); + } else { + [date, time] = new_value.split('T') + } + this.querySelector("[type='date']").value = date; + this.querySelector("[type='time']").value = time; + } + + addEventListener(type, proc) { + if (type != 'input') throw "Only input supported"; + + this.querySelector("[type='date']").addEventListener(type, proc); + this.querySelector("[type='time']").addEventListener(type, proc); + } +} + +customElements.define('date-time-input', DateTimeInput) + +class PopupElement extends HTMLElement { + constructor () { + super(); + + /* TODO populate remaining */ + // this.id = 'popup' + this.dataset.uid + } + + redraw () { + console.log('IMPLEMENT ME'); + } + + connectedCallback() { + let body = document.getElementById('popup-template').content.cloneNode(true).firstElementChild; + + let uid = this.dataset.uid + // console.log(uid); + + body.getElementsByClassName('populate-with-uid') + .forEach((e) => e.setAttribute('data-uid', uid)); + + /* tabs */ + let tabgroup_id = gensym(); + for (let tab of body.querySelectorAll(".tabgroup .tab")) { + let new_id = gensym(); + let input = tab.querySelector("input"); + input.id = new_id; + input.name = tabgroup_id; + tab.querySelector("label").setAttribute('for', new_id); + } + /* end tabs */ + + /* nav bar */ + let nav = body.getElementsByClassName("popup-control")[0]; + bind_popup_control(nav); + + let btn = body.querySelector('.popup-control .close-tooltip') + btn.addEventListener('click', () => { + close_popup(this); + }); + /* end nav bar */ + + this.replaceChildren(body); + + let that = this; + this.getElementsByClassName("calendar-selection") + .addEventListener('change', function () { + let uid = that.closest('[data-uid]').dataset.uid + let obj = vcal_objects[uid] + this.value; + // event.properties.calendar = this.value; + }); + + } +} + +window.addEventListener('load', function () { + customElements.define('popup-element', PopupElement) +}); + +function wholeday_checkbox (box) { + box.closest('.timeinput') + .getElementsByTagName('date-time-input') + .forEach(el => el.dateonly = box.checked); +} diff --git a/static/jcal.js b/static/jcal.js deleted file mode 100644 index 003294d1..00000000 --- a/static/jcal.js +++ /dev/null @@ -1,174 +0,0 @@ -function jcal_type_to_xcal(doc, type, value) { - let el = doc.createElementNS(xcal, type); - switch (type) { - case 'boolean': - el.textContent = value ? "true" : "false"; - break; - - case 'float': - case 'integer': - el.textContent = '' + value; - break; - - case 'period': - let [start, end] = value; - let startEl = doc.createElementNS(xcal, 'start'); - startEl.textContent = start; - let endEL; - if (end.find('P')) { - endEl = doc.createElementNS(xcal, 'duration'); - } else { - endEl = doc.createElementNS(xcal, 'end'); - } - endEl.textContent = end; - el.appendChild(startEl); - el.appendChild(endEl); - break; - - case 'recur': - for (var key in value) { - if (! value.hasOwnProperty(key)) continue; - let e = doc.createElementNS(xcal, key); - e.textContent = value[key]; - el.appendChild(e); - } - break; - - case 'date': - case 'time': - case 'date-time': - - case 'duration': - - case 'binary': - case 'text': - case 'uri': - case 'cal-address': - case 'utc-offset': - el.textContent = value; - break; - - default: - /* TODO error */ - } - return el; -} - -function jcal_property_to_xcal_property(doc, jcal) { - let [propertyName, params, type, ...values] = jcal; - - let tag = doc.createElementNS(xcal, propertyName); - - /* setup parameters */ - let paramEl = doc.createElementNS(xcal, 'params'); - for (var key in params) { - /* Check if the key actually belongs to us. - At least (my) format also appears when iterating - over the parameters. Probably a case of builtins - vs user defined. - - This is also the reason we can't check if params - is empty beforehand, and instead check the - number of children of paramEl below. - */ - if (! params.hasOwnProperty(key)) continue; - - let el = doc.createElementNS(xcal, key); - - for (let v of asList(params[key])) { - let text = doc.createElementNS(xcal, 'text'); - text.textContent = '' + v; - el.appendChild(text); - } - - paramEl.appendChild(el); - } - - if (paramEl.childCount > 0) { - tag.appendChild(paramEl); - } - - /* setup value (and type) */ - // let typeEl = doc.createElementNS(xcal, type); - - switch (propertyName) { - case 'geo': - if (type == 'float') { - // assert values[0] == [x, y] - let [x, y] = values[0]; - let lat = doc.createElementNS(xcal, 'latitude') - let lon = doc.createElementNS(xcal, 'longitude') - lat.textContent = x; - lon.textContent = y; - tag.appendChild(lat); - tag.appendChild(lon); - } else { - /* TODO, error */ - } - break; - case 'request-status': - if (type == 'text') { - // assert values[0] instanceof Array - let [code, desc, ...data] = values[0]; - let codeEl = doc.createElementNS(xcal, 'code') - code.textContent = code; - tag.appendChild(codeEl); - - - let descEl = doc.createElementNS(xcal, 'description') - desc.textContent = desc; - tag.appendChild(descEl); - - if (data !== []) { - data = data[0]; - let dataEl = doc.createElementNS(xcal, 'data') - data.textContent = data; - tag.appendChild(dataEl); - } - } else { - /* TODO, error */ - } - break; - default: - for (let value of values) { - tag.appendChild(jcal_type_to_xcal(doc, type, value)) - } - } - - return tag; -} - - -function jcal_to_xcal(...jcals) { - let doc = document.implementation.createDocument(xcal, 'icalendar'); - for (let jcal of jcals) { - doc.documentElement.appendChild(jcal_to_xcal_inner(doc, jcal)); - } - return doc; -} - -function jcal_to_xcal_inner(doc, jcal) { - let [tagname, properties, components] = jcal; - - let xcal_tag = doc.createElementNS(xcal, tagname); - - /* I'm not sure if the properties and components tag should be left out - when empty. It should however NOT be an error to leave them in. - */ - - let xcal_properties = doc.createElementNS(xcal, 'properties'); - for (let property of properties) { - xcal_properties.appendChild(jcal_property_to_xcal_property(doc, property)); - } - - let xcal_children = doc.createElementNS(xcal, 'components'); - for (let child of components) { - xcal_children.appendChild(jcal_to_xcal_inner(doc, child)); - } - - xcal_tag.appendChild(xcal_properties); - xcal_tag.appendChild(xcal_children); - - return xcal_tag; - -} diff --git a/static/jcal.ts b/static/jcal.ts new file mode 100644 index 00000000..003294d1 --- /dev/null +++ b/static/jcal.ts @@ -0,0 +1,174 @@ +function jcal_type_to_xcal(doc, type, value) { + let el = doc.createElementNS(xcal, type); + switch (type) { + case 'boolean': + el.textContent = value ? "true" : "false"; + break; + + case 'float': + case 'integer': + el.textContent = '' + value; + break; + + case 'period': + let [start, end] = value; + let startEl = doc.createElementNS(xcal, 'start'); + startEl.textContent = start; + let endEL; + if (end.find('P')) { + endEl = doc.createElementNS(xcal, 'duration'); + } else { + endEl = doc.createElementNS(xcal, 'end'); + } + endEl.textContent = end; + el.appendChild(startEl); + el.appendChild(endEl); + break; + + case 'recur': + for (var key in value) { + if (! value.hasOwnProperty(key)) continue; + let e = doc.createElementNS(xcal, key); + e.textContent = value[key]; + el.appendChild(e); + } + break; + + case 'date': + case 'time': + case 'date-time': + + case 'duration': + + case 'binary': + case 'text': + case 'uri': + case 'cal-address': + case 'utc-offset': + el.textContent = value; + break; + + default: + /* TODO error */ + } + return el; +} + +function jcal_property_to_xcal_property(doc, jcal) { + let [propertyName, params, type, ...values] = jcal; + + let tag = doc.createElementNS(xcal, propertyName); + + /* setup parameters */ + let paramEl = doc.createElementNS(xcal, 'params'); + for (var key in params) { + /* Check if the key actually belongs to us. + At least (my) format also appears when iterating + over the parameters. Probably a case of builtins + vs user defined. + + This is also the reason we can't check if params + is empty beforehand, and instead check the + number of children of paramEl below. + */ + if (! params.hasOwnProperty(key)) continue; + + let el = doc.createElementNS(xcal, key); + + for (let v of asList(params[key])) { + let text = doc.createElementNS(xcal, 'text'); + text.textContent = '' + v; + el.appendChild(text); + } + + paramEl.appendChild(el); + } + + if (paramEl.childCount > 0) { + tag.appendChild(paramEl); + } + + /* setup value (and type) */ + // let typeEl = doc.createElementNS(xcal, type); + + switch (propertyName) { + case 'geo': + if (type == 'float') { + // assert values[0] == [x, y] + let [x, y] = values[0]; + let lat = doc.createElementNS(xcal, 'latitude') + let lon = doc.createElementNS(xcal, 'longitude') + lat.textContent = x; + lon.textContent = y; + tag.appendChild(lat); + tag.appendChild(lon); + } else { + /* TODO, error */ + } + break; + case 'request-status': + if (type == 'text') { + // assert values[0] instanceof Array + let [code, desc, ...data] = values[0]; + let codeEl = doc.createElementNS(xcal, 'code') + code.textContent = code; + tag.appendChild(codeEl); + + + let descEl = doc.createElementNS(xcal, 'description') + desc.textContent = desc; + tag.appendChild(descEl); + + if (data !== []) { + data = data[0]; + let dataEl = doc.createElementNS(xcal, 'data') + data.textContent = data; + tag.appendChild(dataEl); + } + } else { + /* TODO, error */ + } + break; + default: + for (let value of values) { + tag.appendChild(jcal_type_to_xcal(doc, type, value)) + } + } + + return tag; +} + + +function jcal_to_xcal(...jcals) { + let doc = document.implementation.createDocument(xcal, 'icalendar'); + for (let jcal of jcals) { + doc.documentElement.appendChild(jcal_to_xcal_inner(doc, jcal)); + } + return doc; +} + +function jcal_to_xcal_inner(doc, jcal) { + let [tagname, properties, components] = jcal; + + let xcal_tag = doc.createElementNS(xcal, tagname); + + /* I'm not sure if the properties and components tag should be left out + when empty. It should however NOT be an error to leave them in. + */ + + let xcal_properties = doc.createElementNS(xcal, 'properties'); + for (let property of properties) { + xcal_properties.appendChild(jcal_property_to_xcal_property(doc, property)); + } + + let xcal_children = doc.createElementNS(xcal, 'components'); + for (let child of components) { + xcal_children.appendChild(jcal_to_xcal_inner(doc, child)); + } + + xcal_tag.appendChild(xcal_properties); + xcal_tag.appendChild(xcal_children); + + return xcal_tag; + +} diff --git a/static/lib.js b/static/lib.js deleted file mode 100644 index 100f4161..00000000 --- a/static/lib.js +++ /dev/null @@ -1,205 +0,0 @@ -'use strict'; -/* - General procedures which in theory could be used anywhere. - */ - -HTMLElement.prototype._addEventListener = HTMLElement.prototype.addEventListener; -HTMLElement.prototype.addEventListener = function (name, proc) { - if (! this.listeners) this.listeners = {}; - if (! this.listeners[name]) this.listeners[name] = []; - this.listeners[name].push(proc); - return this._addEventListener(name, proc); -}; - - - -/* list of lists -> list of tuples */ -function zip(...args) { - // console.log(args); - if (args === []) return []; - return [...Array(Math.min(...args.map(x => x.length))).keys()] - .map((_, i) => args.map(lst => lst[i])); -} - - -/* ----- Date Extensions ---------------------------- */ - -/* - Extensions to Javascript's Date to allow representing times - with different timezones. Currently only UTC and local time - are supported, but more should be able to be added. - - NOTE that only the raw `get' (and NOT the `getUTC') methods - should be used on these objects, and that the reported timezone - is quite often wrong. - - TODO The years between 0 and 100 (inclusive) gives dates in the twentieth - century, due to how javascript works (...). - */ - -function parseDate(str) { - let year, month, day, hour=false, minute, second=0, utc; - - let end = str.length - 1; - if (str[end] == 'Z') { - utc = true; - str = str.substring(0, end); - }; - - switch (str.length) { - case '2020-01-01T13:37:00'.length: - second = str.substr(17,2); - case '2020-01-01T13:37'.length: - hour = str.substr(11,2); - minute = str.substr(14,2); - case '2020-01-01'.length: - year = str.substr(0,4); - month = str.substr(5,2) - 1; - day = str.substr(8,2); - break; - default: - throw 'Bad argument'; - } - - let date; - if (hour) { - date = new Date(year, month, day, hour, minute, second); - date.utc = utc; - date.dateonly = false; - } else { - date = new Date(year, month, day); - date.dateonly = true; - } - return date; -} - -function copyDate(date) { - let d = new Date(date); - d.utc = date.utc; - d.dateonly = date.dateonly; - return d; -} - -function to_local(date) { - if (! date.utc) return date; - - return new Date(date.getTime() - date.getTimezoneOffset() * 60 * 1000); -} - -/* -------------------------------------------------- */ - -function makeElement (name, attr={}) { - let element = document.createElement(name); - for (let [key, value] of Object.entries(attr)) { - element[key] = value; - } - return element; -} - -function round_time (time, fraction) { - let scale = 1 / fraction; - return Math.round (time * scale) / scale; -} - -/* only used by the bar. - Events use the start and end time of their container, but since the bar - is moving between containers that is clumsy. - Just doing (new Date()/(86400*1000)) would be nice, but there's no good - way to get the time in the current day. - */ -function date_to_percent (date) { - return (date.getHours() + (date.getMinutes() / 60)) * 100/24; -} - -/* if only there was such a thing as a let to wrap around my lambda... */ -/* js infix to not collide with stuff generated backend */ -const gensym = (counter => (prefix="gensym") => prefix + "js" + ++counter)(0) - -function setVar(str, val) { - document.documentElement.style.setProperty("--" + str, val); -} - - -function asList(thing) { - if (thing instanceof Array) { - return thing; - } else { - return [thing]; - } -} - - -function boolean (value) { - switch (typeof value) { - case 'string': - switch (value) { - case 'true': return true; - case 'false': return false; - case '': return false; - default: return true; - } - case 'boolean': - return value; - default: - return !! value; - } -} - - - -/* internal */ -function datepad(thing, width=2) { - return (thing + "").padStart(width, "0"); -} - -function format_date(date, str) { - let fmtmode = false; - let outstr = ""; - for (var i = 0; i < str.length; i++) { - if (fmtmode) { - switch (str[i]) { - /* Moves the date into local time. */ - case 'L': date = to_local(date); break; - case 'Y': outstr += datepad(date.getFullYear(), 4); break; - case 'm': outstr += datepad(date.getMonth() + 1); break; - case 'd': outstr += datepad(date.getDate()); break; - case 'H': outstr += datepad(date.getHours()); break; - case 'M': outstr += datepad(date.getMinutes()); break; - case 'S': outstr += datepad(date.getSeconds()); break; - case 'Z': if (date.utc) outstr += 'Z'; break; - } - fmtmode = false; - } else if (str[i] == '~') { - fmtmode = true; - } else { - outstr += str[i]; - } - } - return outstr; -} - -Object.prototype.format = function (/* any number of arguments */) { return "" + this; } -Date.prototype.format = function (str) { return format_date (this, str); } - -/* - * Finds the first element of the DOMTokenList whichs value matches - * the supplied regexp. Returns a pair of the index and the value. - */ -DOMTokenList.prototype.find = function (regexp) { - let entries = this.entries(); - let entry; - while (! (entry = entries.next()).done) { - if (entry.value[1].match(regexp)) { - return entry.value; - } - } -} - -/* HTMLCollection is the result of a querySelectorAll */ -HTMLCollection.prototype.forEach = function (proc) { - for (let el of this) { - proc(el); - } -} - -const xcal = "urn:ietf:params:xml:ns:icalendar-2.0"; diff --git a/static/lib.ts b/static/lib.ts new file mode 100644 index 00000000..100f4161 --- /dev/null +++ b/static/lib.ts @@ -0,0 +1,205 @@ +'use strict'; +/* + General procedures which in theory could be used anywhere. + */ + +HTMLElement.prototype._addEventListener = HTMLElement.prototype.addEventListener; +HTMLElement.prototype.addEventListener = function (name, proc) { + if (! this.listeners) this.listeners = {}; + if (! this.listeners[name]) this.listeners[name] = []; + this.listeners[name].push(proc); + return this._addEventListener(name, proc); +}; + + + +/* list of lists -> list of tuples */ +function zip(...args) { + // console.log(args); + if (args === []) return []; + return [...Array(Math.min(...args.map(x => x.length))).keys()] + .map((_, i) => args.map(lst => lst[i])); +} + + +/* ----- Date Extensions ---------------------------- */ + +/* + Extensions to Javascript's Date to allow representing times + with different timezones. Currently only UTC and local time + are supported, but more should be able to be added. + + NOTE that only the raw `get' (and NOT the `getUTC') methods + should be used on these objects, and that the reported timezone + is quite often wrong. + + TODO The years between 0 and 100 (inclusive) gives dates in the twentieth + century, due to how javascript works (...). + */ + +function parseDate(str) { + let year, month, day, hour=false, minute, second=0, utc; + + let end = str.length - 1; + if (str[end] == 'Z') { + utc = true; + str = str.substring(0, end); + }; + + switch (str.length) { + case '2020-01-01T13:37:00'.length: + second = str.substr(17,2); + case '2020-01-01T13:37'.length: + hour = str.substr(11,2); + minute = str.substr(14,2); + case '2020-01-01'.length: + year = str.substr(0,4); + month = str.substr(5,2) - 1; + day = str.substr(8,2); + break; + default: + throw 'Bad argument'; + } + + let date; + if (hour) { + date = new Date(year, month, day, hour, minute, second); + date.utc = utc; + date.dateonly = false; + } else { + date = new Date(year, month, day); + date.dateonly = true; + } + return date; +} + +function copyDate(date) { + let d = new Date(date); + d.utc = date.utc; + d.dateonly = date.dateonly; + return d; +} + +function to_local(date) { + if (! date.utc) return date; + + return new Date(date.getTime() - date.getTimezoneOffset() * 60 * 1000); +} + +/* -------------------------------------------------- */ + +function makeElement (name, attr={}) { + let element = document.createElement(name); + for (let [key, value] of Object.entries(attr)) { + element[key] = value; + } + return element; +} + +function round_time (time, fraction) { + let scale = 1 / fraction; + return Math.round (time * scale) / scale; +} + +/* only used by the bar. + Events use the start and end time of their container, but since the bar + is moving between containers that is clumsy. + Just doing (new Date()/(86400*1000)) would be nice, but there's no good + way to get the time in the current day. + */ +function date_to_percent (date) { + return (date.getHours() + (date.getMinutes() / 60)) * 100/24; +} + +/* if only there was such a thing as a let to wrap around my lambda... */ +/* js infix to not collide with stuff generated backend */ +const gensym = (counter => (prefix="gensym") => prefix + "js" + ++counter)(0) + +function setVar(str, val) { + document.documentElement.style.setProperty("--" + str, val); +} + + +function asList(thing) { + if (thing instanceof Array) { + return thing; + } else { + return [thing]; + } +} + + +function boolean (value) { + switch (typeof value) { + case 'string': + switch (value) { + case 'true': return true; + case 'false': return false; + case '': return false; + default: return true; + } + case 'boolean': + return value; + default: + return !! value; + } +} + + + +/* internal */ +function datepad(thing, width=2) { + return (thing + "").padStart(width, "0"); +} + +function format_date(date, str) { + let fmtmode = false; + let outstr = ""; + for (var i = 0; i < str.length; i++) { + if (fmtmode) { + switch (str[i]) { + /* Moves the date into local time. */ + case 'L': date = to_local(date); break; + case 'Y': outstr += datepad(date.getFullYear(), 4); break; + case 'm': outstr += datepad(date.getMonth() + 1); break; + case 'd': outstr += datepad(date.getDate()); break; + case 'H': outstr += datepad(date.getHours()); break; + case 'M': outstr += datepad(date.getMinutes()); break; + case 'S': outstr += datepad(date.getSeconds()); break; + case 'Z': if (date.utc) outstr += 'Z'; break; + } + fmtmode = false; + } else if (str[i] == '~') { + fmtmode = true; + } else { + outstr += str[i]; + } + } + return outstr; +} + +Object.prototype.format = function (/* any number of arguments */) { return "" + this; } +Date.prototype.format = function (str) { return format_date (this, str); } + +/* + * Finds the first element of the DOMTokenList whichs value matches + * the supplied regexp. Returns a pair of the index and the value. + */ +DOMTokenList.prototype.find = function (regexp) { + let entries = this.entries(); + let entry; + while (! (entry = entries.next()).done) { + if (entry.value[1].match(regexp)) { + return entry.value; + } + } +} + +/* HTMLCollection is the result of a querySelectorAll */ +HTMLCollection.prototype.forEach = function (proc) { + for (let el of this) { + proc(el); + } +} + +const xcal = "urn:ietf:params:xml:ns:icalendar-2.0"; diff --git a/static/popup.js b/static/popup.js deleted file mode 100644 index 0b04b280..00000000 --- a/static/popup.js +++ /dev/null @@ -1,105 +0,0 @@ - - -/* event component => coresponding popup component */ -function event_from_popup(popup) { - // return document.getElementById(popup.id.substr(5)) - return find_block(popup.closest('[data-uid]').dataset.uid) -} - -/* popup component => coresponding event component */ -function popup_from_event(event) { - // return document.getElementById("popup" + event.id); - return find_popup(event.closest('[data-uid]').dataset.uid) -} - -/* hides given popup */ -function close_popup(popup) { - popup.classList.remove("visible"); -} - -/* hides all popups */ -function close_all_popups () { - for (let popup of document.querySelectorAll("popup-element.visible")) { - close_popup(popup); - } -} - -/* open given popup */ -function open_popup(popup) { - popup.classList.add("visible"); - let element = event_from_popup(popup); - // let root = document.body; - let root; - switch (VIEW) { - case 'week': - root = document.getElementsByClassName("days")[0]; - break; - case 'month': - default: - root = document.body; - break; - } - /* start sets offset between top left corner - of event in calendar and popup. 10, 10 soo old - event is still visible */ - let offsetX = 10, offsetY = 10; - while (element !== root) { - offsetX += element.offsetLeft; - offsetY += element.offsetTop; - element = element.offsetParent; - } - popup.style.left = offsetX + "px"; - popup.style.top = offsetY + "px"; -} - -/* toggles open/closed status of popup given by id */ -function toggle_popup(popup) { - // let popup = document.getElementById(popup_id); - if (popup.classList.contains("visible")) { - close_popup(popup); - } else { - open_popup(popup); - } -} - -/* Code for managing "selected" popup */ -/* Makes the popup last hovered over the selected popup, moving it to - * the top, and allowing global keyboard bindings to affect it. */ - -let activePopup; - -for (let popup of document.querySelectorAll('.popup-container')) { - /* TODO possibly only change "active" element after a fraction of - * a second, for example when moving between tabs */ - popup.addEventListener('mouseover', function () { - /* This is ever so slightly inefficient, - but it really dosen't mammet */ - for (let other of - document.querySelectorAll('.popup-container')) - { - /* TODO get this from somewhere */ - /* Currently it's manually copied from the stylesheet */ - other.style['z-index'] = 1000; - } - popup.style['z-index'] += 1; - activePopup = popup; - }); -} - -document.addEventListener('keydown', function (event) { - /* Physical key position, names are what that key would - be in QWERTY */ - let i = ({ - 'KeyQ': 0, - 'KeyW': 1, - 'KeyE': 2, - 'KeyR': 3, - })[event.code]; - if (i === undefined) return - if (! activePopup) return; - let element = activePopup.querySelectorAll(".tab > label")[i]; - if (! element) return; - element.click(); -}); - -/* END Code for managing "selected" popup */ diff --git a/static/popup.ts b/static/popup.ts new file mode 100644 index 00000000..0b04b280 --- /dev/null +++ b/static/popup.ts @@ -0,0 +1,105 @@ + + +/* event component => coresponding popup component */ +function event_from_popup(popup) { + // return document.getElementById(popup.id.substr(5)) + return find_block(popup.closest('[data-uid]').dataset.uid) +} + +/* popup component => coresponding event component */ +function popup_from_event(event) { + // return document.getElementById("popup" + event.id); + return find_popup(event.closest('[data-uid]').dataset.uid) +} + +/* hides given popup */ +function close_popup(popup) { + popup.classList.remove("visible"); +} + +/* hides all popups */ +function close_all_popups () { + for (let popup of document.querySelectorAll("popup-element.visible")) { + close_popup(popup); + } +} + +/* open given popup */ +function open_popup(popup) { + popup.classList.add("visible"); + let element = event_from_popup(popup); + // let root = document.body; + let root; + switch (VIEW) { + case 'week': + root = document.getElementsByClassName("days")[0]; + break; + case 'month': + default: + root = document.body; + break; + } + /* start sets offset between top left corner + of event in calendar and popup. 10, 10 soo old + event is still visible */ + let offsetX = 10, offsetY = 10; + while (element !== root) { + offsetX += element.offsetLeft; + offsetY += element.offsetTop; + element = element.offsetParent; + } + popup.style.left = offsetX + "px"; + popup.style.top = offsetY + "px"; +} + +/* toggles open/closed status of popup given by id */ +function toggle_popup(popup) { + // let popup = document.getElementById(popup_id); + if (popup.classList.contains("visible")) { + close_popup(popup); + } else { + open_popup(popup); + } +} + +/* Code for managing "selected" popup */ +/* Makes the popup last hovered over the selected popup, moving it to + * the top, and allowing global keyboard bindings to affect it. */ + +let activePopup; + +for (let popup of document.querySelectorAll('.popup-container')) { + /* TODO possibly only change "active" element after a fraction of + * a second, for example when moving between tabs */ + popup.addEventListener('mouseover', function () { + /* This is ever so slightly inefficient, + but it really dosen't mammet */ + for (let other of + document.querySelectorAll('.popup-container')) + { + /* TODO get this from somewhere */ + /* Currently it's manually copied from the stylesheet */ + other.style['z-index'] = 1000; + } + popup.style['z-index'] += 1; + activePopup = popup; + }); +} + +document.addEventListener('keydown', function (event) { + /* Physical key position, names are what that key would + be in QWERTY */ + let i = ({ + 'KeyQ': 0, + 'KeyW': 1, + 'KeyE': 2, + 'KeyR': 3, + })[event.code]; + if (i === undefined) return + if (! activePopup) return; + let element = activePopup.querySelectorAll(".tab > label")[i]; + if (! element) return; + element.click(); +}); + +/* END Code for managing "selected" popup */ diff --git a/static/rrule.js b/static/rrule.js deleted file mode 100644 index e7377370..00000000 --- a/static/rrule.js +++ /dev/null @@ -1,100 +0,0 @@ -function recur_xml_to_rrule(dom_element) { - let rr = new RRule; - for (let child of dom_element.children) { - let key = child.tagName; /* freq */ - let val = child.textContent; /* weekly */ - rr[key] = val; - } - return rr; -} - -function recur_jcal_to_rrule(jcal) { - let rr = new RRule; - for (var key in jcal) { - rr[key] = jcal[key]; - } - return rr; -} - -class RRule { - - /* direct access to fields is fine */ - /* setting them however requires methods, since there might - be listeners */ - - fields = ['freq', 'until', 'count', 'interval', - 'bysecond', 'byminute', 'byhour', - 'bymonthday', 'byyearday', 'byweekno', - 'bymonth', 'bysetpos', 'wkst', - 'byday' - ] - - constructor() { - - this.listeners = {} - - for (let f of this.fields) { - this[f] = false; - Object.defineProperty( - this, f, { - /* - TODO many of the fields should be wrapped - in type tags. e.g. elements are either - or , NOT a raw date. - by* fields should be wrapped with multiple values. - */ - get: () => this['_' + f], - set: (v) => { - this['_' + f] = v - for (let l of this.listeners[f]) { - l(v); - } - } - }); - this.listeners[f] = []; - } - } - - addListener(field, proc) { - this.listeners[field].push(proc); - } - - /* NOTE this function is probably never used. - Deperate it and refer to RRule.asJcal - together with jcal_to_xcal */ - asXcal(doc) { - /* TODO empty case */ - // let str = ""; - let root = doc.createElementNS(xcal, 'recur'); - for (let f of this.fields) { - let v = this.fields[f]; - if (! v) continue; - let tag = doc.createElementNS(xcal, f); - /* TODO type formatting */ - tag.textContent = `${v}`; - root.appendChild(tag); - } - return root; - } - - asJcal() { - let obj = {}; - for (let f of this.fields) { - let v = this[f]; - if (! v) continue; - /* TODO special formatting for some types */ - obj[f] = v; - } - return obj; - } - - /* - asIcal() { - return this.fields - .map(f => [f, this.fields[f]]) - .filter([_, v] => v) - .map(([k, v]) => `${k}=${v}`) - .join(';'); - } - */ -}; diff --git a/static/rrule.ts b/static/rrule.ts new file mode 100644 index 00000000..e7377370 --- /dev/null +++ b/static/rrule.ts @@ -0,0 +1,100 @@ +function recur_xml_to_rrule(dom_element) { + let rr = new RRule; + for (let child of dom_element.children) { + let key = child.tagName; /* freq */ + let val = child.textContent; /* weekly */ + rr[key] = val; + } + return rr; +} + +function recur_jcal_to_rrule(jcal) { + let rr = new RRule; + for (var key in jcal) { + rr[key] = jcal[key]; + } + return rr; +} + +class RRule { + + /* direct access to fields is fine */ + /* setting them however requires methods, since there might + be listeners */ + + fields = ['freq', 'until', 'count', 'interval', + 'bysecond', 'byminute', 'byhour', + 'bymonthday', 'byyearday', 'byweekno', + 'bymonth', 'bysetpos', 'wkst', + 'byday' + ] + + constructor() { + + this.listeners = {} + + for (let f of this.fields) { + this[f] = false; + Object.defineProperty( + this, f, { + /* + TODO many of the fields should be wrapped + in type tags. e.g. elements are either + or , NOT a raw date. + by* fields should be wrapped with multiple values. + */ + get: () => this['_' + f], + set: (v) => { + this['_' + f] = v + for (let l of this.listeners[f]) { + l(v); + } + } + }); + this.listeners[f] = []; + } + } + + addListener(field, proc) { + this.listeners[field].push(proc); + } + + /* NOTE this function is probably never used. + Deperate it and refer to RRule.asJcal + together with jcal_to_xcal */ + asXcal(doc) { + /* TODO empty case */ + // let str = ""; + let root = doc.createElementNS(xcal, 'recur'); + for (let f of this.fields) { + let v = this.fields[f]; + if (! v) continue; + let tag = doc.createElementNS(xcal, f); + /* TODO type formatting */ + tag.textContent = `${v}`; + root.appendChild(tag); + } + return root; + } + + asJcal() { + let obj = {}; + for (let f of this.fields) { + let v = this[f]; + if (! v) continue; + /* TODO special formatting for some types */ + obj[f] = v; + } + return obj; + } + + /* + asIcal() { + return this.fields + .map(f => [f, this.fields[f]]) + .filter([_, v] => v) + .map(([k, v]) => `${k}=${v}`) + .join(';'); + } + */ +}; diff --git a/static/script.js b/static/script.js deleted file mode 100644 index 16ff7bbd..00000000 --- a/static/script.js +++ /dev/null @@ -1,414 +0,0 @@ -'use strict'; - -/* - calp specific stuff -*/ - -class EventCreator { - - /* dynamicly created event when dragging */ - constructor() { - this.event = false; - this.event_start = { x: NaN, y: NaN }; - this.down_on_event = false; - } - - create_empty_event () { - /* TODO this doesn't clone JS attributes */ - - // let event = document.getElementById("event-template") - // .firstChild.cloneNode(true); - // let popup = document.getElementById("popup-template") - // .firstChild.cloneNode(true); - - // document.createElement('vevent-block'); - - /* -------------------- */ - /* Manually transfer or recreate attributes which we still need */ - /* TODO shouldn't these use transferListeners (or similar)? - See input_list.js:transferListeners */ - - // for (let dt of popup.getElementsByClassName("date-time")) { - // init_date_time_single(dt); - // } - - // popup.getElementsByClassName("edit-form")[0].onsubmit = function () { - // create_event(event); - // return false; /* stop default */ - // } - - /* -------------------- */ - /* Fix tabs for newly created popup */ - - let id = gensym ("__js_event"); - - // TODO remove button? - // $("button 2??").onclick = `remove_event(document.getElementById('${id}'))` - - /* - let tabgroup_id = gensym(); - for (let tab of popup.querySelectorAll(".tabgroup .tab")) { - let new_id = gensym(); - let input = tab.querySelector("input"); - input.id = new_id; - input.name = tabgroup_id; - tab.querySelector("label").setAttribute('for', new_id); - } - - let nav = popup.getElementsByClassName("popup-control")[0]; - bind_popup_control(nav); - */ - - /* -------------------- */ - - // TODO download links - - /* -------------------- */ - - event.id = id; - popup.id = "popup" + id; - - return [popup, event]; - } - - create_event_down (intended_target) { - let that = this; - return function (e) { - /* Only trigger event creation stuff on actuall events background, - NOT on its children */ - that.down_on_event = false; - if (e.target != intended_target) return; - that.down_on_event = true; - - that.event_start.x = e.clientX; - that.event_start.y = e.clientY; - } - } - - /* - round_to: what start and end times should round to when dragging, in fractionsb - of the width of the containing container. - - TODO limit this to only continue when on the intended event_container. - - (event → [0, 1)), 𝐑, bool → event → () - */ - create_event_move(pos_in, round_to=1, wide_element=false) { - let that = this; - return function (e) { - if (e.buttons != 1 || ! that.down_on_event) return; - - /* Create event when we start moving the mouse. */ - if (! that.event) { - /* Small deadzone so tiny click and drags aren't registered */ - if (Math.abs(that.event_start.x - e.clientX) < 10 - && Math.abs(that.event_start.y - e.clientY) < 5) - { return; } - - /* only allow start of dragging on background */ - if (e.target != this) return; - - /* only on left click */ - if (e.buttons != 1) return; - - // let [popup, event] = that.create_empty_event(); - // that.event = event; - that.event = document.createElement('vevent-block'); - - /* TODO better solution to add popup to DOM */ - // document.getElementsByTagName("main")[0].append(popup); - - /* [0, 1) -- where are we in the container */ - /* Ronud to force steps of quarters */ - /* NOTE for in-day events a floor here work better, while for - all day events I want a round, but which has the tip over point - around 0.7 instead of 0.5. - It might also be an idea to subtract a tiny bit from the short events - mouse position, since I feel I always get to late starts. - */ - let time = round_time(pos_in(this, e), round_to); - - that.event.dataset.time1 = time; - that.event.dataset.time2 = time; - - /* ---------------------------------------- */ - - this.appendChild(that.event); - - /* requires that event is child of an '.event-container'. */ - // new VComponent( - // event, - // wide_element=wide_element); - // bind_properties(event, wide_element); - - /* requires that dtstart and dtend properties are initialized */ - - /* ---------------------------------------- */ - - /* Makes all current events transparent when dragging over them. - Without this weird stuff happens when moving over them - - This includes ourselves. - */ - for (let e of this.children) { - e.style.pointerEvents = "none"; - } - - } - - let time1 = Number(that.event.dataset.time1); - let time2 = that.event.dataset.time2 = - round_time(pos_in(that.event.parentElement, e), - round_to); - - /* ---------------------------------------- */ - - let event_container = that.event.closest(".event-container"); - - /* These two are in UTC */ - let container_start = parseDate(event_container.dataset.start); - let container_end = parseDate(event_container.dataset.end); - - /* ---------------------------------------- */ - - /* ms */ - let duration = container_end - container_start; - - let start_in_duration = duration * Math.min(time1,time2); - let end_in_duration = duration * Math.max(time1,time2); - - /* Notice that these are converted to UTC, since the intervals are given - in utc, and I only really care about local time (which a specific local - timezone doesn't give me) - */ - /* TODO Should these inherit UTC from container_*? */ - let d1 = new Date(container_start.getTime() + start_in_duration) - let d2 = new Date(container_start.getTime() + end_in_duration) - - // console.log(that.event); - console.log(d1.format("~L~H:~M"), d2.format("~L~H:~M")); - that.event.redraw({ getProperty: (name) => - ({ dtstart: d1, dtend: d2 })[name]}); - // that.event.properties.dtstart = d1; - // that.event.properties.dtend = d2; - } - } - - create_event_finisher (callback) { - let that = this; - return function create_event_up (e) { - if (! that.event) return; - - /* Restore pointer events for all existing events. - Allow pointer events on our new event - */ - for (let e of that.event.parentElement.children) { - e.style.pointerEvents = ""; - } - - // place_in_edit_mode(that.event); - - let localevent = that.event; - that.event = null; - - callback (localevent); - - } - } -} - - - -/* This incarnation of this function only adds the calendar switcher dropdown. - All events are already editable by switching to that tab. - - TODO stop requiring a weird button press to change calendar. -*/ -// function place_in_edit_mode (event) { -// let popup = document.getElementById("popup" + event.id) -// let container = popup.getElementsByClassName('dropdown-goes-here')[0] -// let calendar_dropdown = document.getElementById('calendar-dropdown-template').firstChild.cloneNode(true); -// -// let [_, calclass] = popup.classList.find(/^CAL_/); -// label: { -// for (let [i, option] of calendar_dropdown.childNodes.entries()) { -// if (option.value === calclass.substr(4)) { -// calendar_dropdown.selectedIndex = i; -// break label; -// } -// } -// /* no match, try find default calendar */ -// let t; -// if ((t = calendar_dropdown.querySelector("[selected]"))) { -// event.properties.calendar = t.value; -// } -// } -// -// -// /* Instant change while user is stepping through would be -// * preferable. But I believe that