aboutsummaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2021-11-26 15:32:41 +0100
committerHugo Hörnquist <hugo@lysator.liu.se>2021-11-26 15:32:41 +0100
commit1df15b2ceaef09b48a39aa6046b577da11ea2f72 (patch)
treecf8dd5340703961c53daae1b2e7d0535d785f6d4 /static
parentSlightly better error hnadling in directory-table. (diff)
downloadcalp-1df15b2ceaef09b48a39aa6046b577da11ea2f72.tar.gz
calp-1df15b2ceaef09b48a39aa6046b577da11ea2f72.tar.xz
Got categories working.
Diffstat (limited to 'static')
-rw-r--r--static/components/input-list.ts101
-rw-r--r--static/components/vevent-edit.ts8
-rw-r--r--static/types.ts8
-rw-r--r--static/vevent.ts114
4 files changed, 181 insertions, 50 deletions
diff --git a/static/components/input-list.ts b/static/components/input-list.ts
index 326cb2b5..899e8f4f 100644
--- a/static/components/input-list.ts
+++ b/static/components/input-list.ts
@@ -2,11 +2,15 @@ export { InputList }
/* This file replaces input_list.js */
+/*
+ TODO allow each item to be a larger unit, possibly containing multiple input
+ fields.
+*/
class InputList extends HTMLElement {
el: HTMLInputElement;
- values: [HTMLInputElement, any][] = [];
+ _listeners: [string, (e: Event) => void][] = [];
constructor() {
super();
@@ -14,20 +18,27 @@ class InputList extends HTMLElement {
}
connectedCallback() {
+ for (let child of this.children) {
+ child.remove();
+ }
this.addInstance();
}
- addInstance() {
+ createInstance(): HTMLInputElement {
let new_el = this.el.cloneNode(true) as HTMLInputElement
let that = this;
new_el.addEventListener('input', function() {
+ /* TODO .value is empty both if it's actually empty, but also
+ for invalid input. Check new_el.validity, and new_el.validationMessage
+ */
if (new_el.value === '') {
- let sibling = this.previousElementSibling || this.nextElementSibling;
- // this.remove();
- // that.values = that.values.filter((p) => p[0] == this)
- that.values = that.values.filter((p) => p[0] != this);
- this.remove();
- (sibling as HTMLInputElement).focus();
+ let sibling = (this.previousElementSibling || this.nextElementSibling)
+ /* Only remove ourselves if we have siblings
+ Otherwise we just linger */
+ if (sibling) {
+ this.remove();
+ (sibling as HTMLInputElement).focus();
+ }
} else {
if (!this.nextElementSibling) {
that.addInstance();
@@ -36,24 +47,76 @@ class InputList extends HTMLElement {
}
}
});
- this.values.push([new_el, ''])
- // this.appendChild(new_el);
- this.replaceChildren(... this.values.map((p) => p[0]))
+
+ for (let [type, proc] of this._listeners) {
+ new_el.addEventListener(type, proc);
+ }
+
+ return new_el;
+ }
+
+ addInstance() {
+ let new_el = this.createInstance();
+ this.appendChild(new_el);
}
get value(): any[] {
- return []
+ let value_list = []
+ for (let child of this.children) {
+ value_list.push((child as any).value);
+ }
+ if (value_list[value_list.length - 1] === '') {
+ value_list.pop();
+ }
+ return value_list
}
set value(new_value: any[]) {
- let els = [];
+
+ let all_equal = true;
+ for (let i = 0; i < this.children.length; i++) {
+ let sv = (this.children[i] as any).value
+ all_equal
+ &&= (sv == new_value[i])
+ || (sv === '' && new_value[i] == undefined)
+ }
+ if (all_equal) return;
+
+ /* Copy our current input elements into a dictionary.
+ This allows us to only create new elements where needed
+ */
+ let values = new Map;
+ for (let child of this.children) {
+ values.set((child as HTMLInputElement).value, child);
+ }
+
+ let output_list: HTMLInputElement[] = []
for (let value of new_value) {
- let new_el = this.el.cloneNode() as HTMLInputElement;
- new_el.value = value;
- els.push(new_el);
+ let element;
+ /* Only create element if needed */
+ if ((element = values.get(value))) {
+ output_list.push(element)
+ /* clear dictionary */
+ values.set(value, false);
+ } else {
+ let new_el = this.createInstance();
+ new_el.value = value;
+ output_list.push(new_el);
+ }
+ }
+ /* final, trailing, element */
+ output_list.push(this.createInstance());
+
+ this.replaceChildren(...output_list);
+ }
+
+ addEventListener(type: string, proc: ((e: Event) => void)) {
+ // if (type != 'input') throw "Only input supported";
+
+ this._listeners.push([type, proc])
+
+ for (let child of this.children) {
+ child.addEventListener(type, proc);
}
- /* Final element (empty) */
- els.push(this.el.cloneNode() as HTMLInputElement);
- this.replaceChildren(...els);
}
}
diff --git a/static/components/vevent-edit.ts b/static/components/vevent-edit.ts
index 4408cbb8..b9b733a0 100644
--- a/static/components/vevent-edit.ts
+++ b/static/components/vevent-edit.ts
@@ -1,9 +1,10 @@
export { ComponentEdit }
import { ComponentVEvent } from './vevent'
+import { InputList } from './input-list'
import { DateTimeInput } from './date-time-input'
-import { vcal_objects, event_calendar_mapping } from '../globals'
+import { vcal_objects } from '../globals'
import { VEvent } from '../vevent'
import { create_event } from '../server_connect'
@@ -55,18 +56,21 @@ class ComponentEdit extends ComponentVEvent {
// for (let el of this.getElementsByClassName("interactive")) {
for (let el of this.querySelectorAll("[data-property]")) {
// console.log(el);
- el.addEventListener('input', () => {
+ el.addEventListener('input', (e) => {
let obj = vcal_objects.get(this.uid)
+ console.log(el, e);
if (obj === undefined) {
throw 'No object with uid ' + this.uid
}
if (!(el instanceof HTMLInputElement
|| el instanceof DateTimeInput
|| el instanceof HTMLTextAreaElement
+ || el instanceof InputList
)) {
console.log(el, 'not an HTMLInputElement');
return;
}
+ // console.log(`obj[${el.dataset.property!}] = `, el.value);
obj.setProperty(
el.dataset.property!,
el.value)
diff --git a/static/types.ts b/static/types.ts
index 2c26308e..567b9a95 100644
--- a/static/types.ts
+++ b/static/types.ts
@@ -129,7 +129,7 @@ type known_ical_types
| 'URL'
| 'VERSION'
-let valid_input_types: Map<string, ical_type | ical_type[]> =
+let valid_input_types: Map<string, Array<ical_type | ical_type[]>> =
new Map([
['ACTION', ['text']], // AUDIO|DISPLAY|EMAIL|*other*
['ATTACH', ['uri', 'binary']],
@@ -178,7 +178,7 @@ let valid_input_types: Map<string, ical_type | ical_type[]> =
['UID', ['text']],
['URL', ['uri']],
['VERSION', ['text']],
- ]) as Map<string, ical_type | ical_type[]>
+ ])
// type JCalLine {
// }
@@ -191,7 +191,9 @@ type uid = string
What really are valid values for any? Does that depend on ical_type? Why is the tail a list?
What really is the type for the parameter map?
*/
-type JCalProperty = [string, Map<string, any>, ical_type, any[]]
+type JCalProperty
+ = [string, Record<string, any>, ical_type, any]
+ | [string, Record<string, any>, ical_type, ...any[]]
type JCal = [tagname, JCalProperty[], JCal[]]
diff --git a/static/vevent.ts b/static/vevent.ts
index 9bfd8dcf..12d8267f 100644
--- a/static/vevent.ts
+++ b/static/vevent.ts
@@ -14,6 +14,9 @@ interface Redrawable extends HTMLElement {
class VEventValue {
type: ical_type
+
+ /* value should NEVER be a list, since multi-valued properties should
+ be split into multiple VEventValue objects! */
value: any
parameters: Map<string, any>
@@ -23,12 +26,13 @@ class VEventValue {
this.parameters = parameters;
}
- to_jcal(): [Map<string, any>, ical_type, any] {
+ to_jcal(): [Record<string, any>, ical_type, any] {
let value;
let v = this.value;
switch (this.type) {
case 'binary':
/* TOOD */
+ value = 'BINARY DATA GOES HERE';
break;
case 'date-time':
value = v.format("~Y-~m-~dT~H:~M:~S");
@@ -39,12 +43,15 @@ class VEventValue {
break;
case 'duration':
/* TODO */
+ value = 'DURATION GOES HERE';
break;
case 'period':
/* TODO */
+ value = 'PERIOD GOES HERE';
break;
case 'utc-offset':
/* TODO */
+ value = 'UTC-OFFSET GOES HERE';
break;
case 'recur':
value = v.asJcal();
@@ -58,7 +65,8 @@ class VEventValue {
case 'boolean':
value = v;
}
- return [this.parameters, this.type, value];
+
+ return [this.parameters, this.type, value]
}
}
@@ -66,6 +74,10 @@ class VEventValue {
class VEventDuration extends VEventValue {
}
+type list_values
+ = 'categories' | 'resources' | 'freebusy' | 'exdate' | 'rdate'
+ | 'CATEGORIES' | 'RESOURCES' | 'FREEBUSY' | 'EXDATE' | 'RDATE';
+
/*
Abstract representation of a calendar event (or similar).
All "live" calendar data in the frontend should live in an object of this type.
@@ -73,7 +85,7 @@ All "live" calendar data in the frontend should live in an object of this type.
class VEvent {
/* Calendar properties */
- properties: Map<uid, VEventValue>
+ properties: Map<string, VEventValue | VEventValue[]>
/* Children (such as alarms for events) */
components: VEvent[]
@@ -85,7 +97,10 @@ class VEvent {
_calendar: string | null = null;
- constructor(properties: Map<string, VEventValue> = new Map(), components: VEvent[] = []) {
+ constructor(
+ properties: Map<string, VEventValue | VEventValue[]> = new Map(),
+ components: VEvent[] = []
+ ) {
this.components = components;
this.registered = [];
/* Re-normalize all given keys to upper case. We could require
@@ -98,10 +113,18 @@ class VEvent {
}
}
- getProperty(key: string): any | undefined {
+ getProperty(key: list_values): any[] | undefined;
+ getProperty(key: string): any | undefined;
+
+ // getProperty(key: 'categories'): string[] | undefined
+
+ getProperty(key: string): any | any[] | undefined {
key = key.toUpperCase()
let e = this.properties.get(key);
if (!e) return e;
+ if (Array.isArray(e)) {
+ return e.map(ee => ee.value)
+ }
return e.value;
}
@@ -110,29 +133,52 @@ class VEvent {
}
__setPropertyInternal(key: string, value: any, type?: ical_type) {
+ function resolve_type(key: string, type?: ical_type): ical_type {
+ if (type) {
+ return type;
+ } else {
+ let type_options = valid_input_types.get(key)
+ if (type_options === undefined) {
+ type = 'unknown'
+ } else if (type_options.length == 0) {
+ type = 'unknown'
+ } else {
+ if (Array.isArray(type_options[0])) {
+ type = type_options[0][0]
+ } else {
+ type = type_options[0]
+ }
+ }
+ return type;
+ }
+ }
+
key = key.toUpperCase();
- let e = this.properties.get(key);
- if (e) {
- if (type) { e.type = type; }
- e.value = value;
+ if (Array.isArray(value)) {
+ this.properties.set(key,
+ value.map(el => new VEventValue(resolve_type(key, type), el)))
return;
}
- if (!type) {
- let type_ = valid_input_types.get(key)
- if (type_ === undefined) {
- type = 'unknown'
- } else if (type_ instanceof Array) {
- type = type_[0]
+ let current = this.properties.get(key);
+ if (current) {
+ if (Array.isArray(current)) {
} else {
- type = type_
+ if (type) { current.type = type; }
+ current.value = value;
+ return;
}
}
- e = new VEventValue(type, value)
- this.properties.set(key, e);
+ type = resolve_type(key, type);
+ let new_value = new VEventValue(type, value)
+ this.properties.set(key, new_value);
}
+ setProperty(key: list_values, value: any[], type?: ical_type): void;
+ setProperty(key: string, value: any, type?: ical_type): void;
+
setProperty(key: string, value: any, type?: ical_type) {
this.__setPropertyInternal(key, value, type);
+
for (let el of this.registered) {
el.redraw(this);
}
@@ -167,12 +213,27 @@ class VEvent {
let out_properties: JCalProperty[] = []
console.log(this.properties);
for (let [key, value] of this.properties) {
- let prop: JCalProperty = [
- key.toLowerCase(),
- ...value.to_jcal(),
- ]
- out_properties.push(prop);
+ console.log("key = ", key, ", value = ", value);
+ if (Array.isArray(value)) {
+ if (value.length == 0) continue;
+ let mostly = value.map(v => v.to_jcal())
+ let values = mostly.map(x => x[2])
+ console.log("mostly", mostly)
+ out_properties.push([
+ key.toLowerCase(),
+ mostly[0][0],
+ mostly[0][1],
+ ...values
+ ])
+ } else {
+ let prop: JCalProperty = [
+ key.toLowerCase(),
+ ...value.to_jcal(),
+ ]
+ out_properties.push(prop);
+ }
}
+
return ['vevent', out_properties, [/* alarms go here*/]]
}
}
@@ -209,7 +270,7 @@ class RecurrenceRule {
bysetpos?: number[]
wkst?: weekday
- to_jcal() {
+ to_jcal(): Record<string, any> {
let obj: any = {}
if (this.freq) obj['freq'] = this.freq;
if (this.until) obj['until'] = this.until.format(this.until.dateonly
@@ -386,7 +447,7 @@ function xml_to_vcal(xml: Element): VEvent {
let properties = xml.getElementsByTagName('properties')[0];
let components = xml.getElementsByTagName('components')[0];
- let property_map = new Map()
+ let property_map: Map<string, VEventValue | VEventValue[]> = new Map;
if (properties) {
property_loop:
for (var i = 0; i < properties.childElementCount; i++) {
@@ -394,7 +455,8 @@ function xml_to_vcal(xml: Element): VEvent {
if (!(tag instanceof Element)) continue;
let parameters = {};
let value: VEventValue | VEventValue[] = [];
- value_loop: for (var j = 0; j < tag.childElementCount; j++) {
+ value_loop:
+ for (var j = 0; j < tag.childElementCount; j++) {
let child = tag.childNodes[j];
if (!(child instanceof Element)) continue;
if (child.tagName == 'parameters') {