Path: blob/master/src/packages/frontend/editors/unknown/editor.tsx
1691 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { React, useActions, useTypedRedux, CSS } from "../../app-framework";6import { delay } from "awaiting";7import { webapp_client } from "../../webapp-client";8import { Button, Alert, Typography, Row, Col } from "antd";9const { Text } = Typography;10import { register_file_editor } from "../../frame-editors/frame-tree/register";11import { filename_extension_notilde } from "@cocalc/util/misc";12import { Loading } from "../../components";13import { Editor as CodeEditor } from "../../frame-editors/code-editor/editor";14import { Actions as CodeEditorActions } from "../../frame-editors/code-editor/actions";1516const STYLE: CSS = {17margin: "0 auto",18padding: "20px",19maxWidth: "1000px",20};2122interface Props {23path: string;24project_id: string;25}2627async function get_mime({ project_id, path, set_mime, set_err, set_snippet }) {28try {29let mime = "";30const compute_server_id =31(await webapp_client.project_client.getServerIdForPath({32project_id,33path,34})) ?? 0;35const { stdout: mime_raw, exit_code: exit_code1 } =36await webapp_client.project_client.exec({37project_id,38command: "file",39args: ["-b", "--mime-type", path],40compute_server_id,41filesystem: true,42});43if (exit_code1 != 0) {44set_err(`Error: exit_code1 = ${exit_code1}`);45} else {46mime = mime_raw.split("\n")[0].trim();47set_mime(mime);48}4950const is_binary = !mime.startsWith("text/");51// limit number of bytes – it could be a "one-line" monster file.52// We *ONLY* limit by number of bytes, because limiting by both53// bytes and lines isn't supported in POSIX (e.g., macos), even though54// it works in Linux.55const content_cmd = is_binary56? {57command: "hexdump",58args: ["-C", "-n", "512", path],59}60: {61command: "head",62args: ["-c", "2000", path],63};6465const { stdout: raw, exit_code: exit_code2 } =66await webapp_client.project_client.exec({67project_id,68...content_cmd,69compute_server_id,70filesystem: true,71});72if (exit_code2 != 0) {73set_err(`Error: exit_code2 = ${exit_code2}`);74} else {75if (is_binary) {76set_snippet(raw);77} else {78set_snippet(79// 80 char line break and limit overall length80raw81.trim()82.slice(0, 20 * 80)83.split(/(.{0,80})/g)84.filter((x) => !!x)85.join(""),86);87}88}89} catch (err) {90set_err(err.toString());91}92}9394export const UnknownEditor: React.FC<Props> = (props: Props) => {95const { path, project_id } = props;96const ext = filename_extension_notilde(path).toLowerCase();97const NAME = useTypedRedux("customize", "site_name");98const actions = useActions({ project_id });99const [mime, set_mime] = React.useState("");100const [err, set_err] = React.useState("");101const [snippet, set_snippet] = React.useState("");102103React.useEffect(() => {104if (mime) return;105get_mime({ project_id, path, set_mime, set_err, set_snippet });106}, []);107108React.useEffect(() => {109if (actions == null) return;110switch (mime) {111case "inode/directory":112(async () => {113// It is actually a directory so we know what to do.114// See https://github.com/sagemathinc/cocalc/issues/5212115actions.open_directory(path);116// We delay before closing this file, since closing the file causes a117// state change to this component at the same time118// as updating (which is a WARNING).119await delay(1);120actions.close_file(path);121})();122break;123case "text/plain":124if (ext) {125// automatically register the code editor126register_file_editor({127ext: [ext],128component: CodeEditor,129Actions: CodeEditorActions,130});131(async () => {132actions.close_file(path);133await delay(1);134actions.open_file({ path });135})();136}137break;138}139}, [mime]);140141function render_ext() {142return (143<Text strong>144<Text code>*.{ext}</Text>145</Text>146);147}148149const explanation = React.useMemo(() => {150if (mime == "inode/x-empty") {151return (152<span>153This file is empty and has the unknown file-extension: {render_ext()}.154</span>155);156} else if (mime.startsWith("text/")) {157return (158<span>159This file might contain plain text, but the file-extension:{" "}160{render_ext()}161is unknown. Try the Code Editor!162</span>163);164} else {165return (166<span>167This is likely a binary file and the file-extension: {render_ext()} is168unknown. Preferrably, you have to open this file via a library/package169in a programming environment, like a Jupyter Notebook.170</span>171);172}173}, [mime]);174175async function register(ext, editor: "code") {176switch (editor) {177case "code":178register_file_editor({179ext: [ext],180component: CodeEditor,181Actions: CodeEditorActions,182});183break;184default:185console.warn(`Unknown editor of type ${editor}, aborting.`);186return;187}188if (actions == null) {189console.warn(190`Project Actions for ${project_id} not available – shouldn't happen.`,191);192return;193}194actions.close_file(path);195await delay(1);196actions.open_file({ path });197}198199function render_header() {200return <h1>Unknown file extension</h1>;201}202203function render_info() {204return (205<div>206{NAME} does not know what to do with this file with the extension{" "}207{render_ext()}. For this session, you can register one of the editors to208open up this file.209</div>210);211}212213function render_warning() {214if (mime.startsWith("text/")) return;215return (216<Alert217message="Warning"218description="Opening binary files could possibly modify and hence damage them. If this happens, you can use Files → Backup to restore them."219type="warning"220showIcon221/>222);223}224225function render_register() {226return (227<>228<div>229{NAME} detected that the file's content has the MIME code{" "}230<Text strong>231<Text code>{mime}</Text>232</Text>233. {explanation}234</div>235<div>The following editors are available:</div>236<ul>237<li>238<Button onClick={() => register(ext, "code")}>239Open {render_ext()} using <Text code>Code Editor</Text>240</Button>241</li>242</ul>243<div>244<Text type="secondary">245<Text strong>Note:</Text> by clicking this button this file will246open immediately. This will be remembered until you open up {NAME}{" "}247again or refresh this page. Alternatively, rename this file's file248extension.249</Text>250</div>251</>252);253}254255function render_content() {256if (!snippet) return;257return (258<>259<div>The content of this file starts like that:</div>260<div>261<pre style={{ fontSize: "70%" }}>{snippet}</pre>262</div>263</>264);265}266267function render() {268if (!mime) {269return <Loading theme={"medium"} />;270}271return (272<>273<Col flex={1}>{render_header()}</Col>274<Col flex={1}>{render_info()}</Col>275<Col flex={1}>{render_warning()}</Col>276<Col flex={1}>{render_register()}</Col>277<Col flex={1}>{render_content()}</Col>278</>279);280}281282return (283<div style={{ overflow: "auto" }}>284<div style={STYLE}>285{err ? (286<Alert type="error" message="Error" showIcon description={err} />287) : (288<Row gutter={[24, 24]}>{render()}</Row>289)}290</div>291</div>292);293};294295296