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.
- package/dist/assets/index-5f9f376e.css +1 -0
- package/dist/assets/index-70b6d947.js +111 -0
- package/dist/index.html +2 -6
- package/package.json +7 -7
- package/src/client/App.tsx +110 -247
- package/src/client/CodeEditor/getOrCreateEditor.ts +6 -1
- package/src/client/CodeEditor/index.tsx +94 -16
- package/src/client/CodeEditor/{json1PresenceBroadcast.js → json1PresenceBroadcast.ts} +9 -3
- package/src/client/CodeEditor/{json1PresenceDisplay.js → json1PresenceDisplay.ts} +9 -48
- package/src/client/CodeEditor/style.scss +5 -1
- package/src/client/CodeErrorOverlay/index.tsx +27 -0
- package/src/client/CodeErrorOverlay/style.scss +30 -0
- package/src/client/PresenceNotifications/PresenceNotifications.test.ts +134 -0
- package/src/client/PresenceNotifications/index.tsx +119 -106
- package/src/client/SplitPaneResizeContext.tsx +12 -7
- package/src/client/TabList/Tab.tsx +70 -0
- package/src/client/TabList/index.tsx +28 -69
- package/src/client/TabList/style.scss +26 -9
- package/src/client/{Settings.tsx → VZSettings.tsx} +34 -21
- package/src/client/{Sidebar/ConfirmationBox.tsx → VZSidebar/DeleteConfirmationModal.tsx} +17 -14
- package/src/client/{Sidebar → VZSidebar}/DirectoryListing.tsx +27 -26
- package/src/client/{Sidebar → VZSidebar}/FileListing.tsx +19 -19
- package/src/client/VZSidebar/Item.tsx +179 -0
- package/src/client/{Sidebar → VZSidebar}/Listing.tsx +20 -14
- package/src/client/{Sidebar → VZSidebar}/index.tsx +48 -30
- package/src/client/{Sidebar → VZSidebar}/styles.scss +17 -12
- package/src/client/bootstrap.ts +3 -0
- package/src/client/style.scss +8 -2
- package/src/client/useActions.ts +93 -0
- package/src/client/useEditorCache.ts +2 -1
- package/src/client/useFileCRUD.ts +103 -0
- package/src/client/useOpenDirectories.ts +25 -7
- package/src/client/useSubmitOperation.ts +25 -0
- package/src/client/usernameLocalStorage.ts +43 -0
- package/src/client/vzReducer/closeTabReducer.test.ts +177 -0
- package/src/client/vzReducer/closeTabsReducer.ts +68 -0
- package/src/client/vzReducer/createInitialState.ts +19 -0
- package/src/client/vzReducer/editorNoLongerWantsFocusReducer.ts +9 -0
- package/src/client/vzReducer/index.ts +107 -0
- package/src/client/vzReducer/openTabReducer.test.ts +110 -0
- package/src/client/vzReducer/openTabReducer.ts +57 -0
- package/src/client/vzReducer/setActiveFileIdReducer.ts +9 -0
- package/src/client/vzReducer/setIsSettingsOpenReducer.ts +9 -0
- package/src/client/vzReducer/setThemeReducer.ts +9 -0
- package/src/client/vzReducer/setUsernameReducer.ts +9 -0
- package/src/index.ts +40 -0
- package/src/server/index.js +13 -2
- package/src/types.ts +12 -5
- package/dist/assets/index-8628ebab.js +0 -111
- package/dist/assets/index-cf19de3b.css +0 -1
- package/src/client/PrettierErrorOverlay/index.tsx +0 -9
- package/src/client/PrettierErrorOverlay/style.scss +0 -9
- package/src/client/Sidebar/Item.tsx +0 -130
- package/src/client/reducer.ts +0 -119
- /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
|
+
};
|
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';
|
package/src/server/index.js
CHANGED
|
@@ -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 =
|
|
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.
|
|
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?:
|
|
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;
|