aboutsummaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2021-10-03 21:54:30 +0200
committerHugo Hörnquist <hugo@lysator.liu.se>2021-10-03 21:54:30 +0200
commit4cf9587a5188e5853bbcf97b71109e7cb9331d9f (patch)
treef6f36930e263f3fee450a700b8e77da667c1afa9 /static
parentFurther work, rework popup. (diff)
downloadcalp-4cf9587a5188e5853bbcf97b71109e7cb9331d9f.tar.gz
calp-4cf9587a5188e5853bbcf97b71109e7cb9331d9f.tar.xz
work
Diffstat (limited to 'static')
-rw-r--r--static/globals.js30
-rw-r--r--static/script.js100
-rw-r--r--static/style.scss3
-rw-r--r--static/vevent.js214
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)
+}