Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/react-wrapper.tsx
1678 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
/* Wrapper in a React component of a non-react editor, so that we can fully rewrite
7
the UI using React without having to rewrite all the editors.
8
9
This should be used ONLY for sagews and jupyter classic and NOTHING ELSE.
10
TODO: There are still some is_public editors, but we shouldn't use any of them.
11
*/
12
13
import { debounce } from "lodash";
14
import { delay } from "awaiting";
15
import { React, useAsyncEffect } from "../app-framework";
16
import { useEffect, useMemo, useRef } from "react";
17
import { copy } from "@cocalc/util/misc";
18
import useResizeObserver from "use-resize-observer";
19
20
const WrappedEditor: React.FC<{ editor: any }> = ({ editor }) => {
21
const ref = useRef<any>(null);
22
const divRef = useRef<any>(null);
23
useResizeObserver({ ref: divRef });
24
25
const refresh = useMemo(() => {
26
// Refreshes -- cause the editor to resize itself
27
return debounce(
28
() => {
29
if (editor.show == null) {
30
typeof editor._show === "function" ? editor._show() : undefined;
31
} else {
32
editor.show();
33
}
34
},
35
350,
36
{ leading: true, trailing: true },
37
);
38
}, [editor]);
39
40
useAsyncEffect(
41
// setup
42
async (is_mounted) => {
43
// We use this delay (and AsyncEffect) because otherwise Jupyter classic
44
// gets stuck in "Loading...". It has something to do with the iframe
45
// trickiness.
46
await delay(0);
47
if (!is_mounted()) return;
48
const elt = $(ref.current);
49
if (elt.length > 0) {
50
elt.replaceWith(editor.element[0]);
51
}
52
editor.show();
53
if (typeof editor.focus === "function") {
54
editor.focus();
55
}
56
if (typeof editor.restore_view_state === "function") {
57
editor.restore_view_state();
58
}
59
window.addEventListener("resize", refresh);
60
},
61
() => {
62
// clean up
63
window.removeEventListener("resize", refresh);
64
// These cover all cases for jQuery type overrides.
65
if (typeof editor.save_view_state === "function") {
66
editor.save_view_state();
67
}
68
if (typeof editor.blur === "function") {
69
editor.blur();
70
}
71
editor.hide();
72
},
73
[],
74
);
75
76
useEffect(refresh);
77
78
// position relative is required by NotifyResize
79
return (
80
<div ref={divRef} className="smc-vfill" style={{ position: "relative" }}>
81
<span className="smc-editor-react-wrapper" ref={ref}></span>
82
</div>
83
);
84
};
85
86
// Used for caching
87
const editors = {};
88
89
function get_key(project_id: string, path: string): string {
90
return `${project_id}-${path}`;
91
}
92
93
export function get_editor(project_id: string, path: string) {
94
return editors[get_key(project_id, path)];
95
}
96
97
export function register_nonreact_editor(opts: {
98
f: (project_id: string, filename: string, extra_opts: object) => any;
99
ext: string | string[];
100
icon?: string;
101
is_public?: boolean;
102
}): void {
103
// Circle import issue -- since editor imports react-wrapper:
104
const { file_options } = require("../editor");
105
const { register_file_editor } = require("../project-file");
106
107
// Do this to make it crystal clear which extensions still
108
// use non-react editors:
109
/* console.log("register_nonreact_editor", {
110
ext: opts.ext,
111
is_public: opts.is_public,
112
});
113
*/
114
115
register_file_editor({
116
ext: opts.ext,
117
is_public: opts.is_public,
118
icon: opts.icon,
119
init(path: string, _redux, project_id: string): string {
120
const key = get_key(project_id, path);
121
122
if (editors[key] == null) {
123
// Overwrite functions called from the various file editors
124
const extra_opts = copy(file_options(path)?.opts ?? {});
125
const e = opts.f(project_id, path, extra_opts);
126
editors[key] = e;
127
}
128
return key;
129
},
130
131
generator(
132
path: string,
133
_redux,
134
project_id: string,
135
): Function | React.JSX.Element {
136
const key = get_key(project_id, path);
137
const wrapper_generator = function () {
138
if (editors[key] != null) {
139
return <WrappedEditor editor={editors[key]} />;
140
} else {
141
// GitHub #4231 and #4232 -- sometimes the editor gets rendered
142
// after it gets removed. Presumably this is just for a moment, but
143
// it's good to do something halfway sensible rather than hit a traceback in
144
// this case...
145
return <div>Please close then re-open this file.</div>;
146
}
147
};
148
wrapper_generator.get_editor = () => editors[key];
149
return wrapper_generator;
150
},
151
152
remove(path: string, _redux, project_id: string) {
153
const key = get_key(project_id, path);
154
if (editors[key]) {
155
editors[key].remove();
156
delete editors[key];
157
}
158
},
159
160
save(path: string, _redux, project_id: string): void {
161
if (opts.is_public) {
162
return;
163
}
164
const f = editors[get_key(project_id, path)]?.save;
165
if (f != null) f();
166
},
167
});
168
}
169
170