Skip to content

Commit aa4379e

Browse files
committed
starting work on issue #3798
1 parent 561659d commit aa4379e

File tree

7 files changed

+317
-12
lines changed

7 files changed

+317
-12
lines changed

src/packages/frontend/chat/register.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55

66
import { alert_message } from "@cocalc/frontend/alerts";
77
import { redux, redux_name } from "@cocalc/frontend/app-framework";
8-
import { register_file_editor } from "@cocalc/frontend/file-editors";
98
import { webapp_client } from "@cocalc/frontend/webapp-client";
109
import { path_split, startswith } from "@cocalc/util/misc";
1110
import { ChatActions } from "./actions";
12-
import { ChatRoom } from "./chatroom";
1311
import { ChatStore } from "./store";
1412

1513
// it is fine to call this more than once.
@@ -74,11 +72,3 @@ export function remove(path: string, redux, project_id: string): string {
7472
return name;
7573
}
7674

77-
register_file_editor({
78-
ext: "sage-chat",
79-
icon: "comment",
80-
// init has a weird call signature, for historical reasons.
81-
init: (path, _redux, project_id) => initChat(project_id ?? "", path),
82-
component: ChatRoom,
83-
remove,
84-
});

src/packages/frontend/editors/register-all.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ One you add a new built in editor, it should go here.
1212
// Import each module, which loads a file editor. These call register_file_editor.
1313
// This should be a comprehensive list of all React editors
1414

15-
import "../chat/register";
1615
import "./archive/actions";
1716
import "./stopwatch/register";
1817

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
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+
chat Editor Actions
8+
*/
9+
10+
import {
11+
Actions as CodeEditorActions,
12+
CodeEditorState,
13+
} from "../code-editor/actions";
14+
import { FrameTree } from "../frame-tree/types";
15+
import { TaskActions } from "@cocalc/frontend/editors/task-editor/actions";
16+
import { TaskStore } from "@cocalc/frontend/editors/task-editor/store";
17+
import { redux_name } from "../../app-framework";
18+
import { aux_file, cmp } from "@cocalc/util/misc";
19+
import { Map } from "immutable";
20+
21+
interface TaskEditorState extends CodeEditorState {
22+
// nothing yet
23+
}
24+
25+
export class Actions extends CodeEditorActions<TaskEditorState> {
26+
protected doctype: string = "syncdb";
27+
protected primary_keys: string[] = ["task_id"];
28+
protected string_cols: string[] = ["desc"];
29+
protected searchEmbeddings = {
30+
primaryKey: "task_id",
31+
textColumn: "desc",
32+
metaColumns: ["due_date", "done"],
33+
};
34+
taskActions: { [frameId: string]: TaskActions } = {};
35+
taskStore: TaskStore;
36+
auxPath: string;
37+
38+
_init2(): void {
39+
this.auxPath = aux_file(this.path, "tasks");
40+
this.taskStore = this.redux.createStore(
41+
redux_name(this.project_id, this.auxPath),
42+
TaskStore
43+
);
44+
const syncdb = this._syncstring;
45+
syncdb.on("change", this.syncdbChange);
46+
syncdb.once("change", this.ensurePositionsAreUnique);
47+
}
48+
49+
private syncdbChange(changes) {
50+
const syncdb = this._syncstring;
51+
const store = this.taskStore;
52+
if (syncdb == null || store == null) {
53+
// may happen during close
54+
return;
55+
}
56+
let tasks = store.get("tasks") ?? Map();
57+
changes.forEach((x) => {
58+
const task_id = x.get("task_id");
59+
const t = syncdb.get_one(x);
60+
if (t == null) {
61+
// deleted
62+
tasks = tasks.delete(task_id);
63+
} else {
64+
// changed
65+
tasks = tasks.set(task_id, t as any);
66+
}
67+
});
68+
69+
store.setState({ tasks });
70+
for (const id in this.taskActions) {
71+
this.taskActions[id]._update_visible();
72+
}
73+
}
74+
75+
private ensurePositionsAreUnique() {
76+
let tasks = this.taskStore.get("tasks");
77+
if (tasks == null) {
78+
return;
79+
}
80+
// iterate through tasks adding their (string) positions to a
81+
// "set" (using a map)
82+
const s = {};
83+
let unique = true;
84+
tasks.forEach((task, id) => {
85+
if (tasks == null) return; // won't happpen, but TS doesn't know that.
86+
let pos = task.get("position");
87+
if (pos == null) {
88+
// no position set at all -- just arbitrarily set it to 0; it'll get
89+
// fixed below, if this conflicts.
90+
pos = 0;
91+
tasks = tasks.set(id, task.set("position", 0));
92+
}
93+
if (s[pos]) {
94+
// already got this position -- so they can't be unique
95+
unique = false;
96+
return false;
97+
}
98+
s[pos] = true;
99+
});
100+
if (unique) {
101+
// positions turned out to all be unique - done
102+
return;
103+
}
104+
// positions are NOT unique - this could happen, e.g., due to merging
105+
// offline changes. We fix this by simply spreading them all out to be
106+
// 0 to n, arbitrarily breaking ties.
107+
const v: [number, string][] = [];
108+
tasks.forEach((task, id) => {
109+
v.push([task.get("position") ?? 0, id]);
110+
});
111+
v.sort((a, b) => cmp(a[0], b[0]));
112+
let position = 0;
113+
const actions = this.getTaskActions();
114+
if (actions == null) return;
115+
for (let x of v) {
116+
actions.set_task(x[1], { position });
117+
position += 1;
118+
}
119+
}
120+
121+
getTaskActions(frameId?): TaskActions | undefined {
122+
if (frameId == null) {
123+
for (const actions of Object.values(this.taskActions)) {
124+
return actions;
125+
}
126+
return undefined;
127+
}
128+
if (this.taskActions[frameId] != null) {
129+
return this.taskActions[frameId];
130+
}
131+
const auxPath = this.auxPath + frameId;
132+
const reduxName = redux_name(this.project_id, auxPath);
133+
const actions = this.redux.createActions(reduxName, TaskActions);
134+
actions._init(
135+
this.project_id,
136+
this.auxPath,
137+
this._syncstring,
138+
this.taskStore,
139+
this.path
140+
);
141+
actions._init_frame(frameId, this);
142+
this.taskActions[frameId] = actions;
143+
return actions;
144+
}
145+
146+
undo() {
147+
this.getTaskActions()?.undo();
148+
}
149+
redo() {
150+
this.getTaskActions()?.redo();
151+
}
152+
153+
help() {
154+
this.getTaskActions()?.help();
155+
}
156+
157+
close_frame(frameId: string): void {
158+
super.close_frame(frameId); // actually closes the frame itself
159+
// now clean up if it is a task frame:
160+
161+
if (this.taskActions[frameId] != null) {
162+
this.closeTaskFrame(frameId);
163+
}
164+
}
165+
166+
closeTaskFrame(frameId: string): void {
167+
const actions = this.taskActions[frameId];
168+
if (actions == null) {
169+
return;
170+
}
171+
delete this.taskActions[frameId];
172+
const name = actions.name;
173+
this.redux.removeActions(name);
174+
actions.close();
175+
}
176+
177+
close(): void {
178+
if (this._state == "closed") {
179+
return;
180+
}
181+
for (const frameId in this.taskActions) {
182+
this.closeTaskFrame(frameId);
183+
}
184+
this.redux.removeStore(this.taskStore.name);
185+
super.close();
186+
}
187+
188+
_raw_default_frame_tree(): FrameTree {
189+
return { type: "tasks" };
190+
}
191+
192+
async export_to_markdown(): Promise<void> {
193+
try {
194+
await this.getTaskActions()?.export_to_markdown();
195+
} catch (error) {
196+
this.set_error(`${error}`);
197+
}
198+
}
199+
200+
public focus(id?: string): void {
201+
if (id === undefined) {
202+
id = this._get_active_id();
203+
}
204+
if (this._get_frame_type(id) == "tasks") {
205+
this.getTaskActions(id)?.show();
206+
return;
207+
}
208+
super.focus(id);
209+
}
210+
211+
public blur(id?: string): void {
212+
if (id === undefined) {
213+
id = this._get_active_id();
214+
}
215+
if (this._get_frame_type(id) == "tasks") {
216+
this.getTaskActions(id)?.hide();
217+
}
218+
}
219+
220+
protected languageModelGetText(frameId: string, scope): string {
221+
if (this._get_frame_type(frameId) == "tasks") {
222+
const node = this._get_frame_node(frameId);
223+
return (
224+
this.getTaskActions(frameId)?.chatgptGetText(
225+
scope,
226+
node?.get("data-current_task_id")
227+
) ?? ""
228+
);
229+
}
230+
return super.languageModelGetText(frameId, scope);
231+
}
232+
233+
languageModelGetScopes() {
234+
return new Set<"cell">(["cell"]);
235+
}
236+
237+
languageModelGetLanguage() {
238+
return "md";
239+
}
240+
241+
// async updateEmbeddings(): Promise<number> {
242+
// if (this._syncstring == null) return 0;
243+
// return (await this.getTaskActions()?.updateEmbeddings()) ?? 0;
244+
// }
245+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
Top-level react component for editing chat
8+
*/
9+
10+
import { createElement } from "react";
11+
12+
import { TaskEditor } from "@cocalc/frontend/editors/task-editor/editor";
13+
import { set } from "@cocalc/util/misc";
14+
import { createEditor } from "../frame-tree/editor";
15+
import { EditorDescription } from "../frame-tree/types";
16+
import { terminal } from "../terminal-editor/editor";
17+
import { time_travel } from "../time-travel-editor/editor";
18+
19+
const tasks: EditorDescription = {
20+
type: "tasks",
21+
short: "Tasks",
22+
name: "Task List",
23+
icon: "tasks",
24+
component: (props) => {
25+
const actions = props.actions.getTaskActions(props.id);
26+
return createElement(TaskEditor, {
27+
...props,
28+
actions,
29+
path: actions.path,
30+
});
31+
},
32+
commands: set([
33+
"decrease_font_size",
34+
"increase_font_size",
35+
"time_travel",
36+
"undo",
37+
"redo",
38+
"save",
39+
"help",
40+
"export_to_markdown",
41+
"chatgpt",
42+
]),
43+
} as const;
44+
45+
const EDITOR_SPEC = {
46+
tasks,
47+
terminal,
48+
time_travel,
49+
} as const;
50+
51+
export const Editor = createEditor({
52+
editor_spec: EDITOR_SPEC,
53+
display_name: "TaskEditor",
54+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
Register the chatroom editor
8+
*/
9+
10+
import { register_file_editor } from "../frame-tree/register";
11+
12+
register_file_editor({
13+
ext: "sage-chat",
14+
editor: async () => await import("./editor"),
15+
actions: async () => await import("./actions"),
16+
});

src/packages/frontend/frame-editors/register.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ import "./whiteboard-editor/register";
2929

3030
import "./crm-editor/register";
3131
import "./task-editor/register";
32+
import "./chat-editor/register";

src/packages/frontend/frame-editors/task-editor/editor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
/*
7-
Top-level react component for editing markdown documents
7+
Top-level react component for editing tasks
88
*/
99

1010
import { createElement } from "react";

0 commit comments

Comments
 (0)