aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--static/components/date-time-input.ts77
-rw-r--r--static/components/popup-element.ts100
-rw-r--r--static/components/tab-element.ts40
-rw-r--r--static/components/vevent-block.ts63
-rw-r--r--static/components/vevent-description.ts12
-rw-r--r--static/components/vevent-dl.ts24
-rw-r--r--static/components/vevent-edit.ts137
-rw-r--r--static/components/vevent.ts71
-rw-r--r--static/elements.ts30
-rw-r--r--static/event-creator.ts179
-rw-r--r--static/globals.ts571
-rw-r--r--static/jcal.ts5
-rw-r--r--static/lib.ts35
-rw-r--r--static/popup.ts19
-rw-r--r--static/script.ts197
-rw-r--r--static/types.ts7
-rw-r--r--static/vevent.ts4
17 files changed, 811 insertions, 760 deletions
diff --git a/static/components/date-time-input.ts b/static/components/date-time-input.ts
new file mode 100644
index 00000000..1f54b15e
--- /dev/null
+++ b/static/components/date-time-input.ts
@@ -0,0 +1,77 @@
+export { DateTimeInput }
+
+import { to_boolean, parseDate } from '../lib'
+
+/* '<date-time-input />' */
+class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
+ connectedCallback() {
+ /* This can be in the constructor for chromium, but NOT firefox...
+ Vivaldi 4.3.2439.63 stable
+ Mozilla Firefox 94.0.1
+ */
+ this.innerHTML = '<input type="date" /><input type="time" />'
+ // console.log('constructing datetime input')
+ }
+
+ static get observedAttributes() {
+ return ['dateonly']
+ }
+
+ attributeChangedCallback(name: string, _: any, to: any): void {
+ // console.log(this, name, to_boolean(from), to_boolean(to));
+ switch (name) {
+ case 'dateonly':
+ (this.querySelector('input[type="time"]') as HTMLInputElement)
+ .disabled = to_boolean(to)
+ break;
+ }
+ }
+
+ get dateonly(): boolean {
+ return to_boolean(this.getAttribute('dateonly'));
+ }
+
+ set dateonly(bool: boolean) {
+ this.setAttribute('dateonly', "" + bool);
+ }
+
+ get valueAsDate(): Date {
+ let dt;
+ let date = (this.querySelector("input[type='date']") as HTMLInputElement).value;
+ if (to_boolean(this.getAttribute('dateonly'))) {
+ dt = parseDate(date);
+ dt.type = 'date';
+ } else {
+ let time = (this.querySelector("input[type='time']") as HTMLInputElement).value;
+ dt = parseDate(date + 'T' + time)
+ dt.type = 'date-time';
+ }
+ return dt;
+ }
+
+ get value(): string {
+ return this.valueAsDate.format("~Y-~m-~dT~H:~M:~S")
+ }
+
+ set value(new_value: Date | string) {
+ // console.log('Setting date');
+ 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("input[type='date']") as HTMLInputElement).value = date;
+ (this.querySelector("input[type='time']") as HTMLInputElement).value = time;
+ }
+
+ addEventListener(type: string, proc: ((e: Event) => void)) {
+ if (type != 'input') throw "Only input supported";
+
+ (this.querySelector("input[type='date']") as HTMLInputElement)
+ .addEventListener(type, proc);
+ (this.querySelector("input[type='time']") as HTMLInputElement)
+ .addEventListener(type, proc);
+ }
+}
diff --git a/static/components/popup-element.ts b/static/components/popup-element.ts
new file mode 100644
index 00000000..3225fa52
--- /dev/null
+++ b/static/components/popup-element.ts
@@ -0,0 +1,100 @@
+export { PopupElement }
+
+import { gensym } from '../lib'
+import { VEvent } from '../vevent'
+import { bind_popup_control } from '../dragable'
+import { close_popup } from '../popup'
+
+import { ComponentVEvent } from './vevent'
+import { TabElement } from './tab-element'
+
+/* <popup-element /> */
+class PopupElement extends ComponentVEvent {
+
+ tabgroup_id: string
+ tabcount: number
+
+ constructor(uid?: string) {
+ super(uid);
+
+ /* TODO populate remaining */
+ // this.id = 'popup' + this.dataset.uid
+ this.tabgroup_id = gensym();
+ this.tabcount = 0
+ }
+
+ redraw(data: VEvent) {
+ // console.warn('IMPLEMENT ME');
+
+ if (data._calendar) {
+ this.dataset.calendar = data._calendar;
+ }
+
+ /* TODO is there any case where we want to propagate the draw to any of
+ our tabs? or are all our tabs independent? */
+ }
+
+ connectedCallback() {
+ let template: HTMLTemplateElement = document.getElementById('popup-template') as HTMLTemplateElement
+ let body = (template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
+
+ let uid = this.uid;
+ // console.log(uid);
+
+ body.getElementsByClassName('populate-with-uid')
+ .forEach((e) => e.setAttribute('data-uid', uid));
+
+ /* tabs */
+ // for (let tab of body.querySelectorAll(".tabgroup .tab")) {
+ // }
+ window.setTimeout(() => {
+ // let tabs = this.querySelector('tab-element')!
+ // .shadowRoot!
+ // .querySelectorAll('label')
+ // console.log(tabs);
+ // console.log(this.getElementsByTagName('tab-element'))
+ for (let tab of this.getElementsByTagName('tab-element')) {
+ // console.log(tab_container);
+ // let tab = tab_container.shadowRoot!;
+ // tab.documentElement.style.setProperty('--i', i);
+ popuplateTab(tab as TabElement, this.tabgroup_id, this.tabcount)
+ this.tabcount += 1
+ }
+ (this.querySelector('tab-element label') as HTMLInputElement).click()
+ });
+ /* end tabs */
+
+ /* nav bar */
+ let nav = body.getElementsByClassName("popup-control")[0] as HTMLElement;
+ bind_popup_control(nav);
+
+ let btn = body.querySelector('.popup-control .close-tooltip') as HTMLButtonElement
+ btn.addEventListener('click', () => close_popup(this));
+ /* end nav bar */
+
+ this.replaceChildren(body);
+ }
+
+ addTab(tab: TabElement) {
+ let tabgroup = this.getElementsByClassName('tabgroup')![0]!
+ tabgroup.append(tab);
+ popuplateTab(tab, this.tabgroup_id, this.tabcount)
+ this.tabcount += 1
+ }
+}
+
+function popuplateTab(tab: HTMLElement, tabgroup: string, index: number) {
+ // console.log(tab);
+ let new_id = gensym();
+ let input = tab.querySelector('input[type="radio"]') as HTMLInputElement;
+ let label = tab.querySelector("label")!
+ tab.style.setProperty('--tab-index', '' + index);
+ /* TODO this throws a number of errors, but somehow still works...? */
+ if (input !== null) {
+ input.name = tabgroup
+ input.id = new_id;
+ }
+ if (label !== null) {
+ label.setAttribute('for', new_id);
+ }
+}
diff --git a/static/components/tab-element.ts b/static/components/tab-element.ts
new file mode 100644
index 00000000..9403a737
--- /dev/null
+++ b/static/components/tab-element.ts
@@ -0,0 +1,40 @@
+export { TabElement }
+
+/* <tab-element /> */
+class TabElement extends HTMLElement {
+ constructor() {
+ super();
+ }
+
+ connectedCallback() {
+ // this.replaceChildren(template.cloneNode(true));
+ let template
+ = (document.getElementById('tab-template') as HTMLTemplateElement)
+ .content
+ // const shadowRoot = this.attachShadow({ mode: 'open' })
+ // .appendChild(template.cloneNode(true));
+ // console.log(this);
+ let label = this.querySelector('[slot="label"]')
+ let content = this.querySelector('[slot="content"]')
+ if (!verifySlot(label)) throw "Bad label";
+ if (!verifySlot(content)) throw "Bad content";
+
+ /* TODO set label hover title somewhere around here */
+
+ this.replaceChildren(template.cloneNode(true));
+ this.querySelector('slot[name="label"]')!.replaceWith(label);
+ this.querySelector('slot[name="content"]')!.replaceWith(content);
+ }
+}
+
+function verifySlot(el: Node | null): el is HTMLElement {
+ if (el === null) {
+ console.error("Element is null");
+ return false;
+ }
+ if (!(el instanceof HTMLElement)) {
+ console.error("Node is not an HTMLElement", el);
+ return false;
+ }
+ return true
+}
diff --git a/static/components/vevent-block.ts b/static/components/vevent-block.ts
new file mode 100644
index 00000000..439ba20e
--- /dev/null
+++ b/static/components/vevent-block.ts
@@ -0,0 +1,63 @@
+export { ComponentBlock }
+
+import { ComponentVEvent } from './vevent'
+import { VEvent } from '../vevent'
+import { toggle_popup, find_popup } from '../popup'
+import { parseDate, to_local } from '../lib'
+
+
+/* <vevent-block />
+
+ A grahpical block in the week view.
+*/
+class ComponentBlock extends ComponentVEvent {
+ constructor(uid?: string) {
+ super(uid);
+
+ this.addEventListener('click', () => {
+ let uid = this.uid
+ let popup = find_popup(uid);
+ if (popup === null) throw new Error('no popup for uid ' + uid);
+ toggle_popup(popup);
+ });
+ }
+
+ redraw(data: VEvent) {
+ super.redraw(data);
+
+ let p;
+ if ((p = data.getProperty('dtstart'))) {
+ let c = this.closest('.event-container') as HTMLElement
+ let start = parseDate(c.dataset.start!).getTime()
+ let end = parseDate(c.dataset.end!).getTime();
+ // console.log(p);
+ let pp = to_local(p).getTime()
+ let result = 100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start) + "%"
+ if (c.classList.contains('longevents')) {
+ this.style.left = result
+ } else {
+ this.style.top = result
+ }
+ // console.log('dtstart', p);
+ }
+ if ((p = data.getProperty('dtend'))) {
+ // console.log('dtend', p);
+ let c = this.closest('.event-container') as HTMLElement
+ let start = parseDate(c.dataset.start!).getTime()
+ let end = parseDate(c.dataset.end!).getTime();
+ let pp = to_local(p).getTime()
+ let result = 100 - (100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start)) + "%"
+ if (c.classList.contains('longevents')) {
+ this.style.width = 'unset';
+ this.style.right = result;
+ } else {
+ this.style.height = 'unset';
+ this.style.bottom = result;
+ }
+ }
+
+ if (data._calendar) {
+ this.dataset.calendar = data._calendar;
+ }
+ }
+}
diff --git a/static/components/vevent-description.ts b/static/components/vevent-description.ts
new file mode 100644
index 00000000..f97b60e1
--- /dev/null
+++ b/static/components/vevent-description.ts
@@ -0,0 +1,12 @@
+export { ComponentDescription }
+
+import { ComponentVEvent } from './vevent'
+
+/*
+ <vevent-description />
+*/
+class ComponentDescription extends ComponentVEvent {
+ constructor() {
+ super();
+ }
+}
diff --git a/static/components/vevent-dl.ts b/static/components/vevent-dl.ts
new file mode 100644
index 00000000..a9e60d81
--- /dev/null
+++ b/static/components/vevent-dl.ts
@@ -0,0 +1,24 @@
+export { VEventDL }
+
+import { ComponentVEvent } from './vevent'
+import { VEvent } from '../vevent'
+import { makeElement } from '../lib'
+
+/* <vevent-dl /> */
+class VEventDL extends ComponentVEvent {
+ redraw(obj: VEvent) {
+ let dl = buildDescriptionList(
+ Array.from(obj.boundProperties)
+ .map(key => [key, obj.getProperty(key)]))
+ this.replaceChildren(dl);
+ }
+}
+
+function buildDescriptionList(data: [string, any][]): HTMLElement {
+ let dl = document.createElement('dl');
+ for (let [key, val] of data) {
+ dl.appendChild(makeElement('dt', { innerText: key }))
+ dl.appendChild(makeElement('dd', { innerText: val }))
+ }
+ return dl;
+}
diff --git a/static/components/vevent-edit.ts b/static/components/vevent-edit.ts
new file mode 100644
index 00000000..602e1872
--- /dev/null
+++ b/static/components/vevent-edit.ts
@@ -0,0 +1,137 @@
+export { ComponentEdit }
+
+import { ComponentVEvent } from './vevent'
+import { DateTimeInput } from './date-time-input'
+
+import { vcal_objects, event_calendar_mapping } from '../globals'
+import { VEvent } from '../vevent'
+import { create_event } from '../server_connect'
+
+/* <vevent-edit />
+ Edit form for a given VEvent. Used as the edit tab of popups.
+*/
+class ComponentEdit extends ComponentVEvent {
+
+ firstTime: boolean
+
+ 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 */
+
+ let data = vcal_objects.get(this.uid)
+
+ if (!data) {
+ throw `Data missing for uid ${this.dataset.uid}.`
+ }
+
+
+ // return;
+
+ /* Handle calendar dropdown */
+ for (let el of this.getElementsByClassName('calendar-selection')) {
+ for (let opt of el.getElementsByTagName('option')) {
+ opt.selected = false;
+ if (opt.value == event_calendar_mapping.get(this.uid)) {
+ data.setCalendar(opt.value);
+ opt.selected = true;
+ /* No break since we want to set the remainders 'selected' to false */
+ }
+ }
+
+ el.addEventListener('change', (e) => {
+ let v = (e.target as HTMLSelectElement).selectedOptions[0].value
+ // e.selectedOptions[0].innerText
+
+ let obj = vcal_objects.get(this.uid)!
+ obj.setCalendar(v);
+ });
+ }
+
+ this.redraw(data);
+
+ for (let el of this.getElementsByClassName("interactive")) {
+ // console.log(el);
+ el.addEventListener('input', () => {
+ let obj = vcal_objects.get(this.uid)
+ if (obj === undefined) {
+ throw 'No object with uid ' + this.uid
+ }
+ if (!(el instanceof HTMLInputElement
+ || el instanceof DateTimeInput)) {
+ console.log(el, 'not an HTMLInputElement');
+ return;
+ }
+ obj.setProperty(
+ el.dataset.property!,
+ el.value)
+ });
+ }
+
+ let submit = this.querySelector('form') as HTMLFormElement
+ submit.addEventListener('submit', (e) => {
+ console.log(submit, e);
+ create_event(vcal_objects.get(this.uid)!);
+
+ e.preventDefault();
+ return false;
+ });
+ }
+
+ redraw(data: VEvent) {
+ // update ourselves from template
+
+ if (!this.template) {
+ throw "Something";
+ }
+
+ let body;
+ if (this.firstTime) {
+ body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
+ } else {
+ body = this;
+ }
+
+ for (let el of body.getElementsByClassName("interactive")) {
+ if (!(el instanceof HTMLElement)) continue;
+ let p = el.dataset.property!;
+ let d: any;
+ 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 */
+ /* Technically we just want to cast to HTMLElement with
+ value field here, but multiple types implement it
+ sepparately, and no common interface exist */
+ (el as HTMLInputElement).value = d;
+ });
+ }
+ }
+
+ for (let el of body.getElementsByTagName('calendar-selection')) {
+ for (let opt of el.getElementsByTagName('option')) {
+ opt.selected = false;
+ if (opt.value == data._calendar) {
+ opt.selected = true;
+ }
+ }
+ }
+
+ if (this.firstTime) {
+ this.replaceChildren(body);
+ this.firstTime = false;
+ }
+ }
+
+}
diff --git a/static/components/vevent.ts b/static/components/vevent.ts
new file mode 100644
index 00000000..de232794
--- /dev/null
+++ b/static/components/vevent.ts
@@ -0,0 +1,71 @@
+export { ComponentVEvent }
+
+import { vcal_objects } from '../globals'
+import { VEvent } from '../vevent'
+
+/* Root component for all events which content is closely linked to a
+@code{VEvent} object
+
+Lacks an accompaning tag, and shouldn't be directly instanciated.
+*/
+class ComponentVEvent extends HTMLElement {
+
+ template: HTMLTemplateElement
+ uid: string
+
+ constructor(uid?: string) {
+ super();
+ this.template = document.getElementById(this.tagName) as HTMLTemplateElement;
+
+ let real_uid;
+ if (this.dataset.uid) uid = this.dataset.uid;
+ if (uid) real_uid = uid;
+
+ if (!real_uid) {
+ throw `UID required`
+ }
+
+ this.uid = real_uid;
+
+ vcal_objects.get(this.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)
+ should take care of that some other way */
+ }
+
+ connectedCallback() {
+ let uid, v;
+ if ((uid = this.dataset.uid)) {
+ v = vcal_objects.get(uid)
+ if (v) this.redraw(v);
+ }
+ }
+
+ redraw(data: VEvent) {
+ // update ourselves from template
+
+ if (!this.template) {
+ throw "Something";
+ }
+
+ let body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
+
+ for (let el of body.getElementsByClassName("bind")) {
+ if (!(el instanceof HTMLElement)) continue;
+ let p = el.dataset.property!;
+ let d, fmt;
+ if ((d = data.getProperty(p))) {
+ if ((fmt = el.dataset.fmt)) {
+ el.innerHTML = d.format(fmt);
+ } else {
+ el.innerHTML = d;
+ }
+ }
+ }
+
+ this.replaceChildren(body);
+ }
+
+}
diff --git a/static/elements.ts b/static/elements.ts
new file mode 100644
index 00000000..06e0e31f
--- /dev/null
+++ b/static/elements.ts
@@ -0,0 +1,30 @@
+import { ComponentDescription } from './components/vevent-description'
+import { ComponentEdit } from './components/vevent-edit'
+import { VEventDL } from './components/vevent-dl'
+import { ComponentBlock } from './components/vevent-block'
+import { DateTimeInput } from './components/date-time-input'
+import { PopupElement } from './components/popup-element'
+import { TabElement } from './components/tab-element'
+
+export { initialize_components }
+
+function initialize_components() {
+
+
+ /* These MUST be created AFTER vcal_objcets and event_calendar_mapping are
+ inistialized, since their constructors assume that that piece of global
+ state is available */
+ customElements.define('vevent-description', ComponentDescription);
+ customElements.define('vevent-edit', ComponentEdit);
+ customElements.define('vevent-dl', VEventDL);
+ customElements.define('vevent-block', ComponentBlock);
+
+ /* date-time-input should be instansiatable any time, but we do it here
+ becouse why not */
+
+ customElements.define('date-time-input', DateTimeInput /*, { extends: 'input' } */)
+
+ /* These maybe also require that the global maps are initialized */
+ customElements.define('popup-element', PopupElement)
+ customElements.define('tab-element', TabElement)
+}
diff --git a/static/event-creator.ts b/static/event-creator.ts
new file mode 100644
index 00000000..cf3468ce
--- /dev/null
+++ b/static/event-creator.ts
@@ -0,0 +1,179 @@
+export { EventCreator }
+
+import { VEvent } from './vevent'
+import { v4 as uuid } from 'uuid'
+import { ComponentBlock } from './components/vevent-block'
+import { round_time } from './lib'
+import { parseDate } from './lib'
+
+class EventCreator {
+
+ /* Event which we are trying to create */
+ ev: VEvent | null = null;
+
+ /* Graphical block for event. Only here so we can find its siblings,
+ and update pointer events accordingly */
+ event: Element | null = null;
+
+ event_start: { x: number, y: number } = { x: NaN, y: NaN }
+ down_on_event: boolean = false
+ timeStart: number = 0
+
+ create_event_down(intended_target: HTMLElement): (e: MouseEvent) => any {
+ let that = this;
+ return function(e: MouseEvent) {
+ /* 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: ((c: HTMLElement, e: MouseEvent) => number),
+ round_to: number = 1,
+ wide_element: boolean = false
+ ): ((e: MouseEvent) => any) {
+ let that = this;
+ return function(this: HTMLElement, e: MouseEvent) {
+ if (e.buttons != 1 || !that.down_on_event) return;
+
+ /* Create event when we start moving the mouse. */
+ if (!that.ev) {
+ /* 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.ev = new VEvent();
+ that.ev.setProperty('summary', 'Created Event');
+ that.ev.setProperty('uid', uuid())
+
+ // let ev_block = document.createElement('vevent-block') as ComponentBlock;
+ let ev_block = new ComponentBlock(that.ev.getProperty('uid'));
+ that.event = ev_block;
+ that.ev.register(ev_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.
+ */
+
+ // that.event.dataset.time1 = '' + time;
+ // that.event.dataset.time2 = '' + time;
+
+ /* ---------------------------------------- */
+
+ this.appendChild(ev_block);
+
+ /* 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 as HTMLElement).style.pointerEvents = "none";
+ }
+
+ that.timeStart = round_time(pos_in(this, e), round_to);
+ }
+
+ let time = round_time(pos_in(this, e), round_to);
+
+ // let time1 = Number(that.event.dataset.time1);
+ // let time2 = round_time(
+ // pos_in(that.event.parentElement!, e),
+ // round_to);
+ // that.event.dataset.time2 = '' + time2
+
+ /* ---------------------------------------- */
+
+ let event_container = this.closest(".event-container") as HTMLElement;
+
+ /* 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.valueOf() - container_start.valueOf();
+
+ let start_in_duration = duration * Math.min(that.timeStart, time);
+ let end_in_duration = duration * Math.max(that.timeStart, time);
+
+ /* 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)
+
+ /* TODO these writes should preferably be grouped,
+ to save a redraw for all registered listeners */
+ that.ev.setProperty('dtstart', d1);
+ that.ev.setProperty('dtend', d2);
+
+ // console.log(that.event);
+ // console.log(d1.format("~L~H:~M"), d2.format("~L~H:~M"));
+ }
+ }
+
+ create_event_finisher(callback: ((ev: VEvent) => void)) {
+ let that = this;
+ return function create_event_up(e: MouseEvent) {
+ if (!that.ev) return;
+
+ /* Restore pointer events for all existing events.
+ Allow pointer events on our new event
+ */
+ for (let e of (that.event as Element).parentElement!.children) {
+ (e as HTMLElement).style.pointerEvents = "";
+ }
+
+ let localevent = that.ev;
+ that.ev = null
+ that.event = null;
+
+ callback(localevent);
+
+ }
+ }
+}
diff --git a/static/globals.ts b/static/globals.ts
index 5187d007..be79dae7 100644
--- a/static/globals.ts
+++ b/static/globals.ts
@@ -1,266 +1,25 @@
export {
- vcal_objects,
- find_block, find_popup, PopupElement,
- ComponentBlock
+ find_block,
+ VIEW, EDIT_MODE,
+ vcal_objects, event_calendar_mapping
}
-import { close_popup, toggle_popup } from './popup'
-import { VEvent, xml_to_vcal } from './vevent'
-import { bind_popup_control } from './dragable'
-import { uid, parseDate, gensym, to_local, boolean, makeElement } from './lib'
-import { create_event } from './server_connect'
+import { VEvent } from './vevent'
+import { uid } from './types'
const vcal_objects: Map<uid, VEvent> = new Map;
-(window as any).vcal_objects = vcal_objects;
-
-interface HasValue {
- value: string
-}
-
-function hasValue(obj: any): obj is HasValue {
- return 'value' in obj;
-}
-
-/* Root component for all events which content is closely linked to a
-@code{VEvent} object
-
-Lacks an accompaning tag, and shouldn't be directly instanciated.
-*/
-class ComponentVEvent extends HTMLElement {
-
- template: HTMLTemplateElement
- uid: string
-
- constructor(uid?: string) {
- super();
- this.template = document.getElementById(this.tagName) as HTMLTemplateElement;
-
- let real_uid;
- if (this.dataset.uid) uid = this.dataset.uid;
- if (uid) real_uid = uid;
-
- if (!real_uid) {
- throw `UID required`
- }
-
- this.uid = real_uid;
-
- vcal_objects.get(this.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)
- should take care of that some other way */
- }
-
- connectedCallback() {
- let uid, v;
- if ((uid = this.dataset.uid)) {
- v = vcal_objects.get(uid)
- if (v) this.redraw(v);
- }
- }
-
- redraw(data: VEvent) {
- // update ourselves from template
-
- if (!this.template) {
- throw "Something";
- }
-
- let body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
-
- for (let el of body.getElementsByClassName("bind")) {
- if (!(el instanceof HTMLElement)) continue;
- let p = el.dataset.property!;
- let d, fmt;
- if ((d = data.getProperty(p))) {
- if ((fmt = el.dataset.fmt)) {
- el.innerHTML = d.format(fmt);
- } else {
- el.innerHTML = d;
- }
- }
- }
-
- this.replaceChildren(body);
- }
-
-}
-
-
-/*
- <vevent-description />
-*/
-class ComponentDescription extends ComponentVEvent {
- constructor() {
- super();
- }
-}
-
-function popuplateTab(tab: HTMLElement, tabgroup: string, index: number) {
- // console.log(tab);
- let new_id = gensym();
- let input = tab.querySelector('input[type="radio"]') as HTMLInputElement;
- let label = tab.querySelector("label")!
- tab.style.setProperty('--tab-index', '' + index);
- /* TODO this throws a number of errors, but somehow still works...? */
- if (input !== null) {
- input.name = tabgroup
- input.id = new_id;
- }
- if (label !== null) {
- label.setAttribute('for', new_id);
- }
-}
+const event_calendar_mapping: Map<uid, string> = new Map;
-/* <vevent-dl /> */
-class VEventDL extends ComponentVEvent {
- redraw(obj: VEvent) {
- let dl = buildDescriptionList(
- Array.from(obj.boundProperties)
- .map(key => [key, obj.getProperty(key)]))
- this.replaceChildren(dl);
+declare global {
+ interface Window {
+ vcal_objects: Map<uid, VEvent>;
}
}
+window.vcal_objects = vcal_objects;
-/* <vevent-edit />
- Edit form for a given VEvent. Used as the edit tab of popups.
-*/
-class ComponentEdit extends ComponentVEvent {
-
- firstTime: boolean
+declare let VIEW: 'month' | 'week'
+declare let EDIT_MODE: boolean
- 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 */
-
- let data = vcal_objects.get(this.uid)
-
- if (!data) {
- throw `Data missing for uid ${this.dataset.uid}.`
- }
-
-
- // return;
-
- /* Handle calendar dropdown */
- for (let el of this.getElementsByClassName('calendar-selection')) {
- for (let opt of el.getElementsByTagName('option')) {
- opt.selected = false;
- if (opt.value == event_calendar_mapping.get(this.uid)) {
- data.setCalendar(opt.value);
- opt.selected = true;
- /* No break since we want to set the remainders 'selected' to false */
- }
- }
-
- el.addEventListener('change', (e) => {
- let v = (e.target as HTMLSelectElement).selectedOptions[0].value
- // e.selectedOptions[0].innerText
-
- let obj = vcal_objects.get(this.uid)!
- obj.setCalendar(v);
- });
- }
-
- this.redraw(data);
-
- for (let el of this.getElementsByClassName("interactive")) {
- // console.log(el);
- el.addEventListener('input', () => {
- let obj = vcal_objects.get(this.uid)
- if (obj === undefined) {
- throw 'No object with uid ' + this.uid
- }
- if (!(hasValue(el) && el instanceof HTMLElement)) {
- console.log(el, 'not an HTMLInputElement');
- return;
- }
- obj.setProperty(
- el.dataset.property!,
- el.value)
- });
- }
-
- let submit = this.querySelector('form') as HTMLFormElement
- submit.addEventListener('submit', (e) => {
- console.log(submit, e);
- create_event(vcal_objects.get(this.uid)!);
-
- e.preventDefault();
- return false;
- });
- }
-
- redraw(data: VEvent) {
- // update ourselves from template
-
- if (!this.template) {
- throw "Something";
- }
-
- let body;
- if (this.firstTime) {
- body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
- } else {
- body = this;
- }
-
- for (let el of body.getElementsByClassName("interactive")) {
- if (!(el instanceof HTMLElement)) continue;
- let p = el.dataset.property!;
- let d: any;
- 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 */
- /* Technically we just want to cast to HTMLElement with
- value field here, but multiple types implement it
- sepparately, and no common interface exist */
- (el as HTMLInputElement).value = d;
- });
- }
- }
-
- for (let el of body.getElementsByTagName('calendar-selection')) {
- for (let opt of el.getElementsByTagName('option')) {
- opt.selected = false;
- if (opt.value == data._calendar) {
- opt.selected = true;
- }
- }
- }
-
- if (this.firstTime) {
- this.replaceChildren(body);
- this.firstTime = false;
- }
- }
-
-}
-
-function find_popup(uid: uid): HTMLElement | null {
- // 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: uid): HTMLElement | null {
let obj = vcal_objects.get(uid)
@@ -276,319 +35,17 @@ function find_block(uid: uid): HTMLElement | null {
return null;
}
-/* <vevent-block />
- A grahpical block in the week view.
-*/
-class ComponentBlock extends ComponentVEvent {
- constructor(uid?: string) {
- super(uid);
- this.addEventListener('click', () => {
- let uid = this.uid
- let popup = find_popup(uid);
- if (popup === null) throw new Error('no popup for uid ' + uid);
- toggle_popup(popup);
- });
- }
- redraw(data: VEvent) {
- super.redraw(data);
- let p;
- if ((p = data.getProperty('dtstart'))) {
- let c = this.closest('.event-container') as HTMLElement
- let start = parseDate(c.dataset.start!).getTime()
- let end = parseDate(c.dataset.end!).getTime();
- // console.log(p);
- let pp = to_local(p).getTime()
- let result = 100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start) + "%"
- if (c.classList.contains('longevents')) {
- this.style.left = result
- } else {
- this.style.top = result
- }
- // console.log('dtstart', p);
- }
- if ((p = data.getProperty('dtend'))) {
- // console.log('dtend', p);
- let c = this.closest('.event-container') as HTMLElement
- let start = parseDate(c.dataset.start!).getTime()
- let end = parseDate(c.dataset.end!).getTime();
- let pp = to_local(p).getTime()
- let result = 100 - (100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start)) + "%"
- if (c.classList.contains('longevents')) {
- this.style.width = 'unset';
- this.style.right = result;
- } else {
- this.style.height = 'unset';
- this.style.bottom = result;
- }
- }
- if (data._calendar) {
- this.dataset.calendar = data._calendar;
- }
- }
-}
-
-const event_calendar_mapping: Map<uid, string> = new Map;
-
-window.addEventListener('load', function() {
-
- // let json_objects_el = document.getElementById('json-objects');
- let div = document.getElementById('xcal-data')!;
- let vevents = div.firstElementChild!.children;
-
- for (let vevent of vevents) {
- let ev = xml_to_vcal(vevent);
- vcal_objects.set(ev.getProperty('uid'), ev)
- }
-
-
- let div2 = document.getElementById('calendar-event-mapping')!;
- for (let calendar of div2.children) {
- for (let child of calendar.children) {
- event_calendar_mapping.set(
- child.innerHTML, calendar.getAttribute('key')!);
- }
- }
-
- /*
- - .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-dl', VEventDL);
- customElements.define('vevent-block', ComponentBlock);
-})
-
-
-
-
-/* '<date-time-input />' */
-class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
- connectedCallback() {
- /* This can be in the constructor for chromium, but NOT firefox...
- Vivaldi 4.3.2439.63 stable
- Mozilla Firefox 94.0.1
- */
- this.innerHTML = '<input type="date" /><input type="time" />'
- // console.log('constructing datetime input')
- }
-
- static get observedAttributes() {
- return ['dateonly']
- }
-
- attributeChangedCallback(name: string, _: any, to: any): void {
- // console.log(this, name, boolean(from), boolean(to));
- switch (name) {
- case 'dateonly':
- (this.querySelector('input[type="time"]') as HTMLInputElement)
- .disabled = boolean(to)
- break;
- }
- }
-
- get dateonly(): boolean {
- return boolean(this.getAttribute('dateonly'));
- }
-
- set dateonly(bool: boolean) {
- this.setAttribute('dateonly', "" + bool);
- }
-
- get valueAsDate(): Date {
- let dt;
- let date = (this.querySelector("input[type='date']") as HTMLInputElement).value;
- if (boolean(this.getAttribute('dateonly'))) {
- dt = parseDate(date);
- dt.type = 'date';
- } else {
- let time = (this.querySelector("input[type='time']") as HTMLInputElement).value;
- dt = parseDate(date + 'T' + time)
- dt.type = 'date-time';
- }
- return dt;
- }
-
- get value(): string {
- return this.valueAsDate.format("~Y-~m-~dT~H:~M:~S")
- }
-
- set value(new_value: Date | string) {
- // console.log('Setting date');
- 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("input[type='date']") as HTMLInputElement).value = date;
- (this.querySelector("input[type='time']") as HTMLInputElement).value = time;
- }
-
- addEventListener(type: string, proc: ((e: Event) => void)) {
- if (type != 'input') throw "Only input supported";
-
- (this.querySelector("input[type='date']") as HTMLInputElement)
- .addEventListener(type, proc);
- (this.querySelector("input[type='time']") as HTMLInputElement)
- .addEventListener(type, proc);
- }
-}
-
-customElements.define('date-time-input', DateTimeInput /*, { extends: 'input' } */)
-
-
-function verifySlot(el: Node | null): el is HTMLElement {
- if (el === null) {
- console.error("Element is null");
- return false;
- }
- if (!(el instanceof HTMLElement)) {
- console.error("Node is not an HTMLElement", el);
- return false;
- }
- return true
-}
-
-
-/* <tab-element /> */
-class TabElement extends HTMLElement {
- constructor() {
- super();
- }
-
- connectedCallback() {
- // this.replaceChildren(template.cloneNode(true));
- let template
- = (document.getElementById('tab-template') as HTMLTemplateElement)
- .content
- // const shadowRoot = this.attachShadow({ mode: 'open' })
- // .appendChild(template.cloneNode(true));
- // console.log(this);
- let label = this.querySelector('[slot="label"]')
- let content = this.querySelector('[slot="content"]')
- if (!verifySlot(label)) throw "Bad label";
- if (!verifySlot(content)) throw "Bad content";
- /* TODO set label hover title somewhere around here */
-
- this.replaceChildren(template.cloneNode(true));
- this.querySelector('slot[name="label"]')!.replaceWith(label);
- this.querySelector('slot[name="content"]')!.replaceWith(content);
- }
-}
-
-function buildDescriptionList(data: [string, any][]): HTMLElement {
- let dl = document.createElement('dl');
- for (let [key, val] of data) {
- dl.appendChild(makeElement('dt', { innerText: key }))
- dl.appendChild(makeElement('dd', { innerText: val }))
- }
- return dl;
-}
-
-/* <popup-element /> */
-class PopupElement extends ComponentVEvent {
-
- tabgroup_id: string
- tabcount: number
-
- constructor(uid?: string) {
- super(uid);
-
- /* TODO populate remaining */
- // this.id = 'popup' + this.dataset.uid
- this.tabgroup_id = gensym();
- this.tabcount = 0
- }
-
- redraw(data: VEvent) {
- // console.warn('IMPLEMENT ME');
-
- if (data._calendar) {
- this.dataset.calendar = data._calendar;
- }
-
- /* TODO is there any case where we want to propagate the draw to any of
- our tabs? or are all our tabs independent? */
- }
-
- connectedCallback() {
- let template: HTMLTemplateElement = document.getElementById('popup-template') as HTMLTemplateElement
- let body = (template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
-
- let uid = this.uid;
- // console.log(uid);
-
- body.getElementsByClassName('populate-with-uid')
- .forEach((e) => e.setAttribute('data-uid', uid));
-
- /* tabs */
- // for (let tab of body.querySelectorAll(".tabgroup .tab")) {
- // }
- window.setTimeout(() => {
- // let tabs = this.querySelector('tab-element')!
- // .shadowRoot!
- // .querySelectorAll('label')
- // console.log(tabs);
- // console.log(this.getElementsByTagName('tab-element'))
- for (let tab of this.getElementsByTagName('tab-element')) {
- // console.log(tab_container);
- // let tab = tab_container.shadowRoot!;
- // tab.documentElement.style.setProperty('--i', i);
- popuplateTab(tab as TabElement, this.tabgroup_id, this.tabcount)
- this.tabcount += 1
- }
- (this.querySelector('tab-element label') as HTMLInputElement).click()
- });
- /* end tabs */
-
- /* nav bar */
- let nav = body.getElementsByClassName("popup-control")[0] as HTMLElement;
- bind_popup_control(nav);
-
- let btn = body.querySelector('.popup-control .close-tooltip') as HTMLButtonElement
- btn.addEventListener('click', () => close_popup(this));
- /* end nav bar */
-
- this.replaceChildren(body);
- }
-
- addTab(tab: TabElement) {
- let tabgroup = this.getElementsByClassName('tabgroup')![0]!
- tabgroup.append(tab);
- popuplateTab(tab, this.tabgroup_id, this.tabcount)
- this.tabcount += 1
- }
-}
-
-window.addEventListener('load', function() {
- customElements.define('popup-element', PopupElement)
- customElements.define('tab-element', TabElement)
-});
+/*
function wholeday_checkbox(box: HTMLInputElement) {
box.closest('.timeinput')!
.querySelectorAll('input[is="date-time"]')
.forEach((el) => { (el as DateTimeInput).dateonly = box.checked });
}
+*/
diff --git a/static/jcal.ts b/static/jcal.ts
index 59ee93b4..605f41e7 100644
--- a/static/jcal.ts
+++ b/static/jcal.ts
@@ -1,6 +1,7 @@
export { jcal_to_xcal }
-import { ical_type, JCalProperty, JCal } from './types'
-import { xcal, asList } from './lib'
+
+import { xcal, ical_type, JCalProperty, JCal } from './types'
+import { asList } from './lib'
function jcal_type_to_xcal(doc: Document, type: ical_type, value: any): Element {
let el = doc.createElementNS(xcal, type);
diff --git a/static/lib.ts b/static/lib.ts
index ee8046aa..1dfd83f4 100644
--- a/static/lib.ts
+++ b/static/lib.ts
@@ -1,7 +1,7 @@
export {
- makeElement, date_to_percent, uid,
- parseDate, gensym, to_local, boolean,
- xcal, asList, round_time
+ makeElement, date_to_percent,
+ parseDate, gensym, to_local, to_boolean,
+ asList, round_time
}
/*
@@ -41,8 +41,6 @@ declare global {
}
}
-type uid = string
-
HTMLElement.prototype._addEventListener = HTMLElement.prototype.addEventListener;
HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event) => void) {
if (!this.listeners) this.listeners = {};
@@ -52,17 +50,6 @@ HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event)
};
-
-/* list of lists -> list of tuples */
-/* TODO figure out how to type this correctly */
-function zip(...args: any[]) {
- // console.log(args);
- if (args === []) return [];
- return [...Array(Math.min(...args.map(x => x.length))).keys()]
- .map((_, i) => args.map(lst => lst[i]));
-}
-
-
/* ----- Date Extensions ---------------------------- */
/*
@@ -120,13 +107,6 @@ function parseDate(str: string): Date {
return date;
}
-function copyDate(date: Date): Date {
- let d = new Date(date);
- d.utc = date.utc;
- d.dateonly = date.dateonly;
- return d;
-}
-
function to_local(date: Date): Date {
if (!date.utc) return date;
@@ -162,11 +142,6 @@ function date_to_percent(date: Date): number /* in 0, 100 */ {
/* js infix to not collide with stuff generated backend */
const gensym = (counter => (prefix = "gensym") => prefix + "js" + ++counter)(0)
-function setVar(str: string, val: any) {
- document.documentElement.style.setProperty("--" + str, val);
-}
-
-
function asList<T>(thing: Array<T> | T): Array<T> {
if (thing instanceof Array) {
return thing;
@@ -176,7 +151,7 @@ function asList<T>(thing: Array<T> | T): Array<T> {
}
-function boolean(value: any): boolean {
+function to_boolean(value: any): boolean {
switch (typeof value) {
case 'string':
switch (value) {
@@ -248,5 +223,3 @@ HTMLCollection.prototype.forEach = function(proc) {
proc(el);
}
}
-
-const xcal = "urn:ietf:params:xml:ns:icalendar-2.0";
diff --git a/static/popup.ts b/static/popup.ts
index 00638089..ce643854 100644
--- a/static/popup.ts
+++ b/static/popup.ts
@@ -1,7 +1,11 @@
-import { find_block, find_popup, PopupElement } from './globals'
+import { VIEW, find_block } from './globals'
+import { PopupElement } from './components/popup-element'
+import { uid } from './types'
+
export {
event_from_popup, popup_from_event, close_popup,
- close_all_popups, /* VIEW, */open_popup, toggle_popup, activePopup
+ close_all_popups, open_popup, toggle_popup, activePopup,
+ find_popup
}
/* TODO rewrite most of this */
@@ -39,7 +43,16 @@ function close_all_popups() {
}
}
-declare let VIEW: 'month' | 'week'
+
+function find_popup(uid: uid): HTMLElement | null {
+ // 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}"]`)
+}
/* open given popup */
function open_popup(popup: HTMLElement) {
diff --git a/static/script.ts b/static/script.ts
index 21685ee9..b9336165 100644
--- a/static/script.ts
+++ b/static/script.ts
@@ -1,191 +1,60 @@
import { close_all_popups } from './popup'
-import { VEvent } from './vevent'
+import { VEvent, xml_to_vcal } from './vevent'
import { SmallcalCellHighlight, Timebar } from './clock'
-import { makeElement, parseDate, round_time } from './lib'
-import { vcal_objects, ComponentBlock, PopupElement } from './globals'
+import { makeElement } from './lib'
+import { vcal_objects, event_calendar_mapping, EDIT_MODE } from './globals'
import { open_popup } from './popup'
-
-import { v4 as uuid } from 'uuid'
+import { EventCreator } from './event-creator'
+import { PopupElement } from './components/popup-element'
+import { initialize_components } from './elements'
/*
calp specific stuff
*/
-class EventCreator {
-
- /* Event which we are trying to create */
- ev: VEvent | null = null;
+window.addEventListener('load', function() {
- /* Graphical block for event. Only here so we can find its siblings,
- and update pointer events accordingly */
- event: Element | null = null;
+ // let json_objects_el = document.getElementById('json-objects');
+ let div = document.getElementById('xcal-data')!;
+ let vevents = div.firstElementChild!.children;
- event_start: { x: number, y: number } = { x: NaN, y: NaN }
- down_on_event: boolean = false
- timeStart: number = 0
+ for (let vevent of vevents) {
+ let ev = xml_to_vcal(vevent);
+ vcal_objects.set(ev.getProperty('uid'), ev)
+ }
- create_event_down(intended_target: HTMLElement): (e: MouseEvent) => any {
- let that = this;
- return function(e: MouseEvent) {
- /* 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;
+ let div2 = document.getElementById('calendar-event-mapping')!;
+ for (let calendar of div2.children) {
+ for (let child of calendar.children) {
+ event_calendar_mapping.set(
+ child.innerHTML, calendar.getAttribute('key')!);
}
}
/*
- 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 → ()
+ - .popup
+ - .block
+ - .list
*/
- create_event_move(
- pos_in: ((c: HTMLElement, e: MouseEvent) => number),
- round_to: number = 1,
- wide_element: boolean = false
- ): ((e: MouseEvent) => any) {
- let that = this;
- return function(this: HTMLElement, e: MouseEvent) {
- if (e.buttons != 1 || !that.down_on_event) return;
-
- /* Create event when we start moving the mouse. */
- if (!that.ev) {
- /* 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.ev = new VEvent();
- that.ev.setProperty('summary', 'Created Event');
- that.ev.setProperty('uid', uuid())
-
- // let ev_block = document.createElement('vevent-block') as ComponentBlock;
- let ev_block = new ComponentBlock(that.ev.getProperty('uid'));
- that.event = ev_block;
- that.ev.register(ev_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.
- */
-
- // that.event.dataset.time1 = '' + time;
- // that.event.dataset.time2 = '' + time;
-
- /* ---------------------------------------- */
-
- this.appendChild(ev_block);
-
- /* 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 as HTMLElement).style.pointerEvents = "none";
- }
-
- that.timeStart = round_time(pos_in(this, e), round_to);
- }
-
- let time = round_time(pos_in(this, e), round_to);
-
- // let time1 = Number(that.event.dataset.time1);
- // let time2 = round_time(
- // pos_in(that.event.parentElement!, e),
- // round_to);
- // that.event.dataset.time2 = '' + time2
-
- /* ---------------------------------------- */
-
- let event_container = this.closest(".event-container") as HTMLElement;
-
- /* 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.valueOf() - container_start.valueOf();
-
- let start_in_duration = duration * Math.min(that.timeStart, time);
- let end_in_duration = duration * Math.max(that.timeStart, time);
-
- /* 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)
-
- /* TODO these writes should preferably be grouped,
- to save a redraw for all registered listeners */
- that.ev.setProperty('dtstart', d1);
- that.ev.setProperty('dtend', d2);
-
- // console.log(that.event);
- // console.log(d1.format("~L~H:~M"), d2.format("~L~H:~M"));
+ /*
+ 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
+ );
}
}
+ */
- create_event_finisher(callback: ((ev: VEvent) => void)) {
- let that = this;
- return function create_event_up(e: MouseEvent) {
- if (!that.ev) return;
+ initialize_components();
- /* Restore pointer events for all existing events.
- Allow pointer events on our new event
- */
- for (let e of (that.event as Element).parentElement!.children) {
- (e as HTMLElement).style.pointerEvents = "";
- }
- let localevent = that.ev;
- that.ev = null
- that.event = null;
- callback(localevent);
- }
- }
-}
-
-declare let EDIT_MODE: boolean
-
-window.addEventListener('load', function() {
// let start_time = document.querySelector("meta[name='start-time']").content;
// let end_time = document.querySelector("meta[name='end-time']").content;
diff --git a/static/types.ts b/static/types.ts
index 6d1331c7..f371c72a 100644
--- a/static/types.ts
+++ b/static/types.ts
@@ -1,7 +1,8 @@
export {
ical_type,
valid_input_types,
- JCalProperty, JCal
+ JCalProperty, JCal,
+ xcal, uid
}
let all_types = [
@@ -181,6 +182,8 @@ let valid_input_types: Map<string, ical_type | ical_type[]> =
type tagname = 'vevent' | string
+type uid = string
+
/* TODO is this type correct?
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?
@@ -188,3 +191,5 @@ type tagname = 'vevent' | string
type JCalProperty = [string, Map<string, any>, ical_type, any[]]
type JCal = [tagname, JCalProperty[], JCal[]]
+
+const xcal = "urn:ietf:params:xml:ns:icalendar-2.0";
diff --git a/static/vevent.ts b/static/vevent.ts
index d8ef58ce..ddabb507 100644
--- a/static/vevent.ts
+++ b/static/vevent.ts
@@ -1,5 +1,5 @@
-import { ical_type, valid_input_types, JCal, JCalProperty } from './types'
-import { uid, parseDate } from './lib'
+import { uid, ical_type, valid_input_types, JCal, JCalProperty } from './types'
+import { parseDate } from './lib'
export { VEvent, xml_to_vcal }