diff options
author | Hugo Hörnquist <hugo@lysator.liu.se> | 2021-10-03 21:54:30 +0200 |
---|---|---|
committer | Hugo Hörnquist <hugo@lysator.liu.se> | 2021-10-03 21:54:30 +0200 |
commit | 4cf9587a5188e5853bbcf97b71109e7cb9331d9f (patch) | |
tree | f6f36930e263f3fee450a700b8e77da667c1afa9 /static | |
parent | Further work, rework popup. (diff) | |
download | calp-4cf9587a5188e5853bbcf97b71109e7cb9331d9f.tar.gz calp-4cf9587a5188e5853bbcf97b71109e7cb9331d9f.tar.xz |
work
Diffstat (limited to '')
-rw-r--r-- | static/globals.js | 30 | ||||
-rw-r--r-- | static/script.js | 100 | ||||
-rw-r--r-- | static/style.scss | 3 | ||||
-rw-r--r-- | static/vevent.js | 214 |
4 files changed, 292 insertions, 55 deletions
diff --git a/static/globals.js b/static/globals.js index 41472264..86368e9a 100644 --- a/static/globals.js +++ b/static/globals.js @@ -7,6 +7,11 @@ class ComponentVEvent extends HTMLElement { 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) @@ -122,12 +127,13 @@ class ComponentEdit extends ComponentVEvent { } function find_popup (uid) { - for (let el of vcal_objects[uid].registered) { - if (el.tagName === 'popup-element') { - return el; - } - } - throw 'Popup not fonud'; + // 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) { @@ -180,6 +186,7 @@ window.addEventListener('load', function () { - .block - .list */ + /* let vevent_els = document.getElementsByClassName('vevent') for (let el of vevent_els) { try { @@ -190,6 +197,7 @@ window.addEventListener('load', function () { ); } } + */ customElements.define('vevent-description', ComponentDescription); customElements.define('vevent-edit', ComponentEdit); @@ -305,6 +313,16 @@ class PopupElement extends HTMLElement { /* 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; + }); + } } diff --git a/static/script.js b/static/script.js index 5ef498f3..16ff7bbd 100644 --- a/static/script.js +++ b/static/script.js @@ -28,14 +28,14 @@ class EventCreator { /* 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); - } + // 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 */ - } + // popup.getElementsByClassName("edit-form")[0].onsubmit = function () { + // create_event(event); + // return false; /* stop default */ + // } /* -------------------- */ /* Fix tabs for newly created popup */ @@ -128,12 +128,12 @@ class EventCreator { */ let time = round_time(pos_in(this, e), round_to); - event.dataset.time1 = time; - event.dataset.time2 = time; + that.event.dataset.time1 = time; + that.event.dataset.time2 = time; /* ---------------------------------------- */ - this.appendChild(event); + this.appendChild(that.event); /* requires that event is child of an '.event-container'. */ // new VComponent( @@ -186,9 +186,11 @@ class EventCreator { let d2 = new Date(container_start.getTime() + end_in_duration) // console.log(that.event); - console.log(d1, d2); - that.event.properties.dtstart = d1; - that.event.properties.dtend = d2; + 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; } } @@ -204,7 +206,7 @@ class EventCreator { e.style.pointerEvents = ""; } - place_in_edit_mode(that.event); + // place_in_edit_mode(that.event); let localevent = that.event; that.event = null; @@ -222,40 +224,40 @@ class EventCreator { 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 <option> first gives us the - * input once selected */ - calendar_dropdown.onchange = function () { - event.properties.calendar = this.value; - } - container.appendChild(calendar_dropdown); - - let tab = popup.getElementsByClassName("tab")[1]; - let radio = tab.getElementsByTagName("input")[0]; - radio.click(); - tab.querySelector("input[name='summary']").focus(); -} +// 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 <option> first gives us the +// * input once selected */ +// calendar_dropdown.onchange = function () { +// event.properties.calendar = this.value; +// } +// container.appendChild(calendar_dropdown); +// +// let tab = popup.getElementsByClassName("tab")[1]; +// let radio = tab.getElementsByTagName("input")[0]; +// radio.click(); +// tab.querySelector("input[name='summary']").focus(); +// } @@ -277,7 +279,7 @@ window.addEventListener('load', function () { }, 1000 * 60); /* Is event creation active? */ - if (false && EDIT_MODE) { + if (true && EDIT_MODE) { let eventCreator = new EventCreator; for (let c of document.getElementsByClassName("events")) { c.onmousedown = eventCreator.create_event_down(c); diff --git a/static/style.scss b/static/style.scss index 4b4f573b..cc1ae15b 100644 --- a/static/style.scss +++ b/static/style.scss @@ -772,6 +772,9 @@ popup-element { min-width: 60ch; min-height: 30ch; + select { + max-width: 100%; + } input { white-space: initial; diff --git a/static/vevent.js b/static/vevent.js new file mode 100644 index 00000000..678f2134 --- /dev/null +++ b/static/vevent.js @@ -0,0 +1,214 @@ +"use strict"; + +class VEventValue { + constructor (type, value, parameters = {}) { + this.type = type; + this.value = value; + this.parameters = parameters; + } + + to_jcal () { + let value; + let v = this.value; + switch (this.type) { + case 'binary': + /* TOOD */ + break; + case 'date-time': + value = v.format("~Y-~m-~dT~H:~M:~S"); + // TODO TZ + break; + case 'date': + value = v.format("~Y-~m-~d"); + break; + case 'duration': + /* TODO */ + break; + case 'period': + /* TODO */ + break; + case 'utc-offset': + /* TODO */ + break; + case 'recur': + value = v.asJcal(); + break; + + case 'float': + case 'integer': + case 'text': + case 'uri': + case 'cal-address': + case 'boolean': + value = v; + } + return [this.parameters, this.type, value]; + } +} + +/* maybe ... */ +class VEventDuration extends VEventValue { +} + +class VEvent { + constructor (properties = {}, components = []) { + this.properties = properties; + this.components = components; + this.registered = []; + } + + getProperty (key) { + let e = this.properties[key]; + if (! e) return e; + return e.value; + } + + setProperty (key, value) { + let e = this.properties[key]; + if (! e) { + let type = (valid_input_types[key.toUpperCase()] || ['unknown'])[0] + if (typeof type === typeof []) type = type[0]; + e = this.properties[key] = new VEventValue(type, value); + } else { + e.value = value; + } + for (let el of this.registered) { + /* TODO update correct fields, allow component to redraw themselves */ + console.log(el); + el.redraw(this); + } + } + + register (htmlNode) { + this.registered.push(htmlNode); + } + + to_jcal () { + let out_properties = [] + for (let [key, value] of Object.entries(this.properties)) { + let sub = value.to_jcal(); + sub.unshift(key) + out_properties.push(sub); + } + return ['vevent', out_properties, [/* alarms go here*/]] + } +} + +function make_vevent_value (value_tag) { + /* TODO parameters */ + return new VEventValue (value_tag.tagName, make_vevent_value_ (value_tag)); +} + +function make_vevent_value_ (value_tag) { + /* RFC6321 3.6. */ + switch (value_tag.tagName) { + case 'binary': + /* Base64 to binary + Seems to handle inline whitespace, which xCal standard reqires + */ + return atob(value_tag.innerHTML) + break; + + case 'boolean': + switch (value_tag.innerHTML) { + case 'true': return true; + case 'false': return false; + default: + console.warn(`Bad boolean ${value_tag.innerHTML}, defaulting with !!`) + return !! value_tag.innerHTML; + } + break; + + case 'time': + case 'date': + case 'date-time': + return parseDate(value_tag.innerHTML); + break; + + case 'duration': + /* TODO duration parser here 'P1D' */ + return value_tag.innerHTML; + break; + + case 'float': + case 'integer': + return +value_tag.innerHTML; + break; + + case 'period': + /* TODO has sub components, meaning that a string wont do */ + let start = value_tag.getElementsByTagName('start')[0] + parseDate(start.innerHTML); + let other; + if ((other = value_tag.getElementsByTagName('end')[0])) { + return parseDate(other.innerHTML) + } else if ((other = value_tag.getElementsByTagName('duration')[0])) { + /* TODO parse duration */ + return other.innerHTML + } else { + console.warn('Invalid end to period, defaulting to 1H'); + return new Date(3600); + } + + case 'recur': + /* TODO parse */ + return ""; + + case 'utc-offset': + /* TODO parse */ + return ""; + + default: + console.warn(`Unknown type '${value_tag.tagName}', defaulting to string`) + case 'cal-address': + case 'uri': + case 'text': + return value_tag.innerHTML; + } +} + +/* xml dom object -> class VEvent */ +function xml_to_vcal (xml) { + /* xml MUST have a VEVENT (or equivalent) as its root */ + let properties = xml.getElementsByTagName('properties')[0]; + let components = xml.getElementsByTagName('components')[0]; + + let property_map = {} + if (properties) { + for (var i = 0; i < properties.childElementCount; i++) { + let tag = properties.childNodes[i]; + let parameters = {}; + let value = []; + for (var j = 0; j < tag.childElementCount; j++) { + let child = tag.childNodes[j]; + switch (tag.tagName) { + case 'parameters': + parameters = /* handle parameters */ {}; + break; + + /* These can contain multiple value tags, per + RFC6321 3.4.1.1. */ + case 'categories': + case 'resources': + case 'freebusy': + case 'exdate': + case 'rdate': + value.push(make_vevent_value(child)); + break; + default: + value = make_vevent_value(child); + } + } + property_map[tag.tagName] = value; + } + } + + let component_list = [] + if (components) { + for (let child of components.childNodes) { + component_list.push(xml_to_vcal(child)) + } + } + + return new VEvent(property_map, component_list) +} |