CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/admin/json-editor.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2023 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Button, Space, Typography } from "antd";
7
import jsonic from "jsonic";
8
import React, { useState } from "react";
9
import { useIntl } from "react-intl";
10
11
import { CSS } from "@cocalc/frontend/app-framework";
12
import { labels } from "@cocalc/frontend/i18n";
13
import { COLORS } from "@cocalc/util/theme";
14
15
const { Paragraph } = Typography;
16
17
// this is a simple json editor, basically a textarea that processes its content using jsonic
18
19
interface Props {
20
value: string;
21
rows?: number;
22
onSave: (value: string) => void;
23
savePosition?: "top-bottom" | "top";
24
readonly?: boolean;
25
}
26
27
export const JsonEditor: React.FC<Props> = (props: Props) => {
28
const intl = useIntl();
29
const { value, onSave, rows = 3, readonly = false } = props;
30
const [error, setError] = useState<string>("");
31
const [focused, setFocused] = useState<boolean>(false);
32
const [editing, setEditing] = useState<string>(value);
33
34
function doCommit(save: boolean) {
35
try {
36
const val = jsonic(editing); // might throw error
37
const oneLine = JSON.stringify(val);
38
setEditing(oneLine); // one-line string
39
setFocused(false);
40
setError("");
41
if (save) onSave(oneLine);
42
} catch (err) {
43
setError(err.message);
44
}
45
}
46
47
function setFormatted() {
48
try {
49
setEditing(JSON.stringify(jsonic(editing), null, 2));
50
} catch (err) {
51
setError(err.message);
52
}
53
}
54
55
function onChange(next: string) {
56
setEditing(next);
57
}
58
59
function renderError(): JSX.Element | null {
60
if (!error) return null;
61
return <div style={{ color: "red" }}>{error}</div>;
62
}
63
64
function doCancel() {
65
setEditing(value); // that's the original value when the component was instantiated
66
setError("");
67
setFocused(false);
68
}
69
70
function onFocus() {
71
if (readonly) return;
72
setFormatted();
73
setFocused(true);
74
}
75
76
const style: CSS = {
77
...(!focused && { color: COLORS.GRAY, cursor: "pointer" }),
78
width: "100%",
79
};
80
81
function renderMain(): JSX.Element {
82
if (focused) {
83
return (
84
<textarea
85
spellCheck="false"
86
onFocus={onFocus}
87
style={style}
88
rows={rows}
89
value={editing}
90
onChange={(event) => {
91
onChange(event.target.value);
92
}}
93
/>
94
);
95
} else {
96
return (
97
<Paragraph
98
onClick={onFocus}
99
type={readonly ? "secondary" : undefined}
100
style={{
101
...(readonly ? {} : { cursor: "pointer" }),
102
fontFamily: "monospace",
103
fontSize: "90%",
104
}}
105
>
106
{editing}
107
</Paragraph>
108
);
109
}
110
}
111
112
function renderButtons() {
113
if (readonly) return;
114
return (
115
<Space>
116
<Button
117
size="small"
118
type={focused ? "primary" : undefined}
119
disabled={!focused}
120
onClick={() => doCommit(true)}
121
>
122
Commit
123
</Button>
124
<Button size="small" disabled={!focused} onClick={doCancel}>
125
{intl.formatMessage(labels.cancel)}
126
</Button>
127
</Space>
128
);
129
}
130
131
return (
132
<div>
133
<div>{renderMain()}</div>
134
{renderError()}
135
{renderButtons()}
136
</div>
137
);
138
};
139
140