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/globals.ts | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 static/globals.ts (limited to 'static/globals.ts') 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); +} -- cgit v1.2.3