From 1484155c211fe8452344ffdc501e858706ecbc51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Wed, 29 Sep 2021 23:36:21 +0200 Subject: Start rework on js setup. --- static/globals.js | 257 ++++++++++++++++++++++++++++++++ static/script.js | 437 +++++++++++++++++++++++++++--------------------------- static/vcal.js | 2 + 3 files changed, 477 insertions(+), 219 deletions(-) create mode 100644 static/globals.js (limited to 'static') diff --git a/static/globals.js b/static/globals.js new file mode 100644 index 00000000..b881c531 --- /dev/null +++ b/static/globals.js @@ -0,0 +1,257 @@ +"use strict"; + +class VEventValue { + constructor (type, value, parameters = {}) { + this.type = type; + this.value = value; + this.parameters = parameters; + } +} + +/* 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) { + el.redraw(this); + } + } + + register (htmlNode) { + this.registered.push(htmlNode); + } +} + +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 'uc-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) +} + +const vcal_objects = {}; + +class ComponentVEvent extends HTMLElement { + constructor () { + super (); + this.template = document.getElementById(this.tagName); + + /* 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 */ + } + + 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; + if ((d = data.getProperty(p))) { + /* TODO format */ + el.innerHTML = d; + } + } + + this.replaceChildren(body); + } + +} + +class ComponentDescription extends ComponentVEvent { + constructor () { + super() ; + } + +} + +class ComponentEdit extends ComponentVEvent { + constructor () { + super(); + + /* Edit tab is rendered here. It's left blank server-side, since + it only makes sense to have something here if we have javascript */ + this.redraw(vcal_objects[this.dataset.uid]); + } +} + +class ComponentBlock extends ComponentVEvent { + constructor () { + super(); + } +} + +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); +}) + diff --git a/static/script.js b/static/script.js index a0d58c27..05ae61c5 100644 --- a/static/script.js +++ b/static/script.js @@ -4,211 +4,211 @@ 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); - - /* -------------------- */ - /* 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 fractions - 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; - - /* 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); - - event.dataset.time1 = time; - event.dataset.time2 = time; - - /* ---------------------------------------- */ - - this.appendChild(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, d2); - 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); - - } - } -} +// 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); +// +// /* -------------------- */ +// /* 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 fractions +// 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; +// +// /* 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); +// +// event.dataset.time1 = time; +// event.dataset.time2 = time; +// +// /* ---------------------------------------- */ +// +// this.appendChild(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, d2); +// 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); +// +// } +// } +// } @@ -254,7 +254,7 @@ function place_in_edit_mode (event) { -window.onload = function () { +window.addEventListener('load', function () { // let start_time = document.querySelector("meta[name='start-time']").content; // let end_time = document.querySelector("meta[name='end-time']").content; @@ -280,7 +280,7 @@ window.onload = function () { init_date_time(); /* Is event creation active? */ - if (EDIT_MODE) { + if (false && EDIT_MODE) { let eventCreator = new EventCreator; for (let c of document.getElementsByClassName("events")) { c.onmousedown = eventCreator.create_event_down(c); @@ -336,17 +336,17 @@ window.onload = function () { el.parentElement.removeAttribute("href"); let popup = document.getElementById("popup" + el.id); - popup.getElementsByClassName("edit-form")[0].onsubmit = function () { - create_event(el); - return false; /* stop default */ - } + // popup.getElementsByClassName("edit-form")[0].onsubmit = function () { + // create_event(el); + // return false; /* stop default */ + // } /* Bind all vcomponent properties into javascript. */ - if (el.closest(".longevents")) { - new VComponent(el, true); - } else { - new VComponent(el, false); - } + // if (el.closest(".longevents")) { + // new VComponent(el, true); + // } else { + // new VComponent(el, false); + // } } @@ -402,7 +402,7 @@ window.onload = function () { // init_arbitary_kv(); - init_input_list(); + // init_input_list(); document.addEventListener('keydown', function (event) { @@ -413,5 +413,4 @@ window.onload = function () { event.preventDefault(); } }); -} - +}) diff --git a/static/vcal.js b/static/vcal.js index 93cfc028..f86c7f65 100644 --- a/static/vcal.js +++ b/static/vcal.js @@ -9,6 +9,8 @@ cdr a procedure which takes a slot and a value and binds the value to the slot. */ + +/* DEPRECATED */ class VComponent { constructor(el, wide_event=false) { -- cgit v1.2.3 From e28b54810bb42b21a069a1257cf5e59e06c735a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Thu, 30 Sep 2021 01:47:38 +0200 Subject: Replace today-button with web component. --- static/clock.js | 38 +++++++++++++++++++++++++++++++------- static/script.js | 9 +-------- 2 files changed, 32 insertions(+), 15 deletions(-) (limited to 'static') diff --git a/static/clock.js b/static/clock.js index 9642ebaf..240041a9 100644 --- a/static/clock.js +++ b/static/clock.js @@ -60,15 +60,39 @@ class SmallcalCellHighlight extends Clock { } } -/* Update [today] button */ -class ButtonUpdater extends Clock { - constructor(el, proc) { +/* -------------------------------------------------- */ + +class ClockElement extends HTMLElement { + constructor () { super(); - this.el = el; - this.proc = proc; } - update(now) { - this.proc(this.el, now); + 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 observerAttributes () { + 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/script.js b/static/script.js index 05ae61c5..580ee520 100644 --- a/static/script.js +++ b/static/script.js @@ -82,7 +82,7 @@ // } // // /* -// round_to: what start and end times should round to when dragging, in fractions +// 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. @@ -258,11 +258,6 @@ window.addEventListener('load', function () { // let start_time = document.querySelector("meta[name='start-time']").content; // let end_time = document.querySelector("meta[name='end-time']").content; - const button_updater = new ButtonUpdater( - document.getElementById("today-button"), - (e, d) => e.href = d.format('~Y-~m-~d') + ".html" - ); - const sch = new SmallcalCellHighlight( document.querySelector('.small-calendar')) @@ -273,7 +268,6 @@ window.addEventListener('load', function () { window.setInterval(() => { let d = new Date; timebar.update(d); - button_updater.update(d); sch.update(d); }, 1000 * 60); @@ -372,7 +366,6 @@ window.addEventListener('load', function () { href: target_href, innerHTML: gotodatebtn.innerHTML, }); - document.getElementById("today-button").href = target_href; gotodatebtn.replaceWith(golink); document.querySelector("#jump-to input[name='date']").onchange = function () { -- cgit v1.2.3 From 927bd58d3340965328f0ccd05f865175b49a6ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Fri, 1 Oct 2021 03:34:06 +0200 Subject: Got date-times working in new system. --- static/clock.js | 2 +- static/globals.js | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 4 deletions(-) (limited to 'static') diff --git a/static/clock.js b/static/clock.js index 240041a9..d33d603a 100644 --- a/static/clock.js +++ b/static/clock.js @@ -75,7 +75,7 @@ class ClockElement extends HTMLElement { this.update(new Date) } - static get observerAttributes () { + static get observedAttributes () { return ['timer_id'] } diff --git a/static/globals.js b/static/globals.js index b881c531..4ee3c62a 100644 --- a/static/globals.js +++ b/static/globals.js @@ -35,6 +35,7 @@ class VEvent { e.value = value; } for (let el of this.registered) { + /* TODO update correct fields, allow component to redraw themselves */ el.redraw(this); } } @@ -188,10 +189,13 @@ class ComponentVEvent extends HTMLElement { for (let el of body.getElementsByClassName("bind")) { let p = el.dataset.property; - let d; + let d, fmt; if ((d = data.getProperty(p))) { - /* TODO format */ - el.innerHTML = d; + if ((fmt = el.dataset.fmt)) { + el.innerHTML = d.format(fmt); + } else { + el.innerHTML = d; + } } } @@ -211,10 +215,61 @@ 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 */ this.redraw(vcal_objects[this.dataset.uid]); + + 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; + } + } + } class ComponentBlock extends ComponentVEvent { @@ -255,3 +310,38 @@ window.addEventListener('load', function () { customElements.define('vevent-block', ComponentBlock); }) + + +class DateTimeInput extends HTMLElement { + constructor () { + super(); + this.innerHTML = '' + } + + get value () { + let date = this.querySelector("[type='date']").value; + let time = this.querySelector("[type='time']").value; + return parseDate(date + 'T' + time) + } + + 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) -- cgit v1.2.3 From 81adf54a8a36beba9622c1929937c871a751b2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Fri, 1 Oct 2021 03:37:31 +0200 Subject: Remove old date_time system. --- static/binders.js | 34 ---------------------------------- static/date_time.js | 36 ------------------------------------ static/script.js | 2 -- 3 files changed, 72 deletions(-) delete mode 100644 static/date_time.js (limited to 'static') diff --git a/static/binders.js b/static/binders.js index a6e37189..ac83b284 100644 --- a/static/binders.js +++ b/static/binders.js @@ -114,37 +114,3 @@ function bind_wholeday(el, e) { } }); } - - -/* used for dtstart and dtend input boxes - init_date_time MUST be called beforehand -*/ -function bind_date_time(el, e) { - e.addEventListener('input', function () { - let dt = el.properties[e.name].value; - if (e.value == '') return; - let y, m, d, h, s; - switch (this.type) { - case 'date': - [y,m,d] = this.value.split('-') - dt.setYear(Number(y)/* - 1900 */); - dt.setMonth(Number(m) - 1); - dt.setDate(d); - break; - case 'time': - [h,m,s] = this.value.split(':') - dt.setHours(Number(h)); - dt.setMinutes(Number(m)); - dt.setSeconds(0); - break; - default: - console.log("How did you get here??? ", e); - } - - el.properties[e.name] = dt; - }); - - el.properties.get_callback_list(e.name).push( - [e, (s, v) => s.value = v.format("~Y-~m-~dT~H:~M")]); - -} diff --git a/static/date_time.js b/static/date_time.js deleted file mode 100644 index 8b7249dd..00000000 --- a/static/date_time.js +++ /dev/null @@ -1,36 +0,0 @@ -function init_date_time_single(dt) { - dt.time = dt.querySelector('[type=time]'); - dt.date = dt.querySelector('[type=date]'); - - Object.defineProperty(dt, 'value', { - get: () => (dt.date.value && dt.time.value) - // TODO wrapping tag - ? dt.date.value + "T" + dt.time.value - : "", - set: (v) => [dt.date.value, dt.time.value] = v.split("T"), - }); - - Object.defineProperty(dt, 'name', { - get: () => dt.attributes.name.value - }); - - dt._addEventListener = dt.addEventListener; - dt.addEventListener = function (field, proc) { - /* input events are propagated to children - other events target ourselves */ - switch (field) { - case 'input': - dt.time.addEventListener(field, proc); - dt.date.addEventListener(field, proc); - break; - default: - dt._addEventListener(field, proc); - } - } -} - -function init_date_time() { - for (let dt of document.getElementsByClassName("date-time")) { - init_date_time_single(dt); - } -} diff --git a/static/script.js b/static/script.js index 580ee520..b4fb7bda 100644 --- a/static/script.js +++ b/static/script.js @@ -271,8 +271,6 @@ window.addEventListener('load', function () { sch.update(d); }, 1000 * 60); - init_date_time(); - /* Is event creation active? */ if (false && EDIT_MODE) { let eventCreator = new EventCreator; -- cgit v1.2.3 From 98a56b782d1ced056c77019e88b4bcea4a270f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Fri, 1 Oct 2021 11:49:26 +0200 Subject: Reintroduce dateonly for date-time-input:s. --- static/binders.js | 24 ------------------------ static/globals.js | 40 ++++++++++++++++++++++++++++++++++++++-- static/lib.js | 28 +++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 27 deletions(-) (limited to 'static') diff --git a/static/binders.js b/static/binders.js index ac83b284..a3742aec 100644 --- a/static/binders.js +++ b/static/binders.js @@ -90,27 +90,3 @@ function bind_view(el, e) { let f = (s, v) => s.innerText = v.format(s.dataset && s.dataset.fmt); el.properties.get_callback_list(e.dataset.property).push([e, f]); } - - -function bind_wholeday(el, e) { - let popup = popup_from_event(el); - let wholeday = popup.querySelector("input[name='wholeday']"); - wholeday.addEventListener('click', function (event) { - for (let f of popup.querySelectorAll("input[type='time']")) { - f.disabled = wholeday.checked; - } - - for (let f of ['dtstart', 'dtend']) { - let param = el.properties[f]; - if (! param) continue; /* dtend optional */ - let d = param.value; - if (wholeday.checked) { - param.type = 'date'; - } else { - param.type = 'date-time'; - } - d.isWholeDay = wholeday.checked; - el.properties[f] = d; - } - }); -} diff --git a/static/globals.js b/static/globals.js index 4ee3c62a..fd576e26 100644 --- a/static/globals.js +++ b/static/globals.js @@ -318,10 +318,40 @@ class DateTimeInput extends HTMLElement { 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; - let time = this.querySelector("[type='time']").value; - return parseDate(date + 'T' + time) + 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) { @@ -345,3 +375,9 @@ class DateTimeInput extends HTMLElement { } customElements.define('date-time-input', DateTimeInput) + +function wholeday_checkbox (box) { + box.closest('.timeinput') + .getElementsByTagName('date-time-input') + .forEach(el => el.dateonly = box.checked); +} diff --git a/static/lib.js b/static/lib.js index 1d42100c..100f4161 100644 --- a/static/lib.js +++ b/static/lib.js @@ -129,6 +129,24 @@ function asList(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"); @@ -159,7 +177,8 @@ function format_date(date, str) { } return outstr; } -Object.prototype.format = function () { return "" + this; } /* any number of arguments */ + +Object.prototype.format = function (/* any number of arguments */) { return "" + this; } Date.prototype.format = function (str) { return format_date (this, str); } /* @@ -176,4 +195,11 @@ DOMTokenList.prototype.find = function (regexp) { } } +/* 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"; -- cgit v1.2.3 From a31bfa6686eb52506d600a5b77a96fddeb9fb344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Sun, 3 Oct 2021 00:47:50 +0200 Subject: to_jcal for new VEvent class. --- static/globals.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'static') diff --git a/static/globals.js b/static/globals.js index fd576e26..c414fc5b 100644 --- a/static/globals.js +++ b/static/globals.js @@ -6,6 +6,44 @@ class VEventValue { 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 ... */ @@ -43,6 +81,16 @@ class VEvent { 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) { -- cgit v1.2.3 From 0ddb299f256a6be037a904b84c4b2cdcdffe18c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Sun, 3 Oct 2021 00:48:04 +0200 Subject: Fix graphical block size. --- static/globals.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'static') diff --git a/static/globals.js b/static/globals.js index c414fc5b..6efba94f 100644 --- a/static/globals.js +++ b/static/globals.js @@ -324,6 +324,21 @@ class ComponentBlock extends ComponentVEvent { constructor () { super(); } + + redraw (data) { + super.redraw(data); + + let p; + if ((p = data.getProperty('dtstart'))) { + this.style.top = date_to_percent(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(p, 1)) + "%"; + } + } } window.addEventListener('load', function () { -- cgit v1.2.3 From a64c2a665af1abe0b91f1c5eb1f97df91ed8a4de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Sun, 3 Oct 2021 17:48:13 +0200 Subject: Further work, rework popup. --- static/dragable.js | 6 +- static/globals.js | 309 ++++++++++++--------------------------- static/popup.js | 12 +- static/script.js | 421 +++++++++++++++++++++++++++-------------------------- static/style.scss | 30 ++-- 5 files changed, 330 insertions(+), 448 deletions(-) (limited to 'static') diff --git a/static/dragable.js b/static/dragable.js index 41895760..6eb0b999 100644 --- a/static/dragable.js +++ b/static/dragable.js @@ -21,14 +21,16 @@ function bind_popup_control (nav) { 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-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-container"); + let popup = nav.closest("popup-element"); popup.style.left = startX + (e.clientX - x) + "px"; popup.style.top = startY + (e.clientY - y) + "px"; diff --git a/static/globals.js b/static/globals.js index 6efba94f..41472264 100644 --- a/static/globals.js +++ b/static/globals.js @@ -1,218 +1,5 @@ "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 */ - 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 'uc-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) -} - const vcal_objects = {}; class ComponentVEvent extends HTMLElement { @@ -226,6 +13,13 @@ class ComponentVEvent extends HTMLElement { 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 @@ -270,7 +64,14 @@ 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 */ - this.redraw(vcal_objects[this.dataset.uid]); + + 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', () => { @@ -320,9 +121,31 @@ 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'; +} + +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) { @@ -330,13 +153,13 @@ class ComponentBlock extends ComponentVEvent { let p; if ((p = data.getProperty('dtstart'))) { - this.style.top = date_to_percent(p, 1) + "%"; - console.log('dtstart', p); + 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(p, 1)) + "%"; + // console.log('dtend', p); + this.style.bottom = (100 - date_to_percent(to_local(p), 1)) + "%"; } } } @@ -439,6 +262,56 @@ class DateTimeInput extends HTMLElement { 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); + } +} + +window.addEventListener('load', function () { + customElements.define('popup-element', PopupElement) +}); + function wholeday_checkbox (box) { box.closest('.timeinput') .getElementsByTagName('date-time-input') diff --git a/static/popup.js b/static/popup.js index e19db6f2..0b04b280 100644 --- a/static/popup.js +++ b/static/popup.js @@ -2,12 +2,14 @@ /* event component => coresponding popup component */ function event_from_popup(popup) { - return document.getElementById(popup.id.substr(5)) + // 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 document.getElementById("popup" + event.id); + return find_popup(event.closest('[data-uid]').dataset.uid) } /* hides given popup */ @@ -17,7 +19,7 @@ function close_popup(popup) { /* hides all popups */ function close_all_popups () { - for (let popup of document.querySelectorAll(".popup-container.visible")) { + for (let popup of document.querySelectorAll("popup-element.visible")) { close_popup(popup); } } @@ -51,8 +53,8 @@ function open_popup(popup) { } /* toggles open/closed status of popup given by id */ -function toggle_popup(popup_id) { - let popup = document.getElementById(popup_id); +function toggle_popup(popup) { + // let popup = document.getElementById(popup_id); if (popup.classList.contains("visible")) { close_popup(popup); } else { diff --git a/static/script.js b/static/script.js index b4fb7bda..5ef498f3 100644 --- a/static/script.js +++ b/static/script.js @@ -4,211 +4,216 @@ 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); -// -// /* -------------------- */ -// /* 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; -// -// /* 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); -// -// event.dataset.time1 = time; -// event.dataset.time2 = time; -// -// /* ---------------------------------------- */ -// -// this.appendChild(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, d2); -// 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); -// -// } -// } -// } +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); + + event.dataset.time1 = time; + event.dataset.time2 = time; + + /* ---------------------------------------- */ + + this.appendChild(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, d2); + 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); + + } + } +} @@ -316,9 +321,9 @@ window.addEventListener('load', function () { } } - for (let nav of document.getElementsByClassName("popup-control")) { - bind_popup_control(nav); - } + // for (let nav of document.getElementsByClassName("popup-control")) { + // bind_popup_control(nav); + // } for (let el of document.getElementsByClassName("event")) { /* Popup script replaces need for anchors to events. diff --git a/static/style.scss b/static/style.scss index a29bb24b..4b4f573b 100644 --- a/static/style.scss +++ b/static/style.scss @@ -744,6 +744,21 @@ along with their colors. ---------------------------------------- */ +popup-element { + display: none; + position: absolute; + z-index: 1000; + + /* ??? */ + left: 10px; + top: -50px; + + box-shadow: gray 10px 10px 10px; + + &.visible { + display: block; + } +} .popup { display: flex; @@ -757,21 +772,6 @@ along with their colors. min-width: 60ch; min-height: 30ch; - &-container { - display: none; - position: absolute; - z-index: 1000; - - /* ??? */ - left: 10px; - top: -50px; - - box-shadow: gray 10px 10px 10px; - - &.visible { - display: block; - } - } input { white-space: initial; -- cgit v1.2.3 From 4cf9587a5188e5853bbcf97b71109e7cb9331d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Sun, 3 Oct 2021 21:54:30 +0200 Subject: work --- static/globals.js | 30 ++++++-- static/script.js | 100 ++++++++++++------------- static/style.scss | 3 + static/vevent.js | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 292 insertions(+), 55 deletions(-) create mode 100644 static/vevent.js (limited to 'static') 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