From 04dcab7a429d9b034d41b5aca8bd715c4826de32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Mon, 28 Sep 2020 03:10:54 +0200 Subject: Groundwork for adding new fields from frontend. --- module/calp/html/vcomponent.scm | 17 ++- module/calp/html/view/calendar.scm | 25 +++- static/script.js | 239 ++++++++++++++++++++++++++++++++++++- static/style.scss | 12 ++ 4 files changed, 287 insertions(+), 6 deletions(-) diff --git a/module/calp/html/vcomponent.scm b/module/calp/html/vcomponent.scm index 54bfb9e8..2497aa04 100644 --- a/module/calp/html/vcomponent.scm +++ b/module/calp/html/vcomponent.scm @@ -192,10 +192,19 @@ (type "text") )))) - #; - (input (@ (type "text") - (list "known-fields") - (placeholder "Nytt fält"))) + (hr) + + (div (@ (class "newfield")) + (input (@ (type "text") + (list "known-fields") + (placeholder "Nytt fält"))) + (select (@ (name "TYPE")) + (option (@ (value "TEXT")) "Text")) + (span + (input (@ (type "text") + (placeholder "Värde"))))) + + (hr) (input (@ (type "submit"))) diff --git a/module/calp/html/view/calendar.scm b/module/calp/html/view/calendar.scm index a583d82b..64986b5c 100644 --- a/module/calp/html/view/calendar.scm +++ b/module/calp/html/view/calendar.scm @@ -296,4 +296,27 @@ ;; TODO merge this into the event-set, add attribute ;; for non-displaying elements. (div (@ (class "template") (id "popup-template")) - ,(popup event (string-append "popup" (html-id event))))))))) + ,(popup event (string-append "popup" (html-id event)))))) + + ;; Auto-complets when adding new fields to a component + ;; Any string is however still valid. + (datalist (@ (id "known-fields")) + ,@(map (lambda (f) + `(option (@ (value ,f)))) + '(CALSCALE + METHOD PRODID VERSION ATTACH + CATEGORIES CLASS COMMENT + DESCRIPTION GEO LOCATION + PERCENT-COMPLETE PRIORITY + RESOURCES STATUS SUMMARY + COMPLETED DTEND DUE DTSTART + DURATION FREEBUSY + TRANSP TZID TZNAME + TZOFFSETFROM TZOFFSETTO + TZURL ATTENDEE CONTACT + ORGANIZER RECURRENCE-ID + RELATED-TO URL EXDATE + RDATE RRULE ACTION REPEAT + TRIGGER CREATED DTSTAMP LAST-MODIFIED + SEQUENCE REQUEST-STATUS + )))))) diff --git a/static/script.js b/static/script.js index 069d9a2e..b94d0f2b 100644 --- a/static/script.js +++ b/static/script.js @@ -628,6 +628,222 @@ window.onload = function () { el.oninput = update_inline_list; } + + for (let el of document.getElementsByClassName("newfield")) { + let [name, type_selector, value_field] = el.children; + + /* TODO list fields */ + /* TODO add and remove fields. See update_inline_list */ + + function update_value_field (el) { + let [name_field, type_selector, value_field] = el.children; + + + let value = makeElement('input'); + let values = [value]; + + + switch (name_field.value.toUpperCase()) { + case 'GEO': + value.type = 'number'; + values.push(makeElement('input', { + type: 'number', + })); + break; + + case 'CLASS': + // Add auto completion + break; + + case 'ACTION': + // Add auto completion + break; + + case 'TRANSP': + // Replace with toggle betwen OPAQUE and TRANSPARENT + break; + + case 'PERCENT-COMPLETE': + value.min = 0; + value.max = 100; + break; + + case 'PRIORITY': + value.min = 0; + value.max = 9; + break; + + default: + + + switch (type_selector.options[type_selector.selectedIndex].value) { + case 'integer': + case 'float': + value.type = 'number'; + break; + + case 'uri': + value.type = 'url'; + break; + + case 'binary': + value.type = 'file'; + break; + + case 'date-time': + values.push(makeElement('input', { + type: 'time', + })); + /* fallthrough */ + case 'date': + value.type = 'date'; + break; + + case 'cal-address': + value.type = 'email'; + break; + + case 'utc-offset': + value.type = 'time'; + let lbl = makeElement('label'); + let id = gensym(); + + lbl.setAttribute('for', id); + + /* TODO make these labels stand out more */ + lbl.appendChild(makeElement('span', { + className: 'plus', + innerText: '+', + })); + lbl.appendChild(makeElement('span', { + className: 'minus', + innerText: '-', + })); + values.splice(0,0,lbl); + values.splice(0,0, makeElement('input', { + type: 'checkbox', + style: 'display:none', + className: 'plusminuscheck', + id: id, + })); + break; + + case 'period': + value.type = 'text'; + // TODO validate /P\d*H/ typ + break; + + case 'recur': + // TODO + default: + value.type = 'text'; + } + } + + + value_field.innerHTML = ''; + for (let v of values) { + console.log(v); + value_field.appendChild(v); + } + } + + name.oninput = function () { + let types = valid_input_types[this.value.toUpperCase()]; + type_selector.disabled = false; + if (types) { + type_selector.innerHTML = ''; + for (let type of types) { + type_selector.appendChild( + makeElement('option', { value: type, innerText: type })) + } + if (types.length == 1) { + type_selector.disabled = true; + } + } else { + type_selector.innerHTML = ''; + for (let type of all_types) { + type_selector.appendChild( + makeElement('option', { value: type, innerText: type })) + } + } + + update_value_field(el); + } + type_selector.onchange = function () { + update_value_field(el); + } + } + +} + +let all_types = [ + 'text', + 'uri', + 'binary', + 'float', + 'integer', + 'date-time', + 'date', + 'duration', + 'period', + 'utc-offset', + 'cal-address', + 'recur', +] + + +/* TODO + Todo map for which properties are valid on which object types +*/ + + +let valid_input_types = { + 'CALSCALE': ['text'], + 'METHOD': ['text'], + 'PRODID': ['text'], + 'VERSION': ['text'], + 'ATTACH': ['uri', 'binary'], + 'CATEGORIES': [['text']], + 'CLASS': ['text'], // PUBLIC|PRIVATE|CONFIDENTIAL|*other* + 'COMMENT': ['text'], + 'DESCRIPTION': ['text'], + 'GEO': ['float'], // pair of floats + 'LOCATION': ['text'], + 'PERCENT-COMPLETE': ['integer'], // 0-100 + 'PRIORITY': ['integer'], // 0-9 + 'RESOURCES': [['text']], + 'STATUS': ['text'], // see 3.8.1.11 + 'SUMMARY': ['text'], + 'COMPLETED': ['date-time'], + 'DTEND': ['date', 'date-time'], + 'DUE': ['date', 'date-time'], + 'DTSTART': ['date', 'date-time'], + 'DURATION': ['duration'], + 'FREEBUSY': [['period']], + 'TRANSP': ['text'], // OPAQUE|TRANSPARENT + 'TZID': ['text'], + 'TZNAME': ['text'], + 'TZOFFSETFROM': ['utc-offset'], + 'TZOFFSETTO': ['utc-offset'], + 'TZURL': ['uri'], + 'ATTENDEE': ['cal-address'], + 'CONTACT': ['text'], + 'ORGANIZER': ['cal-address'], + 'RECURRENCE-ID': ['date', 'date-time'], + 'RELATED-TO': ['text'], + 'URL': ['uri'], + 'EXDATE': [['date', 'date-time']], + 'RDATE': [['date', 'date-time', 'period']], + 'RRULE': ['recur'], + 'ACTION': ['text'], // AUDIO|DISPLAY|EMAIL|*other* + 'REPEAT': ['integer'], + 'TRIGGER': ['duration', 'date-time'], + 'CREATED': ['date-time'], + 'DTSTAMP': ['date-time'], + 'LAST-MODIFIED': ['date-time'], + 'SEQUENCE': ['integer'], + 'REQUEST-STATUS': ['text'] } function close_popup(popup) { @@ -754,7 +970,8 @@ class vcomponent { function bind_properties (el, wide_event=false) { el.properties = {} let popup = document.getElementById("popup" + el.id); - let children = el.getElementsByTagName("properties")[0].children; + // let children = el.getElementsByTagName("properties")[0].children; + let children = el.querySelector("vevent > properties").children; for (let child of children) { let field = child.tagName; @@ -771,6 +988,26 @@ function bind_properties (el, wide_event=false) { lst.push([s, f]); } + + /* edit tab */ + for (let s of popup.querySelectorAll(`[name='${field}']`)) { + let f; + s.oninput = function () { + el.properties[s.name] = this.value; + } + switch (s.name) { + case 'description': + f = ((s, v) => s.innerHTML = v.format(s.dataset && s.dataset.fmt)); + lst.push([s, f]); + break; + default: + f = ((s, v) => s.value = v); + lst.push([s, f]); + break; + } + } + + /* Bind vcomponent fields for this event */ for (let s of el.querySelectorAll(field + " > :not(parameters)")) { switch (s.tagName) { diff --git a/static/style.scss b/static/style.scss index c2848940..d69e8bcf 100644 --- a/static/style.scss +++ b/static/style.scss @@ -884,6 +884,18 @@ along with their colors. } +.plusminuschecked label { + color: black; +} + +.plusminuscheck:checked ~ label .plus { + color: green; +} + +.plusminuscheck:not(:checked) ~ label .minus { + color: red; +} + .inline-edit { input { /* important since regular spec is much stronger...*/ -- cgit v1.2.3