Path: blob/master/src/packages/frontend/editors/react-wrapper.tsx
1678 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/* Wrapper in a React component of a non-react editor, so that we can fully rewrite6the UI using React without having to rewrite all the editors.78This should be used ONLY for sagews and jupyter classic and NOTHING ELSE.9TODO: There are still some is_public editors, but we shouldn't use any of them.10*/1112import { debounce } from "lodash";13import { delay } from "awaiting";14import { React, useAsyncEffect } from "../app-framework";15import { useEffect, useMemo, useRef } from "react";16import { copy } from "@cocalc/util/misc";17import useResizeObserver from "use-resize-observer";1819const WrappedEditor: React.FC<{ editor: any }> = ({ editor }) => {20const ref = useRef<any>(null);21const divRef = useRef<any>(null);22useResizeObserver({ ref: divRef });2324const refresh = useMemo(() => {25// Refreshes -- cause the editor to resize itself26return debounce(27() => {28if (editor.show == null) {29typeof editor._show === "function" ? editor._show() : undefined;30} else {31editor.show();32}33},34350,35{ leading: true, trailing: true },36);37}, [editor]);3839useAsyncEffect(40// setup41async (is_mounted) => {42// We use this delay (and AsyncEffect) because otherwise Jupyter classic43// gets stuck in "Loading...". It has something to do with the iframe44// trickiness.45await delay(0);46if (!is_mounted()) return;47const elt = $(ref.current);48if (elt.length > 0) {49elt.replaceWith(editor.element[0]);50}51editor.show();52if (typeof editor.focus === "function") {53editor.focus();54}55if (typeof editor.restore_view_state === "function") {56editor.restore_view_state();57}58window.addEventListener("resize", refresh);59},60() => {61// clean up62window.removeEventListener("resize", refresh);63// These cover all cases for jQuery type overrides.64if (typeof editor.save_view_state === "function") {65editor.save_view_state();66}67if (typeof editor.blur === "function") {68editor.blur();69}70editor.hide();71},72[],73);7475useEffect(refresh);7677// position relative is required by NotifyResize78return (79<div ref={divRef} className="smc-vfill" style={{ position: "relative" }}>80<span className="smc-editor-react-wrapper" ref={ref}></span>81</div>82);83};8485// Used for caching86const editors = {};8788function get_key(project_id: string, path: string): string {89return `${project_id}-${path}`;90}9192export function get_editor(project_id: string, path: string) {93return editors[get_key(project_id, path)];94}9596export function register_nonreact_editor(opts: {97f: (project_id: string, filename: string, extra_opts: object) => any;98ext: string | string[];99icon?: string;100is_public?: boolean;101}): void {102// Circle import issue -- since editor imports react-wrapper:103const { file_options } = require("../editor");104const { register_file_editor } = require("../project-file");105106// Do this to make it crystal clear which extensions still107// use non-react editors:108/* console.log("register_nonreact_editor", {109ext: opts.ext,110is_public: opts.is_public,111});112*/113114register_file_editor({115ext: opts.ext,116is_public: opts.is_public,117icon: opts.icon,118init(path: string, _redux, project_id: string): string {119const key = get_key(project_id, path);120121if (editors[key] == null) {122// Overwrite functions called from the various file editors123const extra_opts = copy(file_options(path)?.opts ?? {});124const e = opts.f(project_id, path, extra_opts);125editors[key] = e;126}127return key;128},129130generator(131path: string,132_redux,133project_id: string,134): Function | React.JSX.Element {135const key = get_key(project_id, path);136const wrapper_generator = function () {137if (editors[key] != null) {138return <WrappedEditor editor={editors[key]} />;139} else {140// GitHub #4231 and #4232 -- sometimes the editor gets rendered141// after it gets removed. Presumably this is just for a moment, but142// it's good to do something halfway sensible rather than hit a traceback in143// this case...144return <div>Please close then re-open this file.</div>;145}146};147wrapper_generator.get_editor = () => editors[key];148return wrapper_generator;149},150151remove(path: string, _redux, project_id: string) {152const key = get_key(project_id, path);153if (editors[key]) {154editors[key].remove();155delete editors[key];156}157},158159save(path: string, _redux, project_id: string): void {160if (opts.is_public) {161return;162}163const f = editors[get_key(project_id, path)]?.save;164if (f != null) f();165},166});167}168169170