vzcode 1.15.0 → 1.17.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-BQgRA0SD.js +181 -0
- package/dist/assets/index-CSSpCKM0.js +447 -0
- package/dist/assets/index-zzK6rRiK.css +1 -0
- package/dist/assets/worker-8af7bTHW.js +293 -0
- package/dist/index.html +2 -2
- package/package.json +40 -34
- package/src/client/AIAssist/AIAssistWidget/index.tsx +10 -6
- package/src/client/App/index.tsx +52 -20
- package/src/client/App/usePending.ts +50 -0
- package/src/client/App/useShareDB.ts +40 -1
- package/src/client/CodeEditor/Copilot/index.ts +28 -0
- package/src/client/CodeEditor/InteractiveWidgets/colorPicker.ts +129 -33
- package/src/client/CodeEditor/InteractiveWidgets/colorsInTextPlugin.ts +36 -1
- package/src/client/CodeEditor/getOrCreateEditor.ts +43 -6
- package/src/client/CodeEditor/index.tsx +7 -4
- package/src/client/CodeEditor/json1PresenceDisplay.ts +5 -5
- package/src/client/CodeEditor/rainbowBrackets.ts +14 -2
- package/src/client/CodeEditor/style.scss +106 -1
- package/src/client/Icons/MicSVG.tsx +25 -0
- package/src/client/RunCodeWidget/index.tsx +20 -3
- package/src/client/TabList/index.tsx +8 -11
- package/src/client/VZCodeContext.tsx +66 -24
- package/src/client/VZKeyboardShortcutsDoc.tsx +8 -8
- package/src/client/VZLeft.tsx +4 -2
- package/src/client/VZMiddle.tsx +86 -27
- package/src/client/VZResizer/index.tsx +3 -3
- package/src/client/VZSettings.tsx +54 -3
- package/src/client/VZSidebar/FileListing.tsx +20 -6
- package/src/client/VZSidebar/Listing.tsx +2 -2
- package/src/client/VZSidebar/VoiceChatModal.tsx +121 -0
- package/src/client/VZSidebar/index.tsx +80 -15
- package/src/client/VZSidebar/styles.scss +6 -2
- package/src/client/presenceColor.ts +8 -0
- package/src/client/useTypeScript/worker/constants.ts +6 -0
- package/src/server/generateAIResponse.js +79 -13
- package/src/server/handleAICopilot.js +77 -0
- package/src/server/index.js +23 -8
- package/src/server/livekit.js +24 -0
- package/src/server/setupEnv.js +1 -1
- package/src/types.ts +1 -0
- package/dist/assets/index-Bm65AzMP.js +0 -425
- package/dist/assets/index-DbFdL1A1.js +0 -159
- package/dist/assets/index-Yz-ZHc0O.css +0 -1
- package/dist/assets/worker-CJWrLBN2.js +0 -286
package/dist/index.html
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
|
|
21
21
|
rel="stylesheet"
|
|
22
22
|
/>
|
|
23
|
-
<script type="module" crossorigin src="/assets/index-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
23
|
+
<script type="module" crossorigin src="/assets/index-BQgRA0SD.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-zzK6rRiK.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vzcode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "Multiplayer code editor system",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -103,68 +103,74 @@
|
|
|
103
103
|
},
|
|
104
104
|
"homepage": "https://github.com/vizhub-core/vzcode#readme",
|
|
105
105
|
"dependencies": {
|
|
106
|
-
"@codemirror/autocomplete": "^6.
|
|
107
|
-
"@codemirror/lang-css": "^6.
|
|
106
|
+
"@codemirror/autocomplete": "^6.18.3",
|
|
107
|
+
"@codemirror/lang-css": "^6.3.1",
|
|
108
108
|
"@codemirror/lang-html": "^6.4.9",
|
|
109
109
|
"@codemirror/lang-javascript": "^6.2.2",
|
|
110
110
|
"@codemirror/lang-json": "^6.0.1",
|
|
111
|
-
"@codemirror/lang-markdown": "^6.
|
|
111
|
+
"@codemirror/lang-markdown": "^6.3.1",
|
|
112
112
|
"@codemirror/state": "^6.4.1",
|
|
113
113
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
114
|
-
"@codemirror/view": "^6.
|
|
115
|
-
"@lezer/highlight": "^1.2.
|
|
114
|
+
"@codemirror/view": "^6.35.0",
|
|
115
|
+
"@lezer/highlight": "^1.2.1",
|
|
116
|
+
"@livekit/components-react": "^2.6.9",
|
|
117
|
+
"@livekit/components-styles": "^1.1.4",
|
|
116
118
|
"@replit/codemirror-indentation-markers": "^6.5.3",
|
|
117
119
|
"@replit/codemirror-interact": "^6.3.1",
|
|
118
120
|
"@replit/codemirror-lang-svelte": "^6.0.0",
|
|
119
121
|
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
|
120
122
|
"@teamwork/websocket-json-stream": "^2.0.0",
|
|
121
123
|
"@typescript/vfs": "^1.6.0",
|
|
122
|
-
"@uiw/codemirror-theme-abcdef": "^4.23.
|
|
123
|
-
"@uiw/codemirror-theme-dracula": "^4.23.
|
|
124
|
-
"@uiw/codemirror-theme-eclipse": "^4.23.
|
|
125
|
-
"@uiw/codemirror-theme-github": "^4.23.
|
|
126
|
-
"@uiw/codemirror-theme-material": "^4.23.
|
|
127
|
-
"@uiw/codemirror-theme-nord": "^4.23.
|
|
128
|
-
"@uiw/codemirror-theme-okaidia": "^4.23.
|
|
129
|
-
"@uiw/codemirror-theme-xcode": "^4.23.
|
|
130
|
-
"@uiw/codemirror-themes": "^4.23.
|
|
131
|
-
"body-parser": "^1.20.
|
|
124
|
+
"@uiw/codemirror-theme-abcdef": "^4.23.6",
|
|
125
|
+
"@uiw/codemirror-theme-dracula": "^4.23.6",
|
|
126
|
+
"@uiw/codemirror-theme-eclipse": "^4.23.6",
|
|
127
|
+
"@uiw/codemirror-theme-github": "^4.23.6",
|
|
128
|
+
"@uiw/codemirror-theme-material": "^4.23.6",
|
|
129
|
+
"@uiw/codemirror-theme-nord": "^4.23.6",
|
|
130
|
+
"@uiw/codemirror-theme-okaidia": "^4.23.6",
|
|
131
|
+
"@uiw/codemirror-theme-xcode": "^4.23.6",
|
|
132
|
+
"@uiw/codemirror-themes": "^4.23.6",
|
|
133
|
+
"body-parser": "^1.20.3",
|
|
132
134
|
"codemirror": "^6.0.1",
|
|
135
|
+
"codemirror-copilot": "^0.0.7",
|
|
133
136
|
"codemirror-ot": "^4.4.0",
|
|
134
137
|
"color-hash": "^2.0.2",
|
|
135
138
|
"d3-array": "^3.2.4",
|
|
136
139
|
"diff-match-patch": "^1.0.5",
|
|
137
140
|
"dotenv": "^16.4.5",
|
|
138
|
-
"express": "^4.
|
|
139
|
-
"ignore": "^
|
|
141
|
+
"express": "^4.21.1",
|
|
142
|
+
"ignore": "^6.0.2",
|
|
140
143
|
"json0-ot-diff": "^1.1.2",
|
|
144
|
+
"livekit-client": "^2.7.1",
|
|
145
|
+
"livekit-server-sdk": "^2.9.2",
|
|
141
146
|
"ngrok": "^4.3.3",
|
|
142
147
|
"open": "^10.1.0",
|
|
143
|
-
"openai": "^4.
|
|
144
|
-
"prettier-plugin-svelte": "^3.2
|
|
148
|
+
"openai": "^4.73.1",
|
|
149
|
+
"prettier-plugin-svelte": "^3.3.2",
|
|
145
150
|
"react": "^18.3.1",
|
|
146
|
-
"react-bootstrap": "^2.10.
|
|
151
|
+
"react-bootstrap": "^2.10.6",
|
|
147
152
|
"react-dom": "^18.3.1",
|
|
148
|
-
"react-router-dom": "^
|
|
149
|
-
"sharedb": "^5.
|
|
153
|
+
"react-router-dom": "^7.0.1",
|
|
154
|
+
"sharedb": "^5.1.1",
|
|
150
155
|
"sharedb-client-browser": "^5.0.3",
|
|
156
|
+
"uuid": "^11.0.3",
|
|
151
157
|
"vizhub-ui": "^3.23.0",
|
|
152
158
|
"ws": "^8.18.0"
|
|
153
159
|
},
|
|
154
160
|
"devDependencies": {
|
|
155
|
-
"@types/react": "^18.3.
|
|
156
|
-
"@types/react-dom": "^18.3.
|
|
157
|
-
"@vitejs/plugin-react": "^4.3.
|
|
158
|
-
"concurrently": "^
|
|
161
|
+
"@types/react": "^18.3.12",
|
|
162
|
+
"@types/react-dom": "^18.3.1",
|
|
163
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
164
|
+
"concurrently": "^9.1.0",
|
|
159
165
|
"cross-env": "^7.0.3",
|
|
160
|
-
"prettier": "^3.
|
|
161
|
-
"sass": "^1.
|
|
162
|
-
"typescript": "^5.
|
|
163
|
-
"vite": "^
|
|
164
|
-
"vitest": "^2.
|
|
166
|
+
"prettier": "^3.4.1",
|
|
167
|
+
"sass": "^1.81.0",
|
|
168
|
+
"typescript": "^5.7.2",
|
|
169
|
+
"vite": "^6.0.2",
|
|
170
|
+
"vitest": "^2.1.8"
|
|
165
171
|
},
|
|
166
172
|
"optionalDependencies": {
|
|
167
|
-
"@rollup/rollup-darwin-arm64": "^4.
|
|
168
|
-
"@rollup/rollup-win32-x64-msvc": "^4.
|
|
173
|
+
"@rollup/rollup-darwin-arm64": "^4.28.0",
|
|
174
|
+
"@rollup/rollup-win32-x64-msvc": "^4.28.0"
|
|
169
175
|
}
|
|
170
176
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useContext, useState } from 'react';
|
|
2
2
|
import { OverlayTrigger, Tooltip } from '../../bootstrap';
|
|
3
|
+
import { FileId, PaneId, TabState } from '../../../types';
|
|
3
4
|
import { VZCodeContext } from '../../VZCodeContext';
|
|
4
5
|
import {
|
|
5
6
|
RequestId,
|
|
@@ -7,8 +8,8 @@ import {
|
|
|
7
8
|
} from '../../generateRequestId';
|
|
8
9
|
import { SparklesSVG } from '../../Icons';
|
|
9
10
|
import { startAIAssist } from '../startAIAssist';
|
|
10
|
-
import { Spinner } from '../Spinner';
|
|
11
11
|
import { editorCacheKey } from '../../useEditorCache';
|
|
12
|
+
import { Spinner } from '../Spinner';
|
|
12
13
|
import './style.scss';
|
|
13
14
|
|
|
14
15
|
const enableStopGeneration = false;
|
|
@@ -18,17 +19,20 @@ export const AIAssistWidget = ({
|
|
|
18
19
|
aiAssistOptions,
|
|
19
20
|
aiAssistTooltipText = 'Start AI Assist',
|
|
20
21
|
aiAssistClickOverride,
|
|
22
|
+
activeFileId,
|
|
23
|
+
tabList,
|
|
24
|
+
paneId,
|
|
21
25
|
}: {
|
|
22
26
|
aiAssistEndpoint: string;
|
|
23
27
|
aiAssistOptions: { [key: string]: string };
|
|
24
28
|
aiAssistTooltipText?: string;
|
|
25
29
|
aiAssistClickOverride?: () => void;
|
|
30
|
+
activeFileId: FileId;
|
|
31
|
+
tabList: Array<TabState>;
|
|
32
|
+
paneId: PaneId;
|
|
26
33
|
}) => {
|
|
27
34
|
const {
|
|
28
35
|
shareDBDoc,
|
|
29
|
-
activeFileId,
|
|
30
|
-
activePaneId,
|
|
31
|
-
tabList,
|
|
32
36
|
editorCache,
|
|
33
37
|
runPrettierRef,
|
|
34
38
|
runCodeRef,
|
|
@@ -52,7 +56,7 @@ export const AIAssistWidget = ({
|
|
|
52
56
|
// Wait for generation to finish.
|
|
53
57
|
await startAIAssist({
|
|
54
58
|
view: editorCache.get(
|
|
55
|
-
editorCacheKey(activeFileId,
|
|
59
|
+
editorCacheKey(activeFileId, paneId),
|
|
56
60
|
).editor,
|
|
57
61
|
shareDBDoc,
|
|
58
62
|
fileId: activeFileId,
|
|
@@ -96,7 +100,7 @@ export const AIAssistWidget = ({
|
|
|
96
100
|
: currentAIStreamId,
|
|
97
101
|
);
|
|
98
102
|
}
|
|
99
|
-
}, [activeFileId,
|
|
103
|
+
}, [activeFileId, paneId, aiStreamId]);
|
|
100
104
|
|
|
101
105
|
const showWidget = enableStopGeneration
|
|
102
106
|
? true
|
package/src/client/App/index.tsx
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
import { useContext } from 'react';
|
|
1
|
+
import { useContext, useState } from 'react';
|
|
2
2
|
import ShareDBClient from 'sharedb-client-browser/dist/sharedb-client-umd.cjs';
|
|
3
3
|
import { json1Presence } from '../../ot';
|
|
4
4
|
import { Username } from '../../types';
|
|
5
5
|
// @ts-ignore
|
|
6
6
|
import PrettierWorker from '../usePrettier/worker?worker';
|
|
7
7
|
// @ts-ignore
|
|
8
|
-
import
|
|
8
|
+
import {
|
|
9
|
+
LiveKitRoom,
|
|
10
|
+
RoomAudioRenderer,
|
|
11
|
+
} from '@livekit/components-react';
|
|
12
|
+
import { v4 } from 'uuid';
|
|
9
13
|
import { SplitPaneResizeProvider } from '../SplitPaneResizeContext';
|
|
10
|
-
import { VZResizer } from '../VZResizer';
|
|
11
14
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from '../
|
|
15
|
-
import { useShareDB } from './useShareDB';
|
|
15
|
+
VZCodeContext,
|
|
16
|
+
VZCodeProvider,
|
|
17
|
+
} from '../VZCodeContext';
|
|
16
18
|
import { VZLeft } from '../VZLeft';
|
|
17
19
|
import { VZMiddle } from '../VZMiddle';
|
|
20
|
+
import { VZResizer } from '../VZResizer';
|
|
18
21
|
import { VZRight } from '../VZRight';
|
|
22
|
+
import TypeScriptWorker from '../useTypeScript/worker?worker';
|
|
19
23
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from '../
|
|
24
|
+
useInitialUsername,
|
|
25
|
+
usePersistUsername,
|
|
26
|
+
} from '../usernameLocalStorage';
|
|
23
27
|
import './style.scss';
|
|
28
|
+
import { useShareDB } from './useShareDB';
|
|
24
29
|
|
|
25
30
|
// Instantiate the Prettier worker.
|
|
26
31
|
const prettierWorker = new PrettierWorker();
|
|
@@ -62,13 +67,24 @@ function App() {
|
|
|
62
67
|
docPresence,
|
|
63
68
|
submitOperation,
|
|
64
69
|
connected,
|
|
70
|
+
pending,
|
|
65
71
|
} = useShareDB({
|
|
66
72
|
connection,
|
|
67
73
|
});
|
|
68
74
|
|
|
75
|
+
//state for the voice channel
|
|
76
|
+
const [liveKitToken, setLiveKitToken] =
|
|
77
|
+
useState(undefined);
|
|
78
|
+
const [liveKitRoomName, setLiveKitRoomName] =
|
|
79
|
+
useState(v4()); //default room name will be an arbitrary uuid
|
|
80
|
+
const [liveKitConnection, setLiveKitConnection] =
|
|
81
|
+
useState(false);
|
|
69
82
|
// Get the initial username from localStorage.
|
|
70
83
|
const initialUsername: Username = useInitialUsername();
|
|
71
84
|
|
|
85
|
+
//@ts-ignore
|
|
86
|
+
const serverUrl = import.meta.env.VITE_LIVEKIT_URL; //Vite only parses VITE_ prefixed environment variables
|
|
87
|
+
|
|
72
88
|
// Feature flag for enabling the right-side panel.
|
|
73
89
|
// Useful for debugging dual split pane functionality.
|
|
74
90
|
// We may want to add this as an actual VZCode feature,
|
|
@@ -89,17 +105,33 @@ function App() {
|
|
|
89
105
|
typeScriptWorker={typeScriptWorker}
|
|
90
106
|
initialUsername={initialUsername}
|
|
91
107
|
connected={connected}
|
|
108
|
+
pending={pending}
|
|
109
|
+
liveKitRoomName={liveKitRoomName}
|
|
110
|
+
setLiveKitRoom={setLiveKitRoomName}
|
|
111
|
+
liveKitToken={liveKitToken}
|
|
112
|
+
setLiveKitToken={setLiveKitToken}
|
|
113
|
+
liveKitConnection={liveKitConnection}
|
|
114
|
+
setLiveKitConnection={setLiveKitConnection}
|
|
92
115
|
>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{
|
|
97
|
-
|
|
98
|
-
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
116
|
+
<LiveKitRoom
|
|
117
|
+
audio={true}
|
|
118
|
+
token={liveKitToken}
|
|
119
|
+
serverUrl={serverUrl}
|
|
120
|
+
connect={liveKitConnection}
|
|
121
|
+
style={{ height: '100%' }}
|
|
122
|
+
>
|
|
123
|
+
<div className="app">
|
|
124
|
+
<VZLeft />
|
|
125
|
+
<VZMiddle allowGlobals={true} />
|
|
126
|
+
{enableRightPanel ? <VZRight /> : null}
|
|
127
|
+
<VZResizer side="left" />
|
|
128
|
+
{enableRightPanel ? (
|
|
129
|
+
<VZResizer side="right" />
|
|
130
|
+
) : null}
|
|
131
|
+
</div>
|
|
132
|
+
<PersistUsername />
|
|
133
|
+
<RoomAudioRenderer />
|
|
134
|
+
</LiveKitRoom>
|
|
103
135
|
</VZCodeProvider>
|
|
104
136
|
</SplitPaneResizeProvider>
|
|
105
137
|
);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
useCallback,
|
|
5
|
+
useRef,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import { ShareDBDoc, VZCodeContent } from '../../types';
|
|
8
|
+
|
|
9
|
+
// import { runDelay } from '../constants';
|
|
10
|
+
// TODO understand what this part is for
|
|
11
|
+
const runDelay = 1000;
|
|
12
|
+
|
|
13
|
+
export const usePending = (
|
|
14
|
+
shareDBDoc: ShareDBDoc<VZCodeContent>,
|
|
15
|
+
) => {
|
|
16
|
+
const lastOpTimerRef = useRef<NodeJS.Timeout>();
|
|
17
|
+
|
|
18
|
+
// TODO rename this to isPending,
|
|
19
|
+
// and also rename connected to isConnected.
|
|
20
|
+
const [pending, setPending] = useState<boolean>(false);
|
|
21
|
+
|
|
22
|
+
const resetOnNoPending = useCallback(() => {
|
|
23
|
+
if (!shareDBDoc) return;
|
|
24
|
+
|
|
25
|
+
// This happens AFTER the changes have been
|
|
26
|
+
// acknowledged by the server.
|
|
27
|
+
shareDBDoc.whenNothingPending(() => {
|
|
28
|
+
setPending(false);
|
|
29
|
+
});
|
|
30
|
+
}, [shareDBDoc, setPending]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!shareDBDoc) return;
|
|
34
|
+
|
|
35
|
+
// This happens when you type, but BEFORE
|
|
36
|
+
// the changes are sent to the server.
|
|
37
|
+
shareDBDoc.on('before op', () => {
|
|
38
|
+
setPending(true);
|
|
39
|
+
|
|
40
|
+
clearTimeout(lastOpTimerRef.current);
|
|
41
|
+
|
|
42
|
+
lastOpTimerRef.current = setTimeout(() => {
|
|
43
|
+
// should be triggered only once, cause upcoming ops unschedule previous resetOnNoPending
|
|
44
|
+
resetOnNoPending();
|
|
45
|
+
}, runDelay);
|
|
46
|
+
});
|
|
47
|
+
}, [shareDBDoc, resetOnNoPending]);
|
|
48
|
+
|
|
49
|
+
return pending;
|
|
50
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useState, useRef } from 'react';
|
|
2
2
|
import { randomId } from '../../randomId';
|
|
3
3
|
import { ShareDBDoc, VZCodeContent } from '../../types';
|
|
4
4
|
import { useSubmitOperation } from '../useSubmitOperation';
|
|
5
|
+
import { usePending } from './usePending';
|
|
5
6
|
|
|
6
7
|
// Set up the connection to ShareDB.
|
|
7
8
|
export const useShareDB = ({
|
|
@@ -30,6 +31,14 @@ export const useShareDB = ({
|
|
|
30
31
|
|
|
31
32
|
const [connected, setConnected] =
|
|
32
33
|
useState<boolean>(false);
|
|
34
|
+
|
|
35
|
+
// const [isSaving, setIsSaving] = useState<boolean>(false);
|
|
36
|
+
// const [isSaved, setIsSaved] = useState<boolean>(false);
|
|
37
|
+
|
|
38
|
+
// const debounceTimeoutRef = useRef<ReturnType<
|
|
39
|
+
// typeof setTimeout
|
|
40
|
+
// > | null>(null);
|
|
41
|
+
|
|
33
42
|
useEffect(() => {
|
|
34
43
|
// Listen for connection state changes
|
|
35
44
|
connection.on('connected', () => {
|
|
@@ -69,7 +78,28 @@ export const useShareDB = ({
|
|
|
69
78
|
// This is not required, because no part of the app outside
|
|
70
79
|
// of the CodeMirror integration uses those file contents.
|
|
71
80
|
// The Sidebar only needs to know file names, not contents.
|
|
81
|
+
// Start saving process when there’s an operation.
|
|
82
|
+
// setIsSaving(true);
|
|
83
|
+
// setIsSaved(false);
|
|
84
|
+
|
|
72
85
|
setContent(shareDBDoc.data);
|
|
86
|
+
|
|
87
|
+
// // Simulate a saving process and switch status to "saved"
|
|
88
|
+
// if (debounceTimeoutRef.current) {
|
|
89
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
90
|
+
// }
|
|
91
|
+
|
|
92
|
+
// // Set up a debounce timer to switch from "saving" to "saved" after 2 seconds of inactivity.
|
|
93
|
+
// debounceTimeoutRef.current = setTimeout(() => {
|
|
94
|
+
// setIsSaving(false);
|
|
95
|
+
// setIsSaved(true); // Only set isSaved to true after 2 seconds of inactivity
|
|
96
|
+
|
|
97
|
+
// const resetSavedTimeout = setTimeout(() => {
|
|
98
|
+
// setIsSaved(false);
|
|
99
|
+
// }, 1000); // 1 second delay after `isSaved` becomes true.
|
|
100
|
+
|
|
101
|
+
// return () => clearTimeout(resetSavedTimeout);
|
|
102
|
+
// }, 1800); // 1.8-second delay for detecting inactivity
|
|
73
103
|
});
|
|
74
104
|
|
|
75
105
|
// Set up presence.
|
|
@@ -101,11 +131,19 @@ export const useShareDB = ({
|
|
|
101
131
|
// TODO unsubscribe from presence
|
|
102
132
|
// TODO unsubscribe from doc
|
|
103
133
|
return () => {
|
|
134
|
+
// if (debounceTimeoutRef.current)
|
|
135
|
+
// clearTimeout(debounceTimeoutRef.current);
|
|
104
136
|
// shareDBDoc.destroy();
|
|
105
137
|
// docPresence.destroy();
|
|
106
138
|
};
|
|
107
139
|
}, []);
|
|
108
140
|
|
|
141
|
+
// True for "Saving..."
|
|
142
|
+
// False for "Saved"
|
|
143
|
+
const pending = usePending(shareDBDoc);
|
|
144
|
+
|
|
145
|
+
console.log('pending ', pending);
|
|
146
|
+
|
|
109
147
|
// A convenience function for mutating the ShareDB document
|
|
110
148
|
// based submitting OT ops generated by diffing the JSON.
|
|
111
149
|
const submitOperation =
|
|
@@ -118,5 +156,6 @@ export const useShareDB = ({
|
|
|
118
156
|
docPresence,
|
|
119
157
|
submitOperation,
|
|
120
158
|
connected,
|
|
159
|
+
pending,
|
|
121
160
|
};
|
|
122
161
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { inlineCopilot } from 'codemirror-copilot';
|
|
2
|
+
|
|
3
|
+
const defaultAICopilotEndpoint = '/ai-copilot';
|
|
4
|
+
|
|
5
|
+
export const copilot = ({
|
|
6
|
+
aiCopilotEndpoint = defaultAICopilotEndpoint,
|
|
7
|
+
}: {
|
|
8
|
+
aiCopilotEndpoint: string;
|
|
9
|
+
}) => {
|
|
10
|
+
return inlineCopilot(async (prefix, suffix) => {
|
|
11
|
+
const response = await fetch(aiCopilotEndpoint, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: { 'Content-Type': 'application/json' },
|
|
14
|
+
body: JSON.stringify({ prefix, suffix }),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const responseJson = await response.json();
|
|
18
|
+
|
|
19
|
+
if (responseJson.error) {
|
|
20
|
+
console.error(
|
|
21
|
+
'[copilot] error from server:',
|
|
22
|
+
responseJson.error,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return responseJson.text;
|
|
27
|
+
});
|
|
28
|
+
};
|
|
@@ -8,39 +8,135 @@ export const colorPickerRegex = /#[0-9A-Fa-f]{6}/g;
|
|
|
8
8
|
// Works without quotes to support CSS.
|
|
9
9
|
export const colorPicker = (
|
|
10
10
|
onInteract?: () => void,
|
|
11
|
-
):
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
sel.
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
11
|
+
): InteractRule => {
|
|
12
|
+
// Create the color picker element when the page loads
|
|
13
|
+
const sel: HTMLInputElement = document.createElement('input');
|
|
14
|
+
sel.type = 'color';
|
|
15
|
+
sel.style.position = 'fixed';
|
|
16
|
+
sel.style.left = '-9999px'; // Initially off-screen
|
|
17
|
+
sel.style.top = '-9999px';
|
|
18
|
+
document.body.appendChild(sel);
|
|
19
|
+
|
|
20
|
+
// Create a close button element to hide the color picker
|
|
21
|
+
const closeButton: HTMLButtonElement = document.createElement('button');
|
|
22
|
+
closeButton.innerText = 'X';
|
|
23
|
+
closeButton.style.position = 'absolute';
|
|
24
|
+
closeButton.style.width = '20px';
|
|
25
|
+
closeButton.style.height = '20px';
|
|
26
|
+
closeButton.style.borderRadius = '50%';
|
|
27
|
+
closeButton.style.border = 'none';
|
|
28
|
+
closeButton.style.backgroundColor = '#ff4d4f';
|
|
29
|
+
closeButton.style.color = 'white';
|
|
30
|
+
closeButton.style.fontSize = '12px';
|
|
31
|
+
closeButton.style.display = 'none'; // Initially hidden
|
|
32
|
+
closeButton.style.cursor = 'pointer';
|
|
33
|
+
closeButton.style.top = '-10px'; // Adjust position relative to color picker
|
|
34
|
+
closeButton.style.right = '-10px';
|
|
35
|
+
sel.parentElement?.appendChild(closeButton); // Add button to the same container
|
|
36
|
+
|
|
37
|
+
// Function to hide the color picker and close button
|
|
38
|
+
const hideColorPicker = () => {
|
|
39
|
+
sel.style.left = '-9999px';
|
|
40
|
+
sel.style.top = '-9999px';
|
|
41
|
+
closeButton.style.display = 'none';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Add event listener to the close button to hide color picker
|
|
45
|
+
closeButton.addEventListener('click', (event) => {
|
|
46
|
+
event.stopPropagation(); // Prevent click event from bubbling to document
|
|
47
|
+
hideColorPicker();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Add event listener for clicks outside the color picker
|
|
51
|
+
document.addEventListener('click', (event: MouseEvent) => {
|
|
52
|
+
if (event.target !== sel && event.target !== closeButton) {
|
|
53
|
+
hideColorPicker();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Function to set the text and color
|
|
58
|
+
const setText = (newText: string) => {
|
|
59
|
+
console.log('Applying color to the element:', newText);
|
|
60
|
+
|
|
61
|
+
// Assuming you're applying the color to a circle element with id 'colorCircle'
|
|
62
|
+
const colorCircle = document.getElementById('colorCircle');
|
|
63
|
+
if (colorCircle) {
|
|
64
|
+
colorCircle.style.backgroundColor = newText;
|
|
65
|
+
console.log(`Color applied to colorCircle: ${newText}`);
|
|
66
|
+
} else {
|
|
67
|
+
console.log('colorCircle element not found');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// If there is any other text element (like an input or div for displaying hex color), update it too.
|
|
71
|
+
const hexDisplay = document.getElementById('hexDisplay');
|
|
72
|
+
if (hexDisplay) {
|
|
73
|
+
hexDisplay.textContent = newText;
|
|
74
|
+
console.log(`Hex value applied to hexDisplay: ${newText}`);
|
|
75
|
+
} else {
|
|
76
|
+
console.log('hexDisplay element not found');
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
regexp: colorPickerRegex,
|
|
82
|
+
cursor: 'pointer',
|
|
83
|
+
|
|
84
|
+
onClick(
|
|
85
|
+
text: string,
|
|
86
|
+
setText: (newText: string) => void,
|
|
87
|
+
event: MouseEvent
|
|
88
|
+
) {
|
|
89
|
+
const startingColor: string = text;
|
|
90
|
+
|
|
91
|
+
// Set the initial value of the color picker
|
|
92
|
+
console.log('Initial color:', startingColor);
|
|
93
|
+
sel.value = startingColor.toLowerCase();
|
|
94
|
+
console.log('Color picker initial value set to:', sel.value);
|
|
95
|
+
|
|
96
|
+
// Position the color picker near the cursor
|
|
97
|
+
const cursorX = event.clientX;
|
|
98
|
+
const cursorY = event.clientY;
|
|
99
|
+
sel.style.left = cursorX + 'px';
|
|
100
|
+
sel.style.top = cursorY + 'px';
|
|
101
|
+
|
|
102
|
+
// Show and position the close button
|
|
103
|
+
closeButton.style.display = 'block';
|
|
104
|
+
closeButton.style.left = `${cursorX + sel.offsetWidth - 15}px`; // Adjust to place in corner
|
|
105
|
+
closeButton.style.top = `${cursorY - 15}px`;
|
|
106
|
+
|
|
107
|
+
// Update the color selection and setText on change
|
|
108
|
+
const updateHex = (e: Event) => {
|
|
109
|
+
const el: HTMLInputElement = e.target as HTMLInputElement;
|
|
110
|
+
console.log('Hex color value selected:', el.value);
|
|
111
|
+
|
|
112
|
+
if (el.value) {
|
|
113
|
+
setText(el.value.toUpperCase()); // Use uppercase for consistency
|
|
114
|
+
}
|
|
115
|
+
if (onInteract) {
|
|
116
|
+
console.log('onInteract called');
|
|
117
|
+
onInteract();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Update color display directly (for example, changing the background color of an element)
|
|
121
|
+
const colorDisplay = document.getElementById('colorDisplay');
|
|
122
|
+
if (colorDisplay) {
|
|
123
|
+
colorDisplay.style.backgroundColor = el.value;
|
|
124
|
+
console.log(`colorDisplay updated to: ${el.value}`);
|
|
125
|
+
} else {
|
|
126
|
+
console.log('colorDisplay element not found');
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Add input event listener to handle color selection
|
|
131
|
+
sel.addEventListener('input', updateHex);
|
|
132
|
+
|
|
133
|
+
// Trigger click event on the color picker to display it
|
|
134
|
+
sel.click();
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
|
|
44
140
|
|
|
45
141
|
// TODO get this working
|
|
46
142
|
// rgb color picker
|