vzcode 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ .vz-code-editor{flex:1;display:flex;font-family:var(--vzcode-font-family);overflow-x:auto}.vz-code-editor .cm-editor{flex:1}.vz-sidebar{height:100%;width:200px;background:rgb(54,65,89);display:flex;flex-direction:column;justify-content:space-between}.vz-sidebar .full-box{padding:7px;display:flex;flex-direction:row;align-items:center;justify-content:space-between}.vz-sidebar .files{overflow:auto}.vz-sidebar .file-or-directory{display:flex;flex-direction:row;align-items:center;justify-content:space-between;cursor:pointer;margin:0 10px;color:#dbdde1;border-radius:5px}.vz-sidebar .link-name{margin-top:30px;margin-left:10px;color:#9aa0ac;font-size:12px;text-decoration:none}.vz-sidebar .new-btn{justify-content:right;margin-right:10px;cursor:pointer}.vz-sidebar .utils{display:flex}.vz-sidebar .utilities{margin-right:3px}.vz-sidebar .file-or-directory:hover,.vz-sidebar .settings:hover{background-color:#4a5469}.vz-sidebar .name{display:flex;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.vz-sidebar .indentation{margin-left:20px}.vz-sidebar .arrow-wrapper{margin-right:4px}.vz-sidebar .settings{height:30px;color:#dbdde1;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:14px 0;margin:10px;border-radius:5px}.vz-tab-list{display:flex;background-color:#252526}.vz-tab-list .tab{background:rgb(45,45,45);color:#fff;font-family:var(--vzcode-font-family);align-items:center;display:flex;padding-top:2px;padding-left:10px;padding-right:10px;border-top-left-radius:10px;border-top-right-radius:10px;margin-top:5px;margin-left:2px;margin-right:2px;cursor:pointer}.vz-tab-list .tab .tab-close{margin-left:5px}.vz-tab-list .tab .tab-close:hover{border-radius:30%;background-color:#252526}.vz-tab-list .tab.active{background-color:#292c34}:root{--vzcode-font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:rgb(41,44,52)}::-webkit-scrollbar-thumb{background:rgb(37,37,38)}::-webkit-scrollbar-thumb:hover{background:#555}html,body,#root,.app{height:100%}.app{background-color:#292c34;font-family:Poppins,sans-serif;display:flex}.app .left{display:flex}.app .right{display:flex;flex:1;flex-direction:column;min-width:0}
package/dist/index.html CHANGED
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
@@ -18,8 +18,8 @@
18
18
  href="https://unpkg.com/bootstrap@5.3.0/dist/css/bootstrap.min.css"
19
19
  rel="stylesheet"
20
20
  />
21
- <script type="module" crossorigin src="/assets/index-41607816.js"></script>
22
- <link rel="stylesheet" href="/assets/index-8bac68c9.css">
21
+ <script type="module" crossorigin src="/assets/index-80e9e6df.js"></script>
22
+ <link rel="stylesheet" href="/assets/index-ccd52a89.css">
23
23
  </head>
24
24
  <body>
25
25
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  "src/**"
12
12
  ],
13
13
  "scripts": {
14
- "test": "vitest",
14
+ "test": "vitest run",
15
15
  "test-interactive": "npm run build; cd test/sampleDirectories/kitchenSink; node ../../../src/server/index.js",
16
16
  "prettier": "prettier {*.*,**/*.*} --write",
17
17
  "tsc": "tsc --noEmit",
@@ -46,39 +46,40 @@
46
46
  },
47
47
  "homepage": "https://github.com/vizhub-core/vzcode#readme",
48
48
  "dependencies": {
49
- "@codemirror/lang-css": "^6.2.0",
50
- "@codemirror/lang-html": "^6.4.4",
49
+ "@codemirror/lang-css": "^6.2.1",
50
+ "@codemirror/lang-html": "^6.4.5",
51
51
  "@codemirror/lang-javascript": "^6.1.9",
52
- "@codemirror/lang-markdown": "^6.1.1",
52
+ "@codemirror/lang-markdown": "^6.2.0",
53
53
  "@codemirror/state": "^6.2.1",
54
54
  "@codemirror/theme-one-dark": "^6.1.2",
55
+ "@replit/codemirror-interact": "^6.1.0",
55
56
  "@teamwork/websocket-json-stream": "^2.0.0",
56
- "@uiw/codemirror-themes-all": "^4.20.2",
57
+ "@uiw/codemirror-themes-all": "^4.21.9",
57
58
  "codemirror": "^6.0.1",
58
59
  "codemirror-ot": "^4.0.0",
59
60
  "d3-array": "^3.2.4",
60
61
  "diff-match-patch": "^1.0.5",
61
- "dotenv": "^16.3.0",
62
+ "dotenv": "^16.3.1",
62
63
  "express": "^4.18.2",
63
64
  "json0-ot-diff": "^1.1.2",
64
65
  "ngrok": "^4.3.3",
65
66
  "open": "^9.1.0",
66
67
  "react": "^18.2.0",
67
- "react-bootstrap": "^2.7.4",
68
+ "react-bootstrap": "^2.8.0",
68
69
  "react-dom": "^18.2.0",
69
- "react-select": "^5.7.3",
70
+ "react-select": "^5.7.4",
70
71
  "sharedb": "^4.0.0",
71
72
  "sharedb-client-browser": "^4.2.1",
72
73
  "ws": "^8.13.0"
73
74
  },
74
75
  "devDependencies": {
75
- "@types/react": "^18.2.12",
76
- "@types/react-dom": "^18.2.5",
77
- "@vitejs/plugin-react": "^4.0.0",
78
- "prettier": "^2.8.8",
79
- "sass": "^1.64.0",
80
- "vite": "^4.3.9",
81
- "vitest": "^0.32.2",
82
- "typescript": "^5.1.6"
76
+ "@types/react": "^18.2.20",
77
+ "@types/react-dom": "^18.2.7",
78
+ "@vitejs/plugin-react": "^4.0.4",
79
+ "prettier": "^3.0.1",
80
+ "sass": "^1.65.1",
81
+ "typescript": "^5.1.6",
82
+ "vite": "^4.4.9",
83
+ "vitest": "^0.34.1"
83
84
  }
84
85
  }
@@ -4,11 +4,14 @@ import { json1Presence } from '../ot';
4
4
  import { randomId } from '../randomId';
5
5
  import { CodeEditor } from './CodeEditor';
6
6
  import { diff } from './diff';
7
- import { Settings } from './settings';
7
+ import { Settings } from './Settings';
8
8
  import { Sidebar } from './Sidebar';
9
+ import { FileId, Files } from '../types';
10
+ import { TabList } from './TabList';
11
+ import { useOpenDirectories } from './useOpenDirectories';
12
+ import { useTabsState } from './useTabsState';
13
+ import { defaultTheme } from './themes';
9
14
  import './style.scss';
10
- import { oneDark } from '@codemirror/theme-one-dark';
11
- import { FileId } from '../types';
12
15
 
13
16
  // Register our custom JSON1 OT type that supports presence.
14
17
  // See https://github.com/vizhub-core/json1-presence
@@ -36,20 +39,31 @@ function App() {
36
39
 
37
40
  // The `doc.data` part of the ShareDB document,
38
41
  // updated on each change to decouple rendering from ShareDB.
39
- const [data, setData] = useState(null);
42
+ const [data, setData] = useState<Files>(null);
40
43
 
41
44
  // The id of the currently open file tab.
42
- const [activeFileId, setActiveFileId] = useState(null);
45
+ const [activeFileId, setActiveFileId] = useState<FileId>(null);
43
46
 
44
47
  // The ordered list of tabs.
45
- const [tabList, setTabList] = useState([]);
48
+ const [tabList, setTabList] = useState<Array<FileId>>([]);
49
+
50
+ // Logic for opening and closing tabs.
51
+ const { closeTab, openTab } = useTabsState(
52
+ activeFileId,
53
+ setActiveFileId,
54
+ tabList,
55
+ setTabList,
56
+ );
46
57
 
47
58
  // The current theme.
48
- const [theme, setTheme] = useState(oneDark);
59
+ const [theme, setTheme] = useState<string>(defaultTheme);
49
60
 
50
61
  // True to show the settings modal.
51
62
  const [isSettingsOpen, setIsSettingsOpen] = useState(false);
52
63
 
64
+ // The set of open directories.
65
+ const { isDirectoryOpen, toggleDirectory } = useOpenDirectories();
66
+
53
67
  // Set up the connection to ShareDB.
54
68
  useEffect(() => {
55
69
  // Since there is only ever a single document,
@@ -74,13 +88,13 @@ function App() {
74
88
  // Listen for all changes and update `data`.
75
89
  // This decouples rendering logic from ShareDB.
76
90
  // This callback gets called on each change.
77
- shareDBDoc.on('op', (op) => {
91
+ shareDBDoc.on('op', () => {
78
92
  setData(shareDBDoc.data);
79
93
  });
80
94
 
81
95
  // Set up presence.
82
96
  // See https://github.com/share/sharedb/blob/master/examples/rich-text-presence/client.js#L53
83
- const docPresence = shareDBDoc.connection.getDocPresence(collection, id);
97
+ const docPresence = connection.getDocPresence(collection, id);
84
98
 
85
99
  // Subscribe to receive remote presence updates.
86
100
  docPresence.subscribe(function (error) {
@@ -96,45 +110,12 @@ function App() {
96
110
 
97
111
  // TODO unsubscribe from presence
98
112
  // TODO unsubscribe from doc
113
+ return () => {
114
+ // shareDBDoc.destroy();
115
+ // docPresence.destroy();
116
+ };
99
117
  }, []);
100
118
 
101
- // Called when a tab is closed.
102
- const closeTab = useCallback(
103
- (fileIdToRemove) => {
104
- const i = tabList.findIndex((fileId) => fileId === fileIdToRemove);
105
-
106
- // Support calling `closeTab` on a fileId that is not open,
107
- // such as when a file is deleted..
108
- if (i !== -1) {
109
- // Remove the tab from the tab list.
110
- const newTabList = [...tabList.slice(0, i), ...tabList.slice(i + 1)];
111
- setTabList(newTabList);
112
-
113
- // If we are closing the active file,
114
- if (activeFileId === fileIdToRemove) {
115
- // set the new active file to the next tab over,
116
- if (newTabList.length > 0) {
117
- setActiveFileId(i === 0 ? newTabList[i] : newTabList[i - 1]);
118
- } else {
119
- // or clear out the active file
120
- // if we've closed the last tab.
121
- setActiveFileId(null);
122
- }
123
- }
124
- }
125
- },
126
- [tabList, activeFileId]
127
- );
128
-
129
- const handleCloseTabClick = useCallback(
130
- (fileIdToRemove) => (event) => {
131
- // Stop propagation so that the outer listener doesn't fire.
132
- event.stopPropagation();
133
- closeTab(fileIdToRemove);
134
- },
135
- [closeTab]
136
- );
137
-
138
119
  // Called when a file in the sidebar is double-clicked.
139
120
  const handleRenameFileClick = useCallback(
140
121
  (fileId: FileId) => {
@@ -151,7 +132,7 @@ function App() {
151
132
  shareDBDoc.submitOp(diff(currentDocument, nextDocument));
152
133
  }
153
134
  },
154
- [shareDBDoc]
135
+ [shareDBDoc],
155
136
  );
156
137
 
157
138
  const deleteFile = useCallback(
@@ -165,17 +146,7 @@ function App() {
165
146
  delete nextDocument[fileId];
166
147
  shareDBDoc.submitOp(diff(currentDocument, nextDocument));
167
148
  },
168
- [shareDBDoc, closeTab]
169
- );
170
-
171
- const handleFileClick = useCallback(
172
- (fileId: FileId) => {
173
- setActiveFileId(fileId);
174
- if (!tabList.includes(fileId)) {
175
- setTabList([...tabList, fileId]);
176
- }
177
- },
178
- [tabList]
149
+ [shareDBDoc, closeTab],
179
150
  );
180
151
 
181
152
  // TODO prompt the user "Are you sure?"
@@ -186,7 +157,7 @@ function App() {
186
157
  event.stopPropagation();
187
158
  deleteFile(fileId);
188
159
  },
189
- [deleteFile]
160
+ [deleteFile],
190
161
  );
191
162
 
192
163
  const createFile = useCallback(() => {
@@ -201,20 +172,10 @@ function App() {
201
172
  shareDBDoc.submitOp(diff(currentDocument, nextDocument));
202
173
  }, [shareDBDoc]);
203
174
 
204
- // True if we are ready to actually render the active tab.
205
- const tabValid = data && activeFileId;
206
-
207
175
  const handleSettingsClose = useCallback(() => {
208
176
  setIsSettingsOpen(false);
209
177
  }, []);
210
178
 
211
- const fileNameSplit = (fileName) => {
212
- const split = fileName.split('/');
213
- //adding the folder that the is in to the tab name
214
- if (split.length === 1) return split[split.length - 1];
215
- return split[split.length - 2] + '/' + split[split.length - 1];
216
- };
217
-
218
179
  return (
219
180
  <div className="app">
220
181
  <div className="left">
@@ -223,9 +184,10 @@ function App() {
223
184
  files={data}
224
185
  handleRenameFileClick={handleRenameFileClick}
225
186
  handleDeleteFileClick={handleDeleteFileClick}
226
- handleFileClick={handleFileClick}
187
+ handleFileClick={openTab}
227
188
  setIsSettingsOpen={setIsSettingsOpen}
228
- isSettingsOpen={isSettingsOpen}
189
+ isDirectoryOpen={isDirectoryOpen}
190
+ toggleDirectory={toggleDirectory}
229
191
  />
230
192
  <Settings
231
193
  show={isSettingsOpen}
@@ -234,30 +196,15 @@ function App() {
234
196
  />
235
197
  </div>
236
198
  <div className="right">
237
- <div className="tab-list">
238
- {tabList.map((fileId) => (
239
- <div
240
- key={fileId}
241
- className={
242
- tabValid
243
- ? `tab${fileId === activeFileId ? ' active' : ''}`
244
- : null
245
- }
246
- onClick={() => {
247
- setActiveFileId(fileId);
248
- }}
249
- >
250
- {tabValid ? fileNameSplit(data[fileId].name) : ''}
251
- <div
252
- className={activeFileId ? 'bx bx-x tab-close' : ''}
253
- onClick={handleCloseTabClick(fileId)}
254
- ></div>
255
- </div>
256
- ))}
257
- </div>
199
+ <TabList
200
+ files={data}
201
+ tabList={tabList}
202
+ activeFileId={activeFileId}
203
+ setActiveFileId={setActiveFileId}
204
+ closeTab={closeTab}
205
+ />
258
206
  {data && activeFileId ? (
259
207
  <CodeEditor
260
- className="editor"
261
208
  shareDBDoc={shareDBDoc}
262
209
  localPresence={localPresence}
263
210
  docPresence={docPresence}
@@ -0,0 +1,115 @@
1
+ import { EditorView, basicSetup } from 'codemirror';
2
+ import { Compartment, EditorState } from '@codemirror/state';
3
+ import { javascript } from '@codemirror/lang-javascript';
4
+ import { markdown } from '@codemirror/lang-markdown';
5
+ import { html } from '@codemirror/lang-html';
6
+ import { css } from '@codemirror/lang-css';
7
+ import { json1Sync } from 'codemirror-ot';
8
+ import { json1Presence, textUnicode } from '../../ot';
9
+ import { FileId, ShareDBDoc } from '../../types';
10
+ import { json1PresenceBroadcast } from './json1PresenceBroadcast';
11
+ import { json1PresenceDisplay } from './json1PresenceDisplay';
12
+ import { widgets } from './widgets';
13
+ // Singleton cache of CodeMirror instances
14
+ // These are created, but never destroyed.
15
+ // TODO consider invalidating this cache at some point.
16
+ // * Keys are file ids
17
+ // * Values are CodeMirror instances (TODO add CodeMirror type)
18
+ const editorCache = new Map<FileId, any>();
19
+
20
+ // Gets a value at a path in an object.
21
+ // e.g. getAtPath({a: {b: 1}}, ['a', 'b']) === 1
22
+ const getAtPath = (obj, path) => {
23
+ let current = obj;
24
+ for (const key of path) {
25
+ if (!current) return undefined;
26
+ current = current[key];
27
+ }
28
+ return current;
29
+ };
30
+
31
+ // Gets or creates a CodeMirror editor for the given file id.
32
+ export const getOrCreateEditor = ({
33
+ fileId,
34
+ shareDBDoc,
35
+ filesPath,
36
+ localPresence,
37
+ docPresence,
38
+ theme,
39
+ }: {
40
+ fileId: FileId;
41
+ shareDBDoc: ShareDBDoc<any>;
42
+ filesPath: string[];
43
+ localPresence: any;
44
+ docPresence: any;
45
+ theme: any;
46
+ }) => {
47
+ const data = shareDBDoc.data;
48
+ const textPath = [...filesPath, fileId, 'text'];
49
+ const namePath = [...filesPath, fileId, 'name'];
50
+
51
+ const text = getAtPath(data, textPath);
52
+ const name = getAtPath(data, namePath);
53
+
54
+ const fileExtension = name.split('.').pop();
55
+
56
+ // TODO revisit if this is the right approach for dynamic themes
57
+ let themeSet = new Compartment();
58
+
59
+ const extensions = [];
60
+
61
+ // This plugin implements multiplayer editing,
62
+ // real-time synchronozation of changes across clients.
63
+ // Does not deal with showing others' cursors.
64
+ extensions.push(
65
+ json1Sync({
66
+ shareDBDoc,
67
+ path: textPath,
68
+ json1: json1Presence,
69
+ textUnicode,
70
+ }),
71
+ );
72
+
73
+ // Deals with broadcasting changes in cursor location and selection.
74
+ if (localPresence)
75
+ extensions.push(json1PresenceBroadcast({ path: textPath, localPresence }));
76
+
77
+ // Deals with receiving the broadcas from other clients and displaying them.
78
+ if (docPresence)
79
+ extensions.push(json1PresenceDisplay({ path: textPath, docPresence }));
80
+
81
+ extensions.push(basicSetup);
82
+ extensions.push(themeSet.of(theme));
83
+
84
+ // TODO handle dynamic changing of the file extension.
85
+ if (
86
+ fileExtension === 'js' ||
87
+ fileExtension === 'json' ||
88
+ fileExtension === 'jsx' ||
89
+ fileExtension === 'ts'
90
+ ) {
91
+ extensions.push(javascript());
92
+ } else if (fileExtension === 'html') {
93
+ extensions.push(html());
94
+ } else if (fileExtension === 'css') {
95
+ extensions.push(css());
96
+ } else if (fileExtension === 'md') {
97
+ extensions.push(markdown());
98
+ }
99
+
100
+ extensions.push(widgets());
101
+
102
+ let editor = editorCache.get(fileId);
103
+ if (!editor) {
104
+ editor = new EditorView({
105
+ state: EditorState.create({
106
+ doc: text,
107
+ extensions,
108
+ }),
109
+ });
110
+
111
+ // Populate the cache.
112
+ editorCache.set(fileId, editor);
113
+ }
114
+ return editor;
115
+ };
@@ -0,0 +1,50 @@
1
+ import { useRef, useLayoutEffect } from 'react';
2
+ import { FileId } from '../../types';
3
+ import { defaultTheme, themesByLabel } from '../themes';
4
+ import { getOrCreateEditor } from './getOrCreateEditor';
5
+ import './style.scss';
6
+
7
+ export const CodeEditor = ({
8
+ activeFileId,
9
+ shareDBDoc,
10
+ localPresence,
11
+ docPresence,
12
+ theme = defaultTheme,
13
+ filesPath = [],
14
+ }: {
15
+ activeFileId: FileId;
16
+ shareDBDoc: any;
17
+ localPresence?: any;
18
+ docPresence?: any;
19
+ theme?: string;
20
+
21
+ // The path of the files object in the ShareDB document.
22
+ // Defaults to the root of the document.
23
+ filesPath?: string[];
24
+ }) => {
25
+ const ref = useRef<HTMLDivElement>(null);
26
+
27
+ useLayoutEffect(() => {
28
+ if (!ref.current) return;
29
+ if (!shareDBDoc) return;
30
+
31
+ // `getOrCreateEditor` gets called
32
+ // every time the active file changes.
33
+ const editor = getOrCreateEditor({
34
+ fileId: activeFileId,
35
+ shareDBDoc,
36
+ filesPath,
37
+ localPresence,
38
+ docPresence,
39
+ // TODO refactor this, make dynamic themes work
40
+ theme: themesByLabel[theme],
41
+ });
42
+ ref.current.appendChild(editor.dom);
43
+
44
+ return () => {
45
+ ref.current.removeChild(editor.dom);
46
+ };
47
+ }, [shareDBDoc, activeFileId]);
48
+
49
+ return <div className="vz-code-editor" ref={ref} />;
50
+ };
@@ -58,7 +58,7 @@ export const json1PresenceDisplay = ({ path, docPresence }) => [
58
58
  }),
59
59
  // Without this argument, we get the following error:
60
60
  // Uncaught Error: Ranges must be added sorted by `from` position and `startSide`
61
- true
61
+ true,
62
62
  );
63
63
 
64
64
  // Somehow this triggers re-rendering of the Decorations.
@@ -74,7 +74,7 @@ export const json1PresenceDisplay = ({ path, docPresence }) => [
74
74
  },
75
75
  {
76
76
  decorations: (v) => v.decorations,
77
- }
77
+ },
78
78
  ),
79
79
  presenceTheme,
80
80
  ];
@@ -0,0 +1,11 @@
1
+ .vz-code-editor {
2
+ flex: 1;
3
+ display: flex;
4
+ font-family: var(--vzcode-font-family);
5
+ overflow-x: auto;
6
+
7
+ /* TODO QA this */
8
+ .cm-editor {
9
+ flex: 1;
10
+ }
11
+ }
@@ -0,0 +1,48 @@
1
+ import interact from '@replit/codemirror-interact';
2
+
3
+ // Interactive code widgets.
4
+ // * Number dragger
5
+ // * Boolean toggler
6
+ // * URL clicker
7
+ // * TODO color picker
8
+ // Inspired by:
9
+ // https://github.com/replit/codemirror-interact/blob/master/dev/index.ts
10
+ export const widgets = () =>
11
+ interact({
12
+ rules: [
13
+ // a rule for a number dragger
14
+ {
15
+ // the regexp matching the value
16
+ regexp: /-?\b\d+\.?\d*\b/g,
17
+ // set cursor to "ew-resize" on hover
18
+ cursor: 'ew-resize',
19
+ // change number value based on mouse X movement on drag
20
+ onDrag: (text, setText, e) => {
21
+ const newVal = Number(text) + e.movementX;
22
+ if (isNaN(newVal)) return;
23
+ setText(newVal.toString());
24
+ },
25
+ },
26
+ // bool toggler
27
+ {
28
+ regexp: /true|false/g,
29
+ cursor: 'pointer',
30
+ onClick: (text, setText) => {
31
+ switch (text) {
32
+ case 'true':
33
+ return setText('false');
34
+ case 'false':
35
+ return setText('true');
36
+ }
37
+ },
38
+ },
39
+ // url clicker
40
+ {
41
+ regexp: /https?:\/\/[^ "]+/g,
42
+ cursor: 'pointer',
43
+ onClick: (text) => {
44
+ window.open(text);
45
+ },
46
+ },
47
+ ],
48
+ });
@@ -3,29 +3,7 @@ import Modal from 'react-bootstrap/Modal';
3
3
  import Form from 'react-bootstrap/Form';
4
4
  import Button from 'react-bootstrap/Button';
5
5
  import Select from 'react-select';
6
- import { oneDark } from '@codemirror/theme-one-dark';
7
- import { okaidia } from '@uiw/codemirror-theme-okaidia';
8
- import { abcdef } from '@uiw/codemirror-theme-abcdef';
9
- import { dracula } from '@uiw/codemirror-theme-dracula';
10
- import { eclipse } from '@uiw/codemirror-theme-eclipse';
11
- import { githubDark } from '@uiw/codemirror-theme-github';
12
- import { material } from '@uiw/codemirror-theme-material';
13
- import { nord } from '@uiw/codemirror-theme-nord';
14
- import { xcodeLight } from '@uiw/codemirror-theme-xcode';
15
-
16
- // TODO document where this list came from
17
- // TODO research which themes are available for CodeMirror 6
18
- const themes = [
19
- { value: abcdef, label: 'abcdef' },
20
- { value: dracula, label: 'darcula' },
21
- { value: eclipse, label: 'eclipse' },
22
- { value: material, label: 'material' },
23
- { value: nord, label: 'nord' },
24
- { value: oneDark, label: 'One Dark' },
25
- { value: okaidia, label: 'okaidia' },
26
- { value: githubDark, label: 'github' },
27
- { value: xcodeLight, label: 'xcode' },
28
- ];
6
+ import { themes } from './themes';
29
7
 
30
8
  const saveTimes = [
31
9
  { value: 1, label: '1 second' },
@@ -34,12 +12,16 @@ const saveTimes = [
34
12
  { value: 30, label: '30 seconds' },
35
13
  ];
36
14
 
37
- export const Settings = ({ show, onClose, setTheme, editor }) => {
15
+ export const Settings = ({ show, onClose, setTheme }) => {
38
16
  const handleChange = useCallback((selectedOption) => {
39
- setTheme(selectedOption.value);
40
- editor.dispatch({
41
- effects: themeSet.reconfigure(selectedOption.value),
42
- });
17
+ setTheme(selectedOption.label);
18
+ // TODO make this work.
19
+ // Sketch of algorithm:
20
+ // for each editor in the editor cache,
21
+ // update the theme
22
+ // editor.dispatch({
23
+ // effects: themeSet.reconfigure(selectedOption.value),
24
+ // });
43
25
  }, []);
44
26
 
45
27
  const handleSaveTimeChange = useCallback((selectedOption) => {
@@ -54,7 +36,12 @@ export const Settings = ({ show, onClose, setTheme, editor }) => {
54
36
  }, []);
55
37
 
56
38
  return show ? (
57
- <Modal show={show} onHide={onClose} animation={false}>
39
+ <Modal
40
+ className="vz-settings"
41
+ show={show}
42
+ onHide={onClose}
43
+ animation={false}
44
+ >
58
45
  <Modal.Header closeButton>
59
46
  <Modal.Title>Settings</Modal.Title>
60
47
  </Modal.Header>
@@ -68,22 +55,14 @@ export const Settings = ({ show, onClose, setTheme, editor }) => {
68
55
  </Form.Group>
69
56
  <Form.Group className="mb-3" controlId="formFork">
70
57
  <Form.Label>Theme</Form.Label>
71
- <Select
72
- classvalue="themes"
73
- options={themes}
74
- onChange={handleChange}
75
- />
58
+ <Select options={themes} onChange={handleChange} />
76
59
  <Form.Text className="text-muted">
77
60
  Select a color theme for the editor
78
61
  </Form.Text>
79
62
  </Form.Group>
80
63
  <Form.Group className="mb-3" controlId="formFork">
81
64
  <Form.Label>Auto-Save Time</Form.Label>
82
- <Select
83
- classvalue="themes"
84
- options={saveTimes}
85
- onChange={handleSaveTimeChange}
86
- />
65
+ <Select options={saveTimes} onChange={handleSaveTimeChange} />
87
66
  <Form.Text className="text-muted">Select an auto save time</Form.Text>
88
67
  </Form.Group>
89
68
  </Modal.Body>