Skip to content

Commit a3de312

Browse files
committed
work in progress on making the chatroom a frame tree editor
- far from done...
1 parent 1c9e8be commit a3de312

File tree

9 files changed

+79
-192
lines changed

9 files changed

+79
-192
lines changed

src/packages/frontend/chat/actions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ export class ChatActions extends Actions<ChatState> {
137137

138138
// Initialize the state of the store from the contents of the syncdb.
139139
public init_from_syncdb(): void {
140-
if (this.syncdb == null) return;
140+
if (this.syncdb == null) {
141+
return;
142+
}
141143
const v = {};
142144
for (let x of this.syncdb.get().toJS()) {
143145
x = this.process_syncdb_obj(x);

src/packages/frontend/chat/chatroom.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,8 @@ interface Props {
7979
is_visible?: boolean;
8080
}
8181

82-
export const ChatRoom: React.FC<Props> = ({ project_id, path, is_visible }) => {
82+
export function ChatRoom({ project_id, path, is_visible }: Props) {
8383
const actions: ChatActions = useActions(project_id, path);
84-
8584
const is_uploading = useRedux(["is_uploading"], project_id, path);
8685
const is_saving = useRedux(["is_saving"], project_id, path);
8786
const is_preview = useRedux(["is_preview"], project_id, path);
@@ -581,4 +580,4 @@ export const ChatRoom: React.FC<Props> = ({ project_id, path, is_visible }) => {
581580
</div>
582581
</FrameContext.Provider>
583582
);
584-
};
583+
}

src/packages/frontend/frame-editors/chat-editor/actions.ts

Lines changed: 53 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -4,171 +4,101 @@
44
*/
55

66
/*
7-
chat Editor Actions
7+
Chat Editor Actions
88
*/
99

1010
import {
1111
Actions as CodeEditorActions,
1212
CodeEditorState,
1313
} from "../code-editor/actions";
1414
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";
15+
import { ChatActions } from "@cocalc/frontend/chat/actions";
16+
import { ChatStore } from "@cocalc/frontend/chat/store";
17+
import { redux_name } from "@cocalc/frontend/app-framework";
18+
import { aux_file } from "@cocalc/util/misc";
2019

21-
interface TaskEditorState extends CodeEditorState {
20+
interface ChatEditorState extends CodeEditorState {
2221
// nothing yet
2322
}
2423

25-
export class Actions extends CodeEditorActions<TaskEditorState> {
24+
export class Actions extends CodeEditorActions<ChatEditorState> {
2625
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;
26+
protected primary_keys = ["date", "sender_id", "event"];
27+
// used only for drafts, since store lots of versions as user types:
28+
protected string_cols = ["input"];
29+
chatActions: { [frameId: string]: ChatActions } = {};
30+
chatStore: ChatStore;
3631
auxPath: string;
3732

3833
_init2(): void {
3934
this.auxPath = aux_file(this.path, "tasks");
40-
this.taskStore = this.redux.createStore(
35+
this.chatStore = this.redux.createStore(
4136
redux_name(this.project_id, this.auxPath),
42-
TaskStore
37+
ChatStore,
4338
);
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-
}
11939
}
12040

121-
getTaskActions(frameId?): TaskActions | undefined {
41+
getChatActions(frameId?): ChatActions | undefined {
12242
if (frameId == null) {
123-
for (const actions of Object.values(this.taskActions)) {
43+
for (const actions of Object.values(this.chatActions)) {
12444
return actions;
12545
}
12646
return undefined;
12747
}
128-
if (this.taskActions[frameId] != null) {
129-
return this.taskActions[frameId];
48+
if (this.chatActions[frameId] != null) {
49+
return this.chatActions[frameId];
13050
}
51+
const syncdb = this._syncstring;
13152
const auxPath = this.auxPath + frameId;
13253
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;
54+
const actions = this.redux.createActions(reduxName, ChatActions);
55+
56+
const init = () => {
57+
actions.set_syncdb(syncdb, this.chatStore);
58+
actions.init_from_syncdb();
59+
syncdb.on("change", actions.syncdb_change.bind(actions));
60+
syncdb.on("has-uncommitted-changes", (val) =>
61+
actions.setState({ has_uncommitted_changes: val }),
62+
);
63+
syncdb.on("has-unsaved-changes", (val) =>
64+
actions.setState({ has_unsaved_changes: val }),
65+
);
66+
};
67+
if (syncdb.get_state() != "ready") {
68+
syncdb.once("ready", init);
69+
} else {
70+
init();
71+
}
72+
73+
this.chatActions[frameId] = actions;
14374
return actions;
14475
}
14576

14677
undo() {
147-
this.getTaskActions()?.undo();
78+
this.getChatActions()?.undo();
14879
}
14980
redo() {
150-
this.getTaskActions()?.redo();
81+
this.getChatActions()?.redo();
15182
}
15283

15384
help() {
154-
this.getTaskActions()?.help();
85+
this.getChatActions()?.help();
15586
}
15687

15788
close_frame(frameId: string): void {
15889
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);
90+
// now clean up if it is a chat frame:
91+
if (this.chatActions[frameId] != null) {
92+
this.closeChatFrame(frameId);
16393
}
16494
}
16595

166-
closeTaskFrame(frameId: string): void {
167-
const actions = this.taskActions[frameId];
96+
closeChatFrame(frameId: string): void {
97+
const actions = this.chatActions[frameId];
16898
if (actions == null) {
16999
return;
170100
}
171-
delete this.taskActions[frameId];
101+
delete this.chatActions[frameId];
172102
const name = actions.name;
173103
this.redux.removeActions(name);
174104
actions.close();
@@ -178,68 +108,22 @@ export class Actions extends CodeEditorActions<TaskEditorState> {
178108
if (this._state == "closed") {
179109
return;
180110
}
181-
for (const frameId in this.taskActions) {
182-
this.closeTaskFrame(frameId);
111+
for (const frameId in this.chatActions) {
112+
this.closeChatFrame(frameId);
183113
}
184-
this.redux.removeStore(this.taskStore.name);
114+
this.redux.removeStore(this.chatStore.name);
185115
super.close();
186116
}
187117

188118
_raw_default_frame_tree(): FrameTree {
189-
return { type: "tasks" };
119+
return { type: "chatroom" };
190120
}
191121

192122
async export_to_markdown(): Promise<void> {
193123
try {
194-
await this.getTaskActions()?.export_to_markdown();
124+
await this.getChatActions()?.export_to_markdown();
195125
} catch (error) {
196126
this.set_error(`${error}`);
197127
}
198128
}
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-
// }
245129
}

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

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,23 @@ Top-level react component for editing chat
88
*/
99

1010
import { createElement } from "react";
11-
12-
import { TaskEditor } from "@cocalc/frontend/editors/task-editor/editor";
11+
import { ChatRoom } from "@cocalc/frontend/chat/chatroom";
1312
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";
13+
import { createEditor } from "@cocalc/frontend/frame-editors/frame-tree/editor";
14+
import type { EditorDescription } from "@cocalc/frontend/frame-editors/frame-tree/types";
15+
import { terminal } from "@cocalc/frontend/frame-editors/terminal-editor/editor";
16+
import { time_travel } from "@cocalc/frontend/frame-editors/time-travel-editor/editor";
1817

19-
const tasks: EditorDescription = {
20-
type: "tasks",
21-
short: "Tasks",
22-
name: "Task List",
23-
icon: "tasks",
18+
const chatroom: EditorDescription = {
19+
type: "chatroom",
20+
short: "Chatroom",
21+
name: "Chatroom",
22+
icon: "comment",
2423
component: (props) => {
25-
const actions = props.actions.getTaskActions(props.id);
26-
return createElement(TaskEditor, {
24+
const actions = props.actions.getChatActions(props.id);
25+
return createElement(ChatRoom, {
2726
...props,
2827
actions,
29-
path: actions.path,
3028
});
3129
},
3230
commands: set([
@@ -43,12 +41,12 @@ const tasks: EditorDescription = {
4341
} as const;
4442

4543
const EDITOR_SPEC = {
46-
tasks,
44+
chatroom,
4745
terminal,
4846
time_travel,
4947
} as const;
5048

5149
export const Editor = createEditor({
5250
editor_spec: EDITOR_SPEC,
53-
display_name: "TaskEditor",
51+
display_name: "ChatEditor",
5452
});

src/packages/frontend/frame-editors/frame-tree/frame-tree.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,11 @@ export const FrameTree: React.FC<FrameTreeProps> = React.memo(
378378
}
379379
spec = editor_spec[type];
380380
component = spec != null ? spec.component : undefined;
381-
if (component == null) throw Error(`unknown type '${type}'`);
381+
if (component == null) {
382+
throw Error(
383+
`unknown type '${type}'. Known types for this editor: ${JSON.stringify(Object.keys(editor_spec))}`,
384+
);
385+
}
382386
} catch (err) {
383387
const mesg = `Invalid frame tree ${JSON.stringify(desc)} -- ${err}`;
384388
console.log(mesg);

src/packages/frontend/frame-editors/frame-tree/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type ConnectionStatus = "disconnected" | "connected" | "connecting";
3939
// e.g. #7787 was caused by merely checking on the name, which had changed.
4040
type EditorType =
4141
| "chat"
42+
| "chatroom"
4243
| "cm-lean"
4344
| "cm"
4445
| "course-assignments"

0 commit comments

Comments
 (0)