Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/task-editor/update-visible.ts
1691 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
Update which tasks and hashtags are visible, and their order.
8
*/
9
10
import { List, Map, Set, fromJS } from "immutable";
11
12
import {
13
cmp,
14
parse_hashtags,
15
search_match,
16
search_split,
17
} from "@cocalc/util/misc";
18
import { get_search } from "./search";
19
import { SORT_INFO, HEADINGS, HEADINGS_DIR } from "./headings-info";
20
import { Counts, LocalTaskStateMap, LocalViewStateMap, TaskMap } from "./types";
21
22
// Show tasks for a few seconds, even after marked done:
23
export const DONE_CUTOFF_MS = 3 * 1000;
24
25
export function update_visible(
26
tasks: Map<string, TaskMap>,
27
local_task_state: LocalTaskStateMap,
28
local_view_state: LocalViewStateMap,
29
counts: Counts,
30
current_task_id?: string,
31
) {
32
const show_deleted = !!local_view_state.get("show_deleted");
33
const show_done = !!local_view_state.get("show_done");
34
35
const now = Date.now();
36
const _is_visible: { [id: string]: boolean } = {}; // cache
37
let redoSoonMs = 0;
38
function is_visible(task: TaskMap, id: string): boolean {
39
const c = _is_visible[id];
40
if (c != null) {
41
return c;
42
}
43
44
if (!show_deleted && task.get("deleted")) {
45
_is_visible[id] = false;
46
} else if (!show_done && task.get("done")) {
47
if (now - (task.get("last_edited") ?? 0) > DONE_CUTOFF_MS) {
48
_is_visible[id] = false;
49
} else {
50
_is_visible[id] = true;
51
const redo = DONE_CUTOFF_MS - (now - (task.get("last_edited") ?? 0));
52
if (redo > 0) {
53
redoSoonMs = Math.max(redo, redoSoonMs) + 1000;
54
}
55
}
56
} else {
57
_is_visible[id] = true;
58
}
59
return _is_visible[id];
60
}
61
62
const relevant_tags: { [tag: string]: true } = {};
63
tasks.forEach((task: TaskMap, id: string) => {
64
if (!is_visible(task, id)) {
65
return;
66
}
67
const desc = task.get("desc") ?? "";
68
for (const x of parse_hashtags(desc)) {
69
const tag = desc.slice(x[0] + 1, x[1]).toLowerCase();
70
relevant_tags[tag] = true;
71
}
72
});
73
74
const search0 = get_search(local_view_state, relevant_tags);
75
const search: (string | RegExp)[] = search_split(search0.toLowerCase());
76
77
const new_counts = {
78
done: 0,
79
deleted: 0,
80
};
81
let current_is_visible = false;
82
83
let sort_column = local_view_state.getIn(["sort", "column"]) ?? HEADINGS[0];
84
if (!HEADINGS.includes(sort_column)) {
85
sort_column = HEADINGS[0];
86
}
87
if (SORT_INFO[sort_column] == null) {
88
SORT_INFO[sort_column] = SORT_INFO[HEADINGS[0]];
89
}
90
const sort_info = SORT_INFO[sort_column];
91
const sort_key = sort_info.key;
92
let sort_dir = local_view_state.getIn(["sort", "dir"]) ?? HEADINGS_DIR[0];
93
if (sort_info.reverse) {
94
// reverse sort order -- done for due date
95
if (sort_dir === "asc") {
96
sort_dir = "desc";
97
} else {
98
sort_dir = "asc";
99
}
100
}
101
// undefined always gets pushed to the bottom (only applies to due date in practice)
102
const sort_default = sort_dir === "desc" ? -1e15 : 1e15;
103
let hashtags = Set<string>(); // contains all the hashtags of visible tasks
104
const v: [string | number | undefined, string][] = [];
105
tasks.forEach((task: TaskMap, id: string) => {
106
if (task.get("done")) {
107
new_counts.done += 1;
108
}
109
if (task.get("deleted")) {
110
new_counts.deleted += 1;
111
}
112
113
const editing_desc = local_task_state?.getIn([id, "editing_desc"]);
114
if (!editing_desc && !is_visible(task, id)) {
115
return;
116
}
117
118
const desc = task.get("desc") ?? "";
119
let visible: boolean;
120
if (search_match(desc, search) || editing_desc) {
121
visible = true; // tag of a currently visible task
122
if (id === current_task_id) {
123
current_is_visible = true;
124
}
125
v.push([task.get(sort_key) ?? sort_default, id]);
126
} else {
127
visible = false; // not a tag of any currently visible task
128
}
129
130
for (const x of parse_hashtags(desc)) {
131
const tag = desc.slice(x[0] + 1, x[1]).toLowerCase();
132
if (visible) {
133
hashtags = hashtags.add(tag);
134
}
135
}
136
});
137
138
if (sort_dir === "desc") {
139
v.sort((a, b) => -cmp(a[0], b[0]));
140
} else {
141
v.sort((a, b) => cmp(a[0], b[0]));
142
}
143
144
const w = v.map((x) => x[1]);
145
const visible = fromJS(w);
146
if ((current_task_id == null || !current_is_visible) && visible.size > 0) {
147
current_task_id = visible.get(0);
148
} else if (!current_is_visible && visible.size === 0) {
149
current_task_id = undefined;
150
}
151
152
if (counts.get("done") !== new_counts.done) {
153
counts = counts.set("done", new_counts.done);
154
}
155
if (counts.get("deleted") !== new_counts.deleted) {
156
counts = counts.set("deleted", new_counts.deleted);
157
}
158
const t: string[] = [];
159
for (const x of search) {
160
if (x[0] !== "#" && x[0] !== "-") {
161
t.push(`${x}`);
162
}
163
}
164
const search_terms = Set(t);
165
const t2: string[] = [];
166
for (const x of search) {
167
if (x[0] !== "#") {
168
t2.push(`${x}`);
169
}
170
}
171
const nonhash_search = List(t2);
172
173
return {
174
visible,
175
current_task_id,
176
counts,
177
hashtags: fromJS(hashtags),
178
search_desc: search.join(" "),
179
search_terms,
180
nonhash_search,
181
redoSoonMs,
182
};
183
}
184
185