vzcode 0.25.0 → 0.27.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.
Files changed (55) hide show
  1. package/dist/assets/index-5f9f376e.css +1 -0
  2. package/dist/assets/index-70b6d947.js +111 -0
  3. package/dist/index.html +2 -6
  4. package/package.json +7 -7
  5. package/src/client/App.tsx +110 -247
  6. package/src/client/CodeEditor/getOrCreateEditor.ts +6 -1
  7. package/src/client/CodeEditor/index.tsx +94 -16
  8. package/src/client/CodeEditor/{json1PresenceBroadcast.js → json1PresenceBroadcast.ts} +9 -3
  9. package/src/client/CodeEditor/{json1PresenceDisplay.js → json1PresenceDisplay.ts} +9 -48
  10. package/src/client/CodeEditor/style.scss +5 -1
  11. package/src/client/CodeErrorOverlay/index.tsx +27 -0
  12. package/src/client/CodeErrorOverlay/style.scss +30 -0
  13. package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
  14. package/src/client/PresenceNotifications/index.tsx +119 -106
  15. package/src/client/SplitPaneResizeContext.tsx +12 -7
  16. package/src/client/TabList/Tab.tsx +70 -0
  17. package/src/client/TabList/index.tsx +28 -69
  18. package/src/client/TabList/style.scss +26 -9
  19. package/src/client/{Settings.tsx → VZSettings.tsx} +34 -21
  20. package/src/client/{Sidebar/ConfirmationBox.tsx → VZSidebar/DeleteConfirmationModal.tsx} +17 -14
  21. package/src/client/{Sidebar → VZSidebar}/DirectoryListing.tsx +27 -26
  22. package/src/client/{Sidebar → VZSidebar}/FileListing.tsx +19 -19
  23. package/src/client/VZSidebar/Item.tsx +179 -0
  24. package/src/client/{Sidebar → VZSidebar}/Listing.tsx +20 -14
  25. package/src/client/{Sidebar → VZSidebar}/index.tsx +48 -30
  26. package/src/client/{Sidebar → VZSidebar}/styles.scss +17 -12
  27. package/src/client/bootstrap.ts +3 -0
  28. package/src/client/style.scss +8 -2
  29. package/src/client/useActions.ts +93 -0
  30. package/src/client/useEditorCache.ts +2 -1
  31. package/src/client/useFileCRUD.ts +103 -0
  32. package/src/client/useOpenDirectories.ts +25 -7
  33. package/src/client/useSubmitOperation.ts +25 -0
  34. package/src/client/usernameLocalStorage.ts +43 -0
  35. package/src/client/vzReducer/closeTabReducer.test.ts +177 -0
  36. package/src/client/vzReducer/closeTabsReducer.ts +68 -0
  37. package/src/client/vzReducer/createInitialState.ts +19 -0
  38. package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
  39. package/src/client/vzReducer/index.ts +107 -0
  40. package/src/client/vzReducer/openTabReducer.test.ts +110 -0
  41. package/src/client/vzReducer/openTabReducer.ts +57 -0
  42. package/src/client/vzReducer/setActiveFileIdReducer.ts +9 -0
  43. package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
  44. package/src/client/vzReducer/setThemeReducer.ts +9 -0
  45. package/src/client/vzReducer/setUsernameReducer.ts +9 -0
  46. package/src/index.ts +40 -0
  47. package/src/server/index.js +13 -2
  48. package/src/types.ts +12 -5
  49. package/dist/assets/index-8628ebab.js +0 -111
  50. package/dist/assets/index-cf19de3b.css +0 -1
  51. package/src/client/PrettierErrorOverlay/index.tsx +0 -9
  52. package/src/client/PrettierErrorOverlay/style.scss +0 -9
  53. package/src/client/Sidebar/Item.tsx +0 -130
  54. package/src/client/reducer.ts +0 -119
  55. /package/src/client/{Sidebar → VZSidebar}/DirectoryArrowSVG.jsx +0 -0
@@ -0,0 +1,107 @@
1
+ import { FileId, Username } from '../../types';
2
+ import { ThemeLabel } from '../themes';
3
+ import { closeTabsReducer } from './closeTabsReducer';
4
+ import { openTabReducer } from './openTabReducer';
5
+ import { setActiveFileIdReducer } from './setActiveFileIdReducer';
6
+ import { setIsSettingsOpenReducer } from './setIsSettingsOpenReducer';
7
+ import { setThemeReducer } from './setThemeReducer';
8
+ import { editorNoLongerWantsFocusReducer } from './editorNoLongerWantsFocusReducer';
9
+ import { setUsernameReducer } from './setUsernameReducer';
10
+
11
+ export { createInitialState } from './createInitialState';
12
+
13
+ // The shape of the state managed by the reducer.
14
+ export type VZState = {
15
+ // The list of open tabs.
16
+ tabList: Array<TabState>;
17
+
18
+ // The ID of the file that is currently active.
19
+ // Invariant: `activeFileId` is always in `tabList`.
20
+ activeFileId: FileId | null;
21
+
22
+ // The theme that is currently active.
23
+ theme: ThemeLabel;
24
+
25
+ // True to show the settings modal.
26
+ isSettingsOpen: boolean;
27
+
28
+ // True if the editor should focus on the next render.
29
+ editorWantsFocus: boolean;
30
+
31
+ // The username of the current user.
32
+ username: Username;
33
+ };
34
+
35
+ // The shape of the actions that can be dispatched to the reducer.
36
+ export type VZAction =
37
+ // `set_active_file_id`
38
+ // * Sets the active file ID.
39
+ | { type: 'set_active_file_id'; activeFileId: FileId }
40
+
41
+ // `open_tab`
42
+ // * Opens a tab.
43
+ // * Also serves to change an already open transient tab to persistent.
44
+ | {
45
+ type: 'open_tab';
46
+ fileId: FileId;
47
+ isTransient: boolean;
48
+ }
49
+
50
+ // `close_tabs`
51
+ // * Closes a set of tabs.
52
+ | { type: 'close_tabs'; fileIdsToClose: Array<FileId> }
53
+
54
+ // `set_theme`
55
+ // * Sets the theme.
56
+ | { type: 'set_theme'; themeLabel: ThemeLabel }
57
+
58
+ // `set_is_settings_open`
59
+ // * Sets whether the settings modal is open.
60
+ | { type: 'set_is_settings_open'; value: boolean }
61
+
62
+ // `editor_no_longer_wants_focus`
63
+ // * Sets `editorWantsFocus` to `false`.
64
+ | { type: 'editor_no_longer_wants_focus' }
65
+
66
+ // `set_username`
67
+ // * Sets the username.
68
+ | { type: 'set_username'; username: Username };
69
+
70
+ // Representation of an open tab.
71
+ export type TabState = {
72
+ // `fileId`
73
+ // The ID of the file that the tab represents.
74
+ fileId: FileId;
75
+
76
+ // `isTransient`
77
+ // Represents whether the tab is temporary or persistent.
78
+ // * `true` if the tab is temporary, meaning its text
79
+ // appears as italic, and it will be automatically
80
+ // closed when the user switches to another file.
81
+ // If `true` and the tab is opened, the editor will not focus.
82
+ // * `false` if the tab is persistent, meaning its text
83
+ // appears as normal, and it will not be automatically
84
+ // closed when the user switches to another file.
85
+ // If `false` and the tab is opened, the editor will focus.
86
+ isTransient: boolean;
87
+ };
88
+
89
+ const reducers = [
90
+ setActiveFileIdReducer,
91
+ openTabReducer,
92
+ closeTabsReducer,
93
+ setThemeReducer,
94
+ setIsSettingsOpenReducer,
95
+ editorNoLongerWantsFocusReducer,
96
+ setUsernameReducer,
97
+ ];
98
+
99
+ export const vzReducer = (
100
+ state: VZState,
101
+ action: VZAction,
102
+ ): VZState =>
103
+ reducers.reduce(
104
+ (currentState, currentReducer) =>
105
+ currentReducer(currentState, action),
106
+ state,
107
+ );
@@ -0,0 +1,110 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { openTabReducer } from './openTabReducer';
3
+ import { VZAction, VZState, createInitialState } from '.';
4
+ import { defaultTheme } from '../themes';
5
+
6
+ const initialState: VZState = createInitialState({
7
+ defaultTheme,
8
+ });
9
+
10
+ describe('openTabReducer', () => {
11
+ it('Opens a new tab', () => {
12
+ const action: VZAction = {
13
+ type: 'open_tab',
14
+ fileId: 'file1',
15
+ isTransient: false,
16
+ };
17
+ const newState = openTabReducer(initialState, action);
18
+
19
+ expect(newState.tabList.length).toBe(1);
20
+ expect(newState.tabList[0].fileId).toBe('file1');
21
+ expect(newState.tabList[0].isTransient).toBe(false);
22
+ expect(newState.activeFileId).toBe('file1');
23
+ });
24
+
25
+ it('Replaces a transient tab', () => {
26
+ const stateWithTransientTab = {
27
+ ...initialState,
28
+ tabList: [{ fileId: 'file2', isTransient: true }],
29
+ };
30
+ const action: VZAction = {
31
+ type: 'open_tab',
32
+ fileId: 'file1',
33
+ isTransient: true,
34
+ };
35
+ const newState = openTabReducer(
36
+ stateWithTransientTab,
37
+ action,
38
+ );
39
+
40
+ expect(newState.tabList.length).toBe(1);
41
+ expect(newState.tabList[0].fileId).toBe('file1');
42
+ expect(newState.tabList[0].isTransient).toBe(true);
43
+ expect(newState.activeFileId).toBe('file1');
44
+ });
45
+
46
+ it('Does not modify a non-transient tab', () => {
47
+ const stateWithNonTransientTab = {
48
+ ...initialState,
49
+ tabList: [{ fileId: 'file1', isTransient: false }],
50
+ };
51
+ const action: VZAction = {
52
+ type: 'open_tab',
53
+ fileId: 'file1',
54
+ isTransient: true,
55
+ };
56
+ const newState = openTabReducer(
57
+ stateWithNonTransientTab,
58
+ action,
59
+ );
60
+
61
+ expect(newState.tabList.length).toBe(1);
62
+ expect(newState.tabList[0].fileId).toBe('file1');
63
+ expect(newState.tabList[0].isTransient).toBe(false); // should remain non-transient
64
+ expect(newState.activeFileId).toBe('file1');
65
+ });
66
+
67
+ it('Activates an already open tab', () => {
68
+ const stateWithMultipleTabs = {
69
+ ...initialState,
70
+ tabList: [
71
+ { fileId: 'file1', isTransient: false },
72
+ { fileId: 'file2', isTransient: false },
73
+ ],
74
+ };
75
+ const action: VZAction = {
76
+ type: 'open_tab',
77
+ fileId: 'file1',
78
+ isTransient: false,
79
+ };
80
+ const newState = openTabReducer(
81
+ stateWithMultipleTabs,
82
+ action,
83
+ );
84
+
85
+ expect(newState.tabList.length).toBe(2);
86
+ expect(newState.activeFileId).toBe('file1');
87
+ });
88
+
89
+ it('Sets editorWantsFocus to true for non-transient tabs', () => {
90
+ const action: VZAction = {
91
+ type: 'open_tab',
92
+ fileId: 'file3',
93
+ isTransient: false, // This tab is persistent
94
+ };
95
+ const newState = openTabReducer(initialState, action);
96
+
97
+ expect(newState.editorWantsFocus).toBe(true);
98
+ });
99
+
100
+ it('Sets editorWantsFocus to false for transient tabs', () => {
101
+ const action: VZAction = {
102
+ type: 'open_tab',
103
+ fileId: 'file4',
104
+ isTransient: true, // This tab is transient
105
+ };
106
+ const newState = openTabReducer(initialState, action);
107
+
108
+ expect(newState.editorWantsFocus).toBe(false);
109
+ });
110
+ });
@@ -0,0 +1,57 @@
1
+ import { TabState, VZAction, VZState } from '.';
2
+
3
+ export const openTabReducer = (
4
+ state: VZState,
5
+ action: VZAction,
6
+ ): VZState => {
7
+ if (action.type !== 'open_tab') return state;
8
+
9
+ // Check if the tab is already open and if it's transient.
10
+ const existingTabIndex = state.tabList.findIndex(
11
+ (tab) => tab.fileId === action.fileId,
12
+ );
13
+ const tabExists = existingTabIndex !== -1;
14
+ const existingTabIsTransient =
15
+ tabExists &&
16
+ state.tabList[existingTabIndex].isTransient;
17
+
18
+ // Find if there's any other transient tab.
19
+ const transientTabIndex = state.tabList.findIndex(
20
+ (tab) =>
21
+ tab.isTransient && tab.fileId !== action.fileId,
22
+ );
23
+
24
+ // The new tab state.
25
+ const newTabState: TabState = {
26
+ fileId: action.fileId,
27
+ isTransient: action.isTransient,
28
+ };
29
+
30
+ let newTabList: Array<TabState>;
31
+
32
+ if (tabExists) {
33
+ if (existingTabIsTransient) {
34
+ newTabList = state.tabList.map((tabState) =>
35
+ tabState.fileId === action.fileId
36
+ ? newTabState
37
+ : tabState,
38
+ );
39
+ } else {
40
+ newTabList = [...state.tabList];
41
+ }
42
+ } else if (transientTabIndex !== -1) {
43
+ newTabList = [...state.tabList];
44
+ newTabList[transientTabIndex] = newTabState;
45
+ } else {
46
+ newTabList = [...state.tabList, newTabState];
47
+ }
48
+
49
+ return {
50
+ ...state,
51
+ activeFileId: action.fileId,
52
+ tabList: newTabList,
53
+
54
+ // If the tab is persistent, the editor should focus on the next render.
55
+ editorWantsFocus: !action.isTransient,
56
+ };
57
+ };
@@ -0,0 +1,9 @@
1
+ import { VZAction, VZState } from '.';
2
+
3
+ export const setActiveFileIdReducer = (
4
+ state: VZState,
5
+ action: VZAction,
6
+ ): VZState =>
7
+ action.type === 'set_active_file_id'
8
+ ? { ...state, activeFileId: action.activeFileId }
9
+ : state;
@@ -0,0 +1,9 @@
1
+ import { VZAction, VZState } from '.';
2
+
3
+ export const setIsSettingsOpenReducer = (
4
+ state: VZState,
5
+ action: VZAction,
6
+ ): VZState =>
7
+ action.type === 'set_is_settings_open'
8
+ ? { ...state, isSettingsOpen: action.value }
9
+ : state;
@@ -0,0 +1,9 @@
1
+ import { VZAction, VZState } from '.';
2
+
3
+ export const setThemeReducer = (
4
+ state: VZState,
5
+ action: VZAction,
6
+ ): VZState =>
7
+ action.type === 'set_theme'
8
+ ? { ...state, theme: action.themeLabel }
9
+ : state;
@@ -0,0 +1,9 @@
1
+ import { VZAction, VZState } from '.';
2
+
3
+ export const setUsernameReducer = (
4
+ state: VZState,
5
+ action: VZAction,
6
+ ): VZState =>
7
+ action.type === 'set_username'
8
+ ? { ...state, username: action.username }
9
+ : state;
package/src/index.ts ADDED
@@ -0,0 +1,40 @@
1
+ export type {
2
+ FileId,
3
+ Files,
4
+ File,
5
+ FileTree,
6
+ FileTreeFile,
7
+ ShareDBDoc,
8
+ VZCodeContent,
9
+ } from './types';
10
+ export type { EditorCache } from './client/useEditorCache';
11
+ export type {
12
+ ThemeLabel,
13
+ ThemeOption,
14
+ } from './client/themes';
15
+
16
+ export { VZSidebar } from './client/VZSidebar';
17
+ export { VZSettings } from './client/VZSettings';
18
+ export {
19
+ SplitPaneResizeProvider,
20
+ SplitPaneResizeContext,
21
+ } from './client/SplitPaneResizeContext';
22
+ export { Resizer } from './client/Resizer';
23
+ export { TabList } from './client/TabList';
24
+ export { CodeEditor } from './client/CodeEditor';
25
+ export { usePrettier } from './client/usePrettier';
26
+ export { CodeErrorOverlay } from './client/CodeErrorOverlay';
27
+ export { PresenceNotifications } from './client/PresenceNotifications';
28
+ export { useFileCRUD } from './client/useFileCRUD';
29
+ export { useSubmitOperation } from './client/useSubmitOperation';
30
+ export {
31
+ vzReducer,
32
+ createInitialState,
33
+ } from './client/vzReducer';
34
+ export { useActions } from './client/useActions';
35
+ export { useOpenDirectories } from './client/useOpenDirectories';
36
+ export {
37
+ defaultTheme,
38
+ useDynamicTheme,
39
+ } from './client/themes';
40
+ export { useEditorCache } from './client/useEditorCache';
@@ -22,8 +22,19 @@ const autoSaveDebounceTimeMS = 800;
22
22
  // when the user is interacting with the widgets in the editor.
23
23
  const throttleTimeMS = 100;
24
24
 
25
+ // Helper function to get the port from command line arguments or use default.
26
+ function getPortFromArgs(defaultPort = 3030) {
27
+ const portArg = process.argv.find((arg) =>
28
+ arg.startsWith('--port='),
29
+ );
30
+ if (portArg) {
31
+ return parseInt(portArg.split('=')[1], 10);
32
+ }
33
+ return defaultPort;
34
+ }
35
+
25
36
  // Server port.
26
- const port = 3030;
37
+ const port = getPortFromArgs();
27
38
 
28
39
  const initialDocument = computeInitialDocument({
29
40
  // Use the current working directory to look for files.
@@ -109,7 +120,7 @@ const save = () => {
109
120
 
110
121
  // Handle renaming files.
111
122
  if (previous.name !== current.name) {
112
- fs.renamesync(previous.name, current.name);
123
+ fs.renameSync(previous.name, current.name);
113
124
  }
114
125
  }
115
126
 
package/src/types.ts CHANGED
@@ -30,6 +30,11 @@ export interface File {
30
30
  text: string;
31
31
  }
32
32
 
33
+ // FileTreePath
34
+ // * The path to the directory, e.g. "src/components", OR
35
+ // * The path to the file e.g. "src/components/HelloWorld.js".
36
+ export type FileTreePath = string;
37
+
33
38
  // FileTree
34
39
  // * A tree of files.
35
40
  // * Each node in the tree is either a directory or a file.
@@ -38,13 +43,9 @@ export interface File {
38
43
  // * Each file has a `fileId` string.
39
44
  // * Each directory has a `name` string.
40
45
  // * Each directory has a `path` string.
41
- // * The `path` string is the path to the directory.
42
- // e.g. "src/components".
43
- // * The `path` string is the path to the file.
44
- // e.g. "src/components/HelloWorld.js".
45
46
  export interface FileTree {
46
47
  name: string;
47
- path?: string;
48
+ path?: FileTreePath;
48
49
  children?: Array<FileTree | FileTreeFile>;
49
50
  }
50
51
 
@@ -93,3 +94,9 @@ export type VZCodeContent = {
93
94
  // * Hot reloading is debounced when this is `false`.
94
95
  isInteracting?: boolean;
95
96
  };
97
+
98
+ // An id used for presence.
99
+ export type PresenceId = string;
100
+
101
+ // A user name for display in presence.
102
+ export type Username = string;