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.
@@ -1,4 +1,4 @@
1
- import { useCallback } from 'react';
1
+ import { useCallback, useMemo } from 'react';
2
2
  import { Item } from './Item';
3
3
  import { Listing } from './Listing';
4
4
  import { DirectoryArrowSVG } from './DirectoryArrowSVG';
@@ -11,7 +11,7 @@ export const DirectoryListing = ({
11
11
  handleRenameFileClick,
12
12
  handleDeleteFileClick,
13
13
  handleFileClick,
14
- openDirectories,
14
+ isDirectoryOpen,
15
15
  toggleDirectory,
16
16
  }: {
17
17
  name: string;
@@ -20,7 +20,7 @@ export const DirectoryListing = ({
20
20
  handleRenameFileClick: (fileId: string) => void;
21
21
  handleDeleteFileClick: (fileId: string, event: React.MouseEvent) => void;
22
22
  handleFileClick: (fileId: string) => void;
23
- openDirectories: { [path: string]: boolean };
23
+ isDirectoryOpen: (path: string) => boolean;
24
24
  toggleDirectory: (path: string) => void;
25
25
  }) => {
26
26
  const handleClick = useCallback(() => {
@@ -35,7 +35,8 @@ export const DirectoryListing = ({
35
35
  console.log('TODO handleRenameDirectoryClick');
36
36
  }, []);
37
37
 
38
- const isOpen = openDirectories[path];
38
+ const isOpen = useMemo(() => isDirectoryOpen(path), [isDirectoryOpen, path]);
39
+
39
40
  return (
40
41
  <>
41
42
  <Item
@@ -64,7 +65,7 @@ export const DirectoryListing = ({
64
65
  handleRenameFileClick={handleRenameFileClick}
65
66
  handleDeleteFileClick={handleDeleteFileClick}
66
67
  handleFileClick={handleFileClick}
67
- openDirectories={openDirectories}
68
+ isDirectoryOpen={isDirectoryOpen}
68
69
  toggleDirectory={toggleDirectory}
69
70
  />
70
71
  );
@@ -23,7 +23,7 @@ export const FileListing = ({
23
23
  (event) => {
24
24
  handleDeleteFileClick(fileId, event);
25
25
  },
26
- [fileId]
26
+ [fileId],
27
27
  );
28
28
 
29
29
  const handleRenameClick = useCallback(() => {
@@ -9,14 +9,14 @@ export const Listing = ({
9
9
  handleRenameFileClick,
10
10
  handleDeleteFileClick,
11
11
  handleFileClick,
12
- openDirectories,
12
+ isDirectoryOpen,
13
13
  toggleDirectory,
14
14
  }: {
15
15
  entity: FileTree | FileTreeFile;
16
16
  handleRenameFileClick: (fileId: FileId) => void;
17
17
  handleDeleteFileClick: (fileId: FileId, event: React.MouseEvent) => void;
18
18
  handleFileClick: (fileId: FileId) => void;
19
- openDirectories: { [path: string]: boolean };
19
+ isDirectoryOpen: (path: string) => boolean;
20
20
  toggleDirectory: (path: string) => void;
21
21
  }) => {
22
22
  const { name, file, fileId } = entity as FileTreeFile;
@@ -37,7 +37,7 @@ export const Listing = ({
37
37
  handleRenameFileClick={handleRenameFileClick}
38
38
  handleDeleteFileClick={handleDeleteFileClick}
39
39
  handleFileClick={handleFileClick}
40
- openDirectories={openDirectories}
40
+ isDirectoryOpen={isDirectoryOpen}
41
41
  toggleDirectory={toggleDirectory}
42
42
  />
43
43
  );
@@ -1,9 +1,8 @@
1
- import { useMemo } from 'react';
1
+ import { useCallback, useMemo } from 'react';
2
2
  import { getFileTree } from '../getFileTree';
3
3
  import { sortFileTree } from '../sortFileTree';
4
4
  import { disableSettings } from '../featureFlags';
5
5
  import { Listing } from './Listing';
6
- import { useOpenDirectories } from './useOpenDirectories';
7
6
 
8
7
  import './styles.scss';
9
8
  import { FileId, FileTree, FileTreeFile, Files } from '../../types';
@@ -15,7 +14,8 @@ export const Sidebar = ({
15
14
  handleDeleteFileClick,
16
15
  handleFileClick,
17
16
  setIsSettingsOpen,
18
- isSettingsOpen,
17
+ isDirectoryOpen,
18
+ toggleDirectory,
19
19
  }: {
20
20
  files: Files;
21
21
  createFile?: () => void;
@@ -23,14 +23,17 @@ export const Sidebar = ({
23
23
  handleDeleteFileClick?: (fileId: FileId, event: React.MouseEvent) => void;
24
24
  handleFileClick?: (fileId: FileId) => void;
25
25
  setIsSettingsOpen?: (isSettingsOpen: boolean) => void;
26
- isSettingsOpen?: boolean;
26
+ isDirectoryOpen?: (path: string) => boolean;
27
+ toggleDirectory?: (path: string) => void;
27
28
  }) => {
28
29
  const fileTree = useMemo(
29
30
  () => (files ? sortFileTree(getFileTree(files)) : null),
30
- [files]
31
+ [files],
31
32
  );
32
33
 
33
- const { openDirectories, toggleDirectory } = useOpenDirectories();
34
+ const handleSettingsClick = useCallback(() => {
35
+ setIsSettingsOpen(true);
36
+ }, []);
34
37
 
35
38
  return (
36
39
  <div className="vz-sidebar">
@@ -53,14 +56,15 @@ export const Sidebar = ({
53
56
  ? fileTree.children.map((entity) => {
54
57
  const { fileId } = entity as FileTreeFile;
55
58
  const { path } = entity as FileTree;
59
+ const key = fileId ? fileId : path;
56
60
  return (
57
61
  <Listing
58
62
  entity={entity}
59
- key={fileId || path}
63
+ key={key}
60
64
  handleRenameFileClick={handleRenameFileClick}
61
65
  handleDeleteFileClick={handleDeleteFileClick}
62
66
  handleFileClick={handleFileClick}
63
- openDirectories={openDirectories}
67
+ isDirectoryOpen={isDirectoryOpen}
64
68
  toggleDirectory={toggleDirectory}
65
69
  />
66
70
  );
@@ -68,10 +72,7 @@ export const Sidebar = ({
68
72
  : null}
69
73
  </div>
70
74
  {disableSettings ? null : (
71
- <div
72
- className="settings"
73
- onClick={() => setIsSettingsOpen(!isSettingsOpen)}
74
- >
75
+ <div className="settings" onClick={handleSettingsClick}>
75
76
  Settings
76
77
  </div>
77
78
  )}
@@ -0,0 +1,82 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { FileId, Files } from '../../types';
3
+ import './style.scss';
4
+
5
+ // Supports adding the file's containing folder to the tab name
6
+ const fileNameSplit = (fileName) => {
7
+ const split = fileName.split('/');
8
+ if (split.length === 1) return split[split.length - 1];
9
+ return split[split.length - 2] + '/' + split[split.length - 1];
10
+ };
11
+
12
+ const Tab = ({
13
+ fileId,
14
+ isActive,
15
+ setActiveFileId,
16
+ closeTab,
17
+ fileName,
18
+ }: {
19
+ fileId: FileId;
20
+ isActive: boolean;
21
+ setActiveFileId: (fileId: FileId) => void;
22
+ closeTab: (fileId: FileId) => void;
23
+ fileName: string;
24
+ }) => {
25
+ const handleCloseClick = useCallback(
26
+ (event: React.MouseEvent) => {
27
+ // Stop propagation so that the outer listener doesn't fire.
28
+ event.stopPropagation();
29
+
30
+ closeTab(fileId);
31
+ },
32
+ [closeTab, fileId],
33
+ );
34
+
35
+ const tabName = useMemo(() => fileNameSplit(fileName), [fileName]);
36
+
37
+ return (
38
+ <div
39
+ className={isActive ? 'tab active' : 'tab'}
40
+ onClick={() => {
41
+ setActiveFileId(fileId);
42
+ }}
43
+ >
44
+ {tabName}
45
+ <div
46
+ className={isActive ? 'bx bx-x tab-close' : ''}
47
+ onClick={handleCloseClick}
48
+ ></div>
49
+ </div>
50
+ );
51
+ };
52
+
53
+ // Displays the list of tabs above the code editor.
54
+ export const TabList = ({
55
+ files,
56
+ tabList,
57
+ activeFileId,
58
+ setActiveFileId,
59
+ closeTab,
60
+ }: {
61
+ files: Files;
62
+ tabList: FileId[];
63
+ activeFileId: FileId;
64
+ setActiveFileId: (fileId: FileId) => void;
65
+ closeTab: (fileId: FileId) => void;
66
+ }) => {
67
+ return (
68
+ <div className="vz-tab-list">
69
+ {files &&
70
+ tabList.map((fileId: FileId) => (
71
+ <Tab
72
+ key={fileId}
73
+ fileId={fileId}
74
+ isActive={fileId === activeFileId}
75
+ setActiveFileId={setActiveFileId}
76
+ closeTab={closeTab}
77
+ fileName={files[fileId].name}
78
+ />
79
+ ))}
80
+ </div>
81
+ );
82
+ };
@@ -0,0 +1,32 @@
1
+ .vz-tab-list {
2
+ display: flex;
3
+ background-color: rgb(37, 37, 38);
4
+
5
+ .tab {
6
+ background: rgb(45, 45, 45);
7
+ color: #fff;
8
+ font-family: var(--vzcode-font-family);
9
+ align-items: center;
10
+ display: flex;
11
+ padding-top: 2px;
12
+ padding-left: 10px;
13
+ padding-right: 10px;
14
+ border-top-left-radius: 10px;
15
+ border-top-right-radius: 10px;
16
+ margin-top: 5px;
17
+ margin-left: 2px;
18
+ margin-right: 2px;
19
+ cursor: pointer;
20
+
21
+ .tab-close {
22
+ margin-left: 5px;
23
+ }
24
+ .tab-close:hover {
25
+ border-radius: 30%;
26
+ background-color: rgb(37, 37, 38);
27
+ }
28
+ }
29
+ .tab.active {
30
+ background-color: rgb(41, 44, 52);
31
+ }
32
+ }
@@ -5,5 +5,5 @@ import App from './App';
5
5
  ReactDOM.createRoot(document.getElementById('root')).render(
6
6
  <React.StrictMode>
7
7
  <App />
8
- </React.StrictMode>
8
+ </React.StrictMode>,
9
9
  );
@@ -45,49 +45,5 @@ body,
45
45
  flex: 1;
46
46
  flex-direction: column;
47
47
  min-width: 0;
48
-
49
- .tab-list {
50
- display: flex;
51
- background-color: rgb(37, 37, 38);
52
-
53
- .tab {
54
- background: rgb(45, 45, 45);
55
- color: #fff;
56
- font-family: var(--vzcode-font-family);
57
- align-items: center;
58
- display: flex;
59
- padding-top: 2px;
60
- padding-left: 10px;
61
- padding-right: 10px;
62
- border-top-left-radius: 10px;
63
- border-top-right-radius: 10px;
64
- margin-top: 5px;
65
- margin-left: 2px;
66
- margin-right: 2px;
67
- cursor: pointer;
68
-
69
- .tab-close {
70
- margin-left: 5px;
71
- }
72
- .tab-close:hover {
73
- border-radius: 30%;
74
- background-color: rgb(37, 37, 38);
75
- }
76
- }
77
- .tab.active {
78
- background-color: rgb(41, 44, 52);
79
- }
80
- }
81
-
82
- .editor {
83
- flex: 1;
84
- display: flex;
85
- font-family: var(--vzcode-font-family);
86
- overflow-x: auto;
87
- }
88
48
  }
89
49
  }
90
-
91
- .themes {
92
- color: rgb(37, 37, 38);
93
- }
@@ -0,0 +1,31 @@
1
+ import { oneDark } from '@codemirror/theme-one-dark';
2
+ import { okaidia } from '@uiw/codemirror-theme-okaidia';
3
+ import { abcdef } from '@uiw/codemirror-theme-abcdef';
4
+ import { dracula } from '@uiw/codemirror-theme-dracula';
5
+ import { eclipse } from '@uiw/codemirror-theme-eclipse';
6
+ import { githubDark } from '@uiw/codemirror-theme-github';
7
+ import { material } from '@uiw/codemirror-theme-material';
8
+ import { nord } from '@uiw/codemirror-theme-nord';
9
+ import { xcodeLight } from '@uiw/codemirror-theme-xcode';
10
+
11
+ // CodeMirror themes
12
+ export const themes = [
13
+ { value: abcdef, label: 'abcdef' },
14
+ { value: dracula, label: 'darcula' },
15
+ { value: eclipse, label: 'eclipse' },
16
+ { value: material, label: 'material' },
17
+ { value: nord, label: 'nord' },
18
+ { value: oneDark, label: 'oneDark' },
19
+ { value: okaidia, label: 'okaidia' },
20
+ { value: githubDark, label: 'github' },
21
+ { value: xcodeLight, label: 'xcode' },
22
+ ];
23
+
24
+ // Map theme labels to theme values
25
+ export const themesByLabel = themes.reduce((acc, theme) => {
26
+ acc[theme.label] = theme.value;
27
+ return acc;
28
+ }, {});
29
+
30
+ // The default theme
31
+ export const defaultTheme = 'oneDark';
@@ -18,20 +18,26 @@ import { useState, useCallback } from 'react';
18
18
  // Inspired by
19
19
  // https://github.com/vizhub-core/vizhub/blob/main/vizhub-v2/packages/neoFrontend/src/pages/VizPage/Body/Editor/FilesSection/useOpenDirectories.js
20
20
  export const useOpenDirectories = () => {
21
- const [openDirectories, setOpenDirectories] = useState<{
22
- [path: string]: boolean;
23
- }>({});
21
+ // The set of open directories by path/
22
+ const [openDirectories, setOpenDirectories] = useState<Set<string>>(
23
+ new Set(),
24
+ );
25
+
26
+ // Whether a directory is open.
27
+ const isDirectoryOpen: (path: string) => boolean = useCallback(
28
+ (path) => openDirectories.has(path),
29
+ [openDirectories],
30
+ );
24
31
 
25
- // TODO consider useReducer to avoid event listener churn
32
+ // Toggle whether a directory is open.
26
33
  const toggleDirectory = useCallback(
27
34
  (path) => {
28
- setOpenDirectories(
29
- Object.assign({}, openDirectories, {
30
- [path]: !openDirectories[path],
31
- })
32
- );
35
+ const newOpenDirectories = new Set(openDirectories);
36
+ newOpenDirectories[isDirectoryOpen(path) ? 'delete' : 'add'](path);
37
+ setOpenDirectories(newOpenDirectories);
33
38
  },
34
- [openDirectories]
39
+ [openDirectories, isDirectoryOpen],
35
40
  );
36
- return { openDirectories, toggleDirectory };
41
+
42
+ return { isDirectoryOpen, toggleDirectory };
37
43
  };
@@ -0,0 +1,51 @@
1
+ import { useCallback } from 'react';
2
+ import { FileId } from '../types';
3
+
4
+ // Encapsulates the state and logic for managing the list of open tabs.
5
+ export const useTabsState = (
6
+ activeFileId: FileId,
7
+ setActiveFileId: (fileId: FileId | null) => void,
8
+ tabList: Array<FileId>,
9
+ setTabList: (tabList: Array<FileId>) => void,
10
+ ) => {
11
+ // Called when a tab is closed.
12
+ const closeTab = useCallback(
13
+ (fileIdToRemove: FileId) => {
14
+ const i = tabList.findIndex((fileId) => fileId === fileIdToRemove);
15
+
16
+ // Support calling `closeTab` on a fileId that is not open,
17
+ // such as when a file is deleted..
18
+ if (i !== -1) {
19
+ // Remove the tab from the tab list.
20
+ const newTabList = [...tabList.slice(0, i), ...tabList.slice(i + 1)];
21
+ setTabList(newTabList);
22
+
23
+ // If we are closing the active file,
24
+ if (activeFileId === fileIdToRemove) {
25
+ // set the new active file to the next tab over,
26
+ if (newTabList.length > 0) {
27
+ setActiveFileId(i === 0 ? newTabList[i] : newTabList[i - 1]);
28
+ } else {
29
+ // or clear out the active file
30
+ // if we've closed the last tab.
31
+ setActiveFileId(null);
32
+ }
33
+ }
34
+ }
35
+ },
36
+ [tabList, activeFileId],
37
+ );
38
+
39
+ // Called when a tab is opened.
40
+ const openTab = useCallback(
41
+ (fileId: FileId) => {
42
+ setActiveFileId(fileId);
43
+ if (!tabList.includes(fileId)) {
44
+ setTabList([...tabList, fileId]);
45
+ }
46
+ },
47
+ [tabList],
48
+ );
49
+
50
+ return { tabList, closeTab, openTab };
51
+ };
package/src/types.ts CHANGED
@@ -55,3 +55,15 @@ export interface FileTreeFile {
55
55
  file: File;
56
56
  fileId: FileId;
57
57
  }
58
+
59
+ // `ShareDBDoc`
60
+ // A ShareDB Document.
61
+ // TODO better types? Integrate with upstream how?
62
+ // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/sharedb/lib/sharedb.d.ts#L110
63
+ export type ShareDBDoc<T> = {
64
+ data: T;
65
+ ingestSnapshot: (snapshot: any, callback) => void;
66
+ subscribe: (callback) => void;
67
+ on: (event: string, callback: () => void) => void;
68
+ off: (event: string, callback: () => void) => void;
69
+ };