From 8ec2f441d40ab89b40cc3158f65c914eff497cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Mon, 4 Oct 2021 23:18:24 +0200 Subject: Major typescript work. --- static/globals.ts | 211 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 128 insertions(+), 83 deletions(-) (limited to 'static/globals.ts') diff --git a/static/globals.ts b/static/globals.ts index 86368e9a..64a3613f 100644 --- a/static/globals.ts +++ b/static/globals.ts @@ -1,15 +1,18 @@ "use strict"; -const vcal_objects = {}; +const vcal_objects: Map = new Map() class ComponentVEvent extends HTMLElement { - constructor () { - super (); - this.template = document.getElementById(this.tagName); + + template: HTMLTemplateElement + + constructor() { + super(); + this.template = document.getElementById(this.tagName) as HTMLTemplateElement; let uid; if ((uid = this.dataset.uid)) { - vcal_objects[uid].register(this); + vcal_objects.get(uid)?.register(this); } /* We DON'T have a redraw here in the general case, since the @@ -18,24 +21,26 @@ class ComponentVEvent extends HTMLElement { should take care of that some other way */ } - connectedCallback () { - let uid; + connectedCallback() { + let uid, v; if ((uid = this.dataset.uid)) { - this.redraw (vcal_objects[uid]); + v = vcal_objects.get(uid) + if (v) this.redraw(v); } } - redraw (data) { + redraw(data: VEvent) { // update ourselves from template - if (! this.template) { + if (!this.template) { throw "Something"; } - let body = this.template.content.cloneNode(true).firstElementChild; + let body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!; for (let el of body.getElementsByClassName("bind")) { - let p = el.dataset.property; + if (!(el instanceof HTMLElement)) continue; + let p = el.dataset.property!; let d, fmt; if ((d = data.getProperty(p))) { if ((fmt = el.dataset.fmt)) { @@ -52,17 +57,27 @@ class ComponentVEvent extends HTMLElement { } class ComponentDescription extends ComponentVEvent { - constructor () { - super() ; + constructor() { + super(); } } class ComponentEdit extends ComponentVEvent { - constructor () { + + firstTime: boolean + uid: string + + constructor() { super(); this.firstTime = true; + + if (this.dataset.uid === undefined) { + throw "data-uid must be set" + } else { + this.uid = this.dataset.uid; + } } connectedCallback() { @@ -70,50 +85,59 @@ class ComponentEdit extends ComponentVEvent { /* 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] + let data = vcal_objects.get(this.uid) - if (! data) { + if (!data) { throw `Data missing for uid ${this.dataset.uid}.` } this.redraw(data); + return; + for (let el of this.getElementsByClassName("interactive")) { el.addEventListener('input', () => { - vcal_objects[this.dataset.uid].setProperty( - el.dataset.property, + let obj = vcal_objects.get(this.uid) + if (obj === undefined) { + throw 'No object with uid ' + this.uid + } + if (!(el instanceof HTMLInputElement)) return; + obj.setProperty( + el.dataset.property!, el.value) }); } } - redraw (data) { + redraw(data: VEvent) { // update ourselves from template - if (! this.template) { + if (!this.template) { throw "Something"; } let body; if (this.firstTime) { - body = this.template.content.cloneNode(true).firstElementChild; + body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!; } else { body = this; } for (let el of body.getElementsByClassName("interactive")) { - let p = el.dataset.property; - let d; + if (!(el instanceof HTMLInputElement)) continue; + let p = el.dataset.property!; + let d: any; if ((d = data.getProperty(p))) { /* https://stackoverflow.com/questions/57157830/how-can-i-specify-the-sequence-of-running-nested-web-components-constructors */ - window.setTimeout (() => { + 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; + console.log(el, d); + (el as HTMLInputElement).value = d; }); } } @@ -126,59 +150,68 @@ class ComponentEdit extends ComponentVEvent { } -function find_popup (uid) { +function find_popup(uid: uid): HTMLElement | null { // 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}"]`) + return document.querySelector(`popup-element[data-uid="${uid}"]`) as HTMLElement } -function find_block (uid) { - for (let el of vcal_objects[uid].registered) { +function find_block(uid: uid): HTMLElement | null { + let obj = vcal_objects.get(uid) + if (obj === undefined) { + return null; + } + for (let el of obj.registered) { if (el.tagName === 'vevent-block') { return el; } } - throw 'Popup not fonud'; + // throw 'Popup not fonud'; + return null; } class ComponentBlock extends ComponentVEvent { - constructor () { + constructor() { super(); this.addEventListener('click', () => { - toggle_popup(find_popup(this.dataset.uid)); + let uid = this.dataset.uid + if (uid === undefined) throw new Error('UID missing from' + this) + let popup = find_popup(uid); + if (popup === null) throw new Error('no popup for uid ' + uid); + toggle_popup(popup); }); } - redraw (data) { + redraw(data: VEvent) { super.redraw(data); let p; if ((p = data.getProperty('dtstart'))) { - this.style.top = date_to_percent(to_local(p), 1) + "%"; + this.style.top = date_to_percent(to_local(p)) + "%"; // 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)) + "%"; + this.style.bottom = (100 - date_to_percent(to_local(p))) + "%"; } } } -window.addEventListener('load', function () { +window.addEventListener('load', function() { // let json_objects_el = document.getElementById('json-objects'); - let div = document.getElementById('xcal-data'); - let vevents = div.firstElementChild.childNodes; + let div = document.getElementById('xcal-data')!; + let vevents = div.firstElementChild!.children; for (let vevent of vevents) { let ev = xml_to_vcal(vevent); - vcal_objects[ev.getProperty('uid')] = ev + vcal_objects.set(ev.getProperty('uid'), ev) } /* @@ -206,49 +239,56 @@ window.addEventListener('load', function () { -class DateTimeInput extends HTMLElement { - constructor () { + +class DateTimeInput extends /* HTMLInputElement */ HTMLElement { + constructor() { super(); this.innerHTML = '' + console.log('constructing datetime input') } - static get observedAttributes () { - return [ 'dateonly' ] + static get observedAttributes() { + return ['dateonly'] } - attributeChangedCallback (name, from, to) { + attributeChangedCallback(name: string, from: any, to: any) { console.log(this, name, boolean(from), boolean(to)); switch (name) { - case 'dateonly': - this.querySelector('[type="time"]').disabled = boolean(to) - break; + case 'dateonly': + (this.querySelector('input[type="time"]') as HTMLInputElement) + .disabled = boolean(to) + break; } } - get dateonly () { + get dateonly(): boolean { return boolean(this.getAttribute('dateonly')); } - set dateonly (bool) { - this.setAttribute ('dateonly', bool); + set dateonly(bool: boolean) { + this.setAttribute('dateonly', "" + bool); } - get value () { - + get valueAsDate(): Date { let dt; - let date = this.querySelector("[type='date']").value; + let date = (this.querySelector("input[type='date']") as HTMLInputElement).value; if (boolean(this.getAttribute('dateonly'))) { dt = parseDate(date); dt.type = 'date'; } else { - let time = this.querySelector("[type='time']").value; + let time = (this.querySelector("input[type='time']") as HTMLInputElement).value; dt = parseDate(date + 'T' + time) dt.type = 'date-time'; } return dt; } - set value (new_value) { + get value(): string { + return this.valueAsDate.format("~Y-~m-~dT~H:~M:~S") + } + + set value(new_value: Date | string) { + console.log('Setting date'); let date, time; if (new_value instanceof Date) { date = new_value.format("~L~Y-~m-~d"); @@ -256,36 +296,42 @@ class DateTimeInput extends HTMLElement { } else { [date, time] = new_value.split('T') } - this.querySelector("[type='date']").value = date; - this.querySelector("[type='time']").value = time; + (this.querySelector("input[type='date']") as HTMLInputElement).value = date; + (this.querySelector("input[type='time']") as HTMLInputElement).value = time; } - addEventListener(type, proc) { + addEventListener(type: string, proc: ((e: Event) => void)) { if (type != 'input') throw "Only input supported"; - this.querySelector("[type='date']").addEventListener(type, proc); - this.querySelector("[type='time']").addEventListener(type, proc); + (this.querySelector("input[type='date']") as HTMLInputElement) + .addEventListener(type, proc); + (this.querySelector("input[type='time']") as HTMLInputElement) + .addEventListener(type, proc); } } -customElements.define('date-time-input', DateTimeInput) +customElements.define('date-time-input', DateTimeInput /*, { extends: 'input' } */) class PopupElement extends HTMLElement { - constructor () { + constructor() { super(); /* TODO populate remaining */ // this.id = 'popup' + this.dataset.uid } - redraw () { + redraw() { console.log('IMPLEMENT ME'); } connectedCallback() { - let body = document.getElementById('popup-template').content.cloneNode(true).firstElementChild; + let template: HTMLTemplateElement = document.getElementById('popup-template') as HTMLTemplateElement + let body = (template.content.cloneNode(true) as DocumentFragment).firstElementChild!; - let uid = this.dataset.uid + if (this.dataset.uid === null) { + throw 'UID is required' + } + let uid = this.dataset.uid! // console.log(uid); body.getElementsByClassName('populate-with-uid') @@ -295,43 +341,42 @@ class PopupElement extends HTMLElement { let tabgroup_id = gensym(); for (let tab of body.querySelectorAll(".tabgroup .tab")) { let new_id = gensym(); - let input = tab.querySelector("input"); + let input = tab.querySelector("input")!; input.id = new_id; input.name = tabgroup_id; - tab.querySelector("label").setAttribute('for', new_id); + tab.querySelector("label")!.setAttribute('for', new_id); } /* end tabs */ /* nav bar */ - let nav = body.getElementsByClassName("popup-control")[0]; + let nav = body.getElementsByClassName("popup-control")[0] as HTMLElement; bind_popup_control(nav); - let btn = body.querySelector('.popup-control .close-tooltip') - btn.addEventListener('click', () => { - close_popup(this); - }); + let btn = body.querySelector('.popup-control .close-tooltip') as HTMLButtonElement + 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; + this.getElementsByClassName("calendar-selection")[0] + .addEventListener('change', function() { + let uid = (that.closest('[data-uid]') as HTMLElement).dataset.uid! + let obj = vcal_objects.get(uid) + // TODO this procedure + // this.value; // event.properties.calendar = this.value; }); } } -window.addEventListener('load', function () { +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); +function wholeday_checkbox(box: HTMLInputElement) { + box.closest('.timeinput')! + .querySelectorAll('input[is="date-time"]') + .forEach((el) => { (el as DateTimeInput).dateonly = box.checked }); } -- cgit v1.2.3