-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWidget.ts
365 lines (282 loc) · 11.2 KB
/
Widget.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
import Perspective from "./Objects/Perspective";
import Task from "./Objects/Task";
import Project from "./Objects/Project";
import Tag from "./Objects/Tag";
import { Query, Hookifier } from "./Objects/Utils";
import { Context } from "./Objects/EngineManager";
import type { Filterable } from "./Objects/Utils";
/**
* Collections of Elements
*
* Widgets are actually pretty simple things:
* they contain a specific Query, probably
* the cached results of the Query, and some
* hooking system to tell you when stuff changed.
*
* @param{Context} context the context you wish to create the widget from
*
*/
abstract class Widget {
protected loadCache:Promise<Filterable[]>|Promise<object[]>;
protected abstract name:string;
protected query:Query;
protected payload:object = {};
constructor(context:Context, payload?:object) {
this.query = new Query(context);
if (payload)
this.payload = payload;
this.loadCache = this.calculate();
Query.hook(this.resolveHooks);
}
/**
* Execute the widget
*
* @returns{Promise<Filterable[]|object[]>} the desired list
*
*/
async execute():Promise<Filterable[]|object[]> {
return await this.loadCache;
}
/**
* Calculate the results of the widget
*
* @returns{Promise<Filterable[]>} the desired list
*
*/
protected abstract calculate():Promise<Filterable[]>|Promise<object[]>;
private resolveHooks = async ():Promise<void> => {
let loadResult:Promise<Filterable[]|object[]> = this.calculate();
if (await loadResult !== await this.loadCache) {
this.loadCache = loadResult;
Hookifier.call(`widgets.${this.name}`);
}
}
/**
* Hook a callback to whence this Query updates
*
* @param{Function} hookFn the function you want to hook in
* @returns{void}
*
*/
hook(hookFn: Function): void {
Hookifier.push(`widgets.${this.name}`, hookFn);
}
/**
* Unook a hooked callback to whence this Query updates
*
* @param{Function} hookFn the function you want to unhook
* @returns{void}
*
*/
unhook(hookFn: Function): void {
Hookifier.remove(`widgets.${this.name}`, hookFn);
}
}
class PerspectivesMenuWidget extends Widget {
name = "persp-menu-widget"
async calculate() {
let allPerspectives:Perspective[] = await this.query.execute(Perspective, (_:Perspective)=>true) as Perspective[];
allPerspectives.sort((a: Perspective, b: Perspective) => a.order-b.order);
return allPerspectives;
}
}
class ProjectMenuWidget extends Widget {
name = "project-menu-widget"
async calculate() {
let topProjects:Project[] = await this.query.execute(Project, (i:Project)=> i.topLevel && !i.isComplete) as Project[];
topProjects.sort((a: Project, b: Project) => a.order-b.order);
return topProjects;
}
}
class MenuWidget extends Widget {
name = "menu-widget"
async calculate() {
let psmw:PerspectivesMenuWidget = new PerspectivesMenuWidget(this.query.cm);
let pjmw:ProjectMenuWidget = new ProjectMenuWidget(this.query.cm);
return [await pjmw.execute(), await psmw.execute()];
}
}
class InboxWidget extends Widget {
name = "inbox-widget"
async calculate(): Promise<Task[]> {
let inboxTasks:Task[] = await this.query.execute(Task, (i:Task) => (i.async_project === null) && !i.isComplete) as Task[];
inboxTasks.sort((a: Task, b: Task) => a.order-b.order);
return inboxTasks;
}
}
class DueSoonWidget extends Widget {
name = "duesoon-widget"
async calculate(): Promise<Task[]> {
let tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate()+1);
let DSTasks:Task[] = await this.query.execute(Task, (i:Task) => i.available && i.due && i.due < tomorrow && !i.isComplete) as Task[];
DSTasks.sort((b: Task, a: Task) => (a.due && b.due) ? a.due.getTime()-b.due.getTime() : 0);
return DSTasks;
}
}
class CompletedWidget extends Widget {
name = "completed-widget"
async calculate() {
let completedTasks:Task[] = await this.query.execute(Task, (i:Task) => (i.isComplete)) as Task[];
let completedProjects:Project[] = await this.query.execute(Project, (i:Project) => (i.isComplete)) as Project[];
let completedItems = [...completedTasks, ...completedProjects]
const cpSorted = completedItems.sort(function(b:Task|Project, a:Task|Project) { return (!a.completeDate || !b.completeDate) ? -1 : a.completeDate.getTime() - b.completeDate.getTime() })
let today = new Date();
let yesterday = new Date();
let thisWeek = new Date();
let thisMonth = new Date();
today.setHours(0,0,0,0);
yesterday.setDate(yesterday.getDate()-1);
yesterday.setHours(0,0,0,0);
thisWeek.setDate(thisWeek.getDate()-7);
thisWeek.setHours(0,0,0,0);
thisMonth.setMonth(thisMonth.getMonth()-1);
thisMonth.setHours(0,0,0,0);
let tasksToday = cpSorted.filter(function (a) {
let tsks = a;
return tsks.completeDate ? tsks.completeDate >= today : false;
});
let tasksYesterday = cpSorted.filter(function (a) {
let tsks = a;
return tsks.completeDate ? tsks.completeDate >= yesterday && tsks.completeDate < today : false;
});
let tasksWeek = cpSorted.filter(function (a) {
let tsks = a;
return tsks.completeDate ? tsks.completeDate >= thisWeek && tsks.completeDate < yesterday : false;
});
let tasksMonth = cpSorted.filter(function (a) {
let tsks = a;
return tsks.completeDate ? tsks.completeDate >= thisMonth && tsks.completeDate < thisWeek : false;
});
let evenBefore = cpSorted.filter(function (a) {
let tsks = a;
return tsks.completeDate ? tsks.completeDate < thisMonth : true;
});
return [tasksToday, tasksYesterday, tasksWeek, tasksMonth, evenBefore];
}
}
/**
* Widget for tags pane get tag collection.
*/
class TagsPaneWidget extends Widget {
name = "tags-pane-widget"
async calculate(): Promise<Tag[]> {
let tags:Tag[] = await this.query.execute(Tag, (_: Tag) => (true)) as Tag[];
return tags;
}
}
/**
* Widget for tasks' project dropdown datapack
*
* Because of the relative heaviness of DFS, this widget has special
* merging rules such that recent concurrent calls listen to the same
* promise.
*
*/
class ProjectDatapackWidget extends Widget {
name = "project-datapack-widget"
// private static dataPromise:Promise<object[]>;
//constructor(context:Context) {
//super(context);
//ProjectDatapackWidget.dataPromise = this.calculate();
//}
//async execute() {
//if (!ProjectDatapackWidget.dataPromise)
//ProjectDatapackWidget.dataPromise = this.calculate();
//let data = await ProjectDatapackWidget.dataPromise;
//return data;
//}
async calculate() {
// Get a list of top-level projects
let topProjects:Project[] = await this.query.execute(Project, (i:Project)=> i.topLevel && !i.isComplete) as Project[];
topProjects.sort((a: Project, b: Project) => a.order-b.order);
// Task: DFS through the list to get projects
let result:object[] = [];
// First, map the depth of all top projects to 0. Stack looks like [obj, depth]
let stack:any[] = topProjects.map((i:Project)=>[i, 0]);
// Reverse stack, b/c we want to process the top one first
stack.reverse();
while (stack.length > 0) {
// Pop the top of the stack and push name and level to result
let popped:[Project,number] = stack.pop();
if (!popped[0]) // if the data does not exist
continue;
//await popped[0].readinessPromise;
result.push({value: popped[0], label:(":: ".repeat(popped[1]))+popped[0].name});
// Query for children
let async_children:(Project|Task)[] = await popped[0].async_children;
// Get the children that's projects
async_children = async_children.filter((i:Task|Project) => (i instanceof Project) && (i.isComplete !== true)) as Project[];
// Reverse the gosh darn list b/c we want to process the top one first
async_children.reverse();
// And then, push the children to the stack
async_children.forEach((i:Project) => stack.push([i, popped[1]+1]));
}
// Clear the promise after a second for a refetch
//setTimeout(()=>{ProjectDatapackWidget.dataPromise = null}, 5000);
return result;
}
}
/**
* Widget for tags' project dropdown datapack
*
* Because of the relative heaviness of DFS, this widget has special
* merging rules such that recent concurrent calls listen to the same
* promise.
*
*/
class TagDatapackWidget extends Widget {
name = "tag-datapack-widget"
//private static dataPromise:Promise<object[]>;
// constructor(context:Context) {
//super(context);
//TagDatapackWidget.dataPromise = this.calculate();
//}
//async execute() {
//if (!TagDatapackWidget.dataPromise)
//TagDatapackWidget.dataPromise = this.calculate();
//let data = await TagDatapackWidget.dataPromise;
//return data;
//}
async calculate() {
// Get a list of all tags
let allTags:Tag[] = await this.query.execute(Tag, (_:Tag) => true) as Tag[];
// Task: DFS through the list to get all tags
let result:object[] = allTags.map((i:Tag)=>({value: i, label:i.name}));
// Clear the promise after a second for a refetch
//setTimeout(()=>{TagDatapackWidget.dataPromise = null}, 5000);
return result;
}
}
/**
* Widget for Upcoming timeline
*/
class TimelineWidget extends Widget {
name = "timeline-pane-widget"
async calculate() {
let tomorrow:Date = new Date();
tomorrow.setDate(tomorrow.getDate()+1);
let timeline:Task[] = await this.query.execute(Task, (t:Task)=>(!t.isComplete && t.due && tomorrow < t.due && t.due < new Date(3021, 1,1))) as Task[];
timeline = timeline.sort((a:Task, b:Task) => (a.due && b.due) ?a.due.getTime() - b.due.getTime():0);
let isSameDateAs:Function = function(aDate:Date, pDate:Date) {
return (
aDate.getFullYear() === pDate.getFullYear() &&
aDate.getMonth() === pDate.getMonth() &&
aDate.getDate() === pDate.getDate()
);
}
let refrenceDate = new Date();
let tcontent = [];
for (let task of timeline) {
let due = task.due;
if (!isSameDateAs(due,refrenceDate)) {
tcontent.push({type:"label", content: due});
refrenceDate = due;
}
tcontent.push({type:"task", content: task});
}
return tcontent;
}
}
export { Widget, ProjectMenuWidget, PerspectivesMenuWidget, InboxWidget, CompletedWidget, ProjectDatapackWidget, TagsPaneWidget, TagDatapackWidget, DueSoonWidget, TimelineWidget, MenuWidget };
//new line here