@mpeggroup/mpeg-sdl-editor 1.3.1

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 (52) hide show
  1. package/.github/workflows/check-bun-dependencies.yml +13 -0
  2. package/.github/workflows/lint-pr-message.yml +13 -0
  3. package/.github/workflows/release-bun-webapp.yml +15 -0
  4. package/.github/workflows/validate-bun-webapp-pr.yml +9 -0
  5. package/LICENSE +21 -0
  6. package/README.md +59 -0
  7. package/build.ts +8 -0
  8. package/functional_tests/README.md +25 -0
  9. package/functional_tests/features/environment.py +50 -0
  10. package/functional_tests/features/steps/helper/selenium.py +15 -0
  11. package/functional_tests/features/steps/webapp.py +30 -0
  12. package/functional_tests/features/webapp.feature +5 -0
  13. package/functional_tests/pip-requirements.txt +2 -0
  14. package/html/favicon.svg +126 -0
  15. package/html/index.html +14 -0
  16. package/html/style.css +2 -0
  17. package/package.json +61 -0
  18. package/src/App.css +3 -0
  19. package/src/App.tsx +266 -0
  20. package/src/assets/mpeg.svg +143 -0
  21. package/src/codemirror/ruler.ts +58 -0
  22. package/src/components/DesktopPanels.tsx +65 -0
  23. package/src/components/Editor.tsx +384 -0
  24. package/src/components/MobileDrawer.tsx +57 -0
  25. package/src/components/Navbar.tsx +226 -0
  26. package/src/components/ResizableLayout.tsx +69 -0
  27. package/src/components/Settings.tsx +170 -0
  28. package/src/components/StatusBar.tsx +47 -0
  29. package/src/components/SubNav.tsx +58 -0
  30. package/src/hooks/useAutoDisplayCompletions.ts +26 -0
  31. package/src/hooks/useDragResize.ts +104 -0
  32. package/src/hooks/useEnableLinting.ts +21 -0
  33. package/src/hooks/useFileOperations.ts +102 -0
  34. package/src/hooks/useMobileDetection.ts +32 -0
  35. package/src/hooks/usePrettier.ts +40 -0
  36. package/src/hooks/useRulerWidth.ts +42 -0
  37. package/src/hooks/useShowSemanticWarnings.ts +23 -0
  38. package/src/hooks/useShowSymanticErrors.ts +23 -0
  39. package/src/hooks/useShowSyntaxErrors.ts +23 -0
  40. package/src/hooks/useTheme.ts +21 -0
  41. package/src/hooks/useToast.ts +37 -0
  42. package/src/logo/getLogoSvg.ts +5 -0
  43. package/src/logo/svg.module.d.ts +5 -0
  44. package/src/main.tsx +13 -0
  45. package/src/sdl/sdlComplete.ts +86 -0
  46. package/src/sdl/sdlLanguage.ts +18 -0
  47. package/src/sdl/sdlLinter.ts +157 -0
  48. package/tailwind.config.ts +16 -0
  49. package/tests/Editor_test.tsx +29 -0
  50. package/tests/happydom.ts +3 -0
  51. package/tests/sdlLanguage_test.ts +39 -0
  52. package/tsconfig.json +27 -0
package/src/App.tsx ADDED
@@ -0,0 +1,266 @@
1
+ import {
2
+ SemanticError,
3
+ SemanticWarning,
4
+ SyntaxError,
5
+ } from "@mpeggroup/mpeg-sdl-parser";
6
+ import { Editor } from "./components/Editor.tsx";
7
+ import type { EditorRef } from "./components/Editor.tsx";
8
+ import { Navbar } from "./components/Navbar.tsx";
9
+ import { SubNav } from "./components/SubNav.tsx";
10
+ import { Settings } from "./components/Settings.tsx";
11
+ import { StatusBar } from "./components/StatusBar.tsx";
12
+ import { ResizableLayout } from "./components/ResizableLayout.tsx";
13
+ import { useToast } from "./hooks/useToast.ts";
14
+ import { useFileOperations } from "./hooks/useFileOperations.ts";
15
+ import { useTheme } from "./hooks/useTheme.ts";
16
+ import { usePrettier } from "./hooks/usePrettier.ts";
17
+ import { useRulerWidth } from "./hooks/useRulerWidth.ts";
18
+ import { useAutoDisplayCompletions } from "./hooks/useAutoDisplayCompletions.ts";
19
+ import { useEnableLinting } from "./hooks/useEnableLinting.ts";
20
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
21
+ import { useMobileDetection } from "./hooks/useMobileDetection.ts";
22
+ import { useShowSyntaxErrors } from "./hooks/useShowSyntaxErrors.ts";
23
+ import { useShowSemanticErrors } from "./hooks/useShowSymanticErrors.ts";
24
+ import { useShowSemanticWarnings } from "./hooks/useShowSemanticWarnings.ts";
25
+
26
+ function getInitialCodeFromHash(): string | null {
27
+ const hash = globalThis.location.hash;
28
+ if (hash.startsWith("#c=")) {
29
+ try {
30
+ return atob(hash.slice(3));
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+
38
+ export function App() {
39
+ const defaultCode =
40
+ "// Start typing your SDL here... <Ctrl+Space> for completions\n";
41
+
42
+ const [code, setCodeInternal] = useState<string>(
43
+ () => getInitialCodeFromHash() ?? defaultCode,
44
+ );
45
+
46
+ const setCode = useCallback((newCode: string) => {
47
+ setCodeInternal(newCode);
48
+ }, []);
49
+
50
+ useEffect(() => {
51
+ const handleHashChange = () => {
52
+ const hashCode = getInitialCodeFromHash();
53
+ if (hashCode !== null) {
54
+ setCode(hashCode);
55
+ }
56
+ };
57
+ globalThis.addEventListener("hashchange", handleHashChange);
58
+ return () => globalThis.removeEventListener("hashchange", handleHashChange);
59
+ }, []);
60
+ const [toastState, showToast] = useToast();
61
+ const { theme, toggleTheme } = useTheme();
62
+ const isMobile = useMobileDetection();
63
+ const [isSettingsShown, setSettingsShown] = useState(false);
64
+ const { rulerWidth, setRulerWidth } = useRulerWidth();
65
+ const { autoDisplayCompletions, setAutoDisplayCompletions } =
66
+ useAutoDisplayCompletions();
67
+ const { enableLinting, setEnableLinting } = useEnableLinting();
68
+ const { showSyntaxErrors, setShowSyntaxErrors } = useShowSyntaxErrors();
69
+ const { showSemanticErrors, setShowSemanticErrors } = useShowSemanticErrors();
70
+ const { showSemanticWarnings, setShowSemanticWarnings } =
71
+ useShowSemanticWarnings();
72
+ const [cursorPosition, setCursorPosition] = useState<
73
+ { line: number; col: number }
74
+ >({ line: 1, col: 1 });
75
+ const editorRef = useRef<EditorRef>(null);
76
+
77
+ const onCursorChange = useCallback(
78
+ (position: { line: number; col: number }) => {
79
+ setCursorPosition((prev) => {
80
+ // Only update if position actually changed
81
+ if (prev.line !== position.line || prev.col !== position.col) {
82
+ return position;
83
+ }
84
+ return prev;
85
+ });
86
+ },
87
+ [],
88
+ );
89
+ const handleShare = useCallback(async () => {
90
+ const encoded = btoa(code);
91
+ const url =
92
+ `${globalThis.location.origin}${globalThis.location.pathname}#c=${encoded}`;
93
+ try {
94
+ await navigator.clipboard.writeText(url);
95
+ showToast("URL copied!");
96
+ } catch {
97
+ showToast("Failed to copy URL", "error");
98
+ }
99
+ }, [code, showToast]);
100
+
101
+ const {
102
+ handleSave,
103
+ handleLoad,
104
+ handleCopy,
105
+ } = useFileOperations({
106
+ code,
107
+ setCode,
108
+ showToast,
109
+ editorRef,
110
+ });
111
+
112
+ const lineCount = useMemo(() => code.split("\n").length, [code]);
113
+ const characterCount = useMemo(() => code.length, [code]);
114
+ const [syntaxErrors, setSyntaxErrors] = useState<SyntaxError[]>([]);
115
+ const [semanticErrors, setSemanticErrors] = useState<SemanticError[]>([]);
116
+ const [semanticWarnings, setSemanticWarnings] = useState<SemanticWarning[]>(
117
+ [],
118
+ );
119
+
120
+ const onSyntaxErrorChange = useCallback((newErrors: SyntaxError[]) => {
121
+ setSyntaxErrors(newErrors);
122
+ }, []);
123
+
124
+ const onSemanticErrorChange = useCallback((newErrors: SemanticError[]) => {
125
+ setSemanticErrors(newErrors);
126
+ }, []);
127
+
128
+ const onSemanticWarningChange = useCallback(
129
+ (newErrors: SemanticWarning[]) => {
130
+ setSemanticWarnings(newErrors);
131
+ },
132
+ [],
133
+ );
134
+
135
+ const syntaxErrorCount = useMemo(() => syntaxErrors.length, [
136
+ syntaxErrors,
137
+ ]);
138
+
139
+ const semanticErrorCount = useMemo(() => semanticErrors.length, [
140
+ semanticErrors,
141
+ ]);
142
+
143
+ const semanticWarningCount = useMemo(() => semanticWarnings.length, [
144
+ semanticWarnings,
145
+ ]);
146
+
147
+ const { handlePrettify } = usePrettier({
148
+ code,
149
+ setCode,
150
+ showToast,
151
+ rulerWidth,
152
+ });
153
+
154
+ const toggleSettings = useCallback(() => {
155
+ setSettingsShown((prev) => !prev);
156
+ }, []);
157
+
158
+ const handleExpandAll = useCallback(() => {
159
+ if (editorRef.current) {
160
+ editorRef.current.expandAll();
161
+ }
162
+ }, []);
163
+
164
+ const handleCollapseAll = useCallback(() => {
165
+ if (editorRef.current) {
166
+ editorRef.current.collapseAll();
167
+ }
168
+ }, []);
169
+
170
+ return (
171
+ <div className="flex flex-col h-screen">
172
+ <Navbar
173
+ theme={theme}
174
+ onCopy={handleCopy}
175
+ onSave={handleSave}
176
+ onLoad={handleLoad}
177
+ onShare={handleShare}
178
+ onToggleSettings={toggleSettings}
179
+ isSettingsShown={isSettingsShown}
180
+ />
181
+ <div className="grow overflow-hidden" data-testid="main-content">
182
+ <ResizableLayout
183
+ theme={theme}
184
+ isSettingsShown={isSettingsShown}
185
+ isMobile={isMobile}
186
+ onToggleSettings={toggleSettings}
187
+ >
188
+ <div className="flex flex-col h-full">
189
+ <SubNav
190
+ theme={theme}
191
+ onPrettify={handlePrettify}
192
+ onExpandAll={handleExpandAll}
193
+ onCollapseAll={handleCollapseAll}
194
+ />
195
+ <div className="grow overflow-auto">
196
+ <Editor
197
+ ref={editorRef}
198
+ code={code}
199
+ onCodeChange={setCode}
200
+ onCursorChange={onCursorChange}
201
+ onSyntaxErrorChange={onSyntaxErrorChange}
202
+ onSemanticErrorChange={onSemanticErrorChange}
203
+ onSemanticWarningChange={onSemanticWarningChange}
204
+ theme={theme}
205
+ rulerWidth={rulerWidth}
206
+ autoDisplayCompletions={autoDisplayCompletions}
207
+ enableLinting={enableLinting}
208
+ showSyntaxErrors={showSyntaxErrors}
209
+ showSemanticErrors={showSemanticErrors}
210
+ showSemanticWarnings={showSemanticWarnings}
211
+ />
212
+ </div>
213
+ <StatusBar
214
+ lineCount={lineCount}
215
+ characterCount={characterCount}
216
+ syntaxErrorCount={syntaxErrorCount}
217
+ semanticErrorCount={semanticErrorCount}
218
+ semanticWarningCount={semanticWarningCount}
219
+ cursorPosition={cursorPosition}
220
+ enableLinting={enableLinting}
221
+ showSyntaxErrors={showSyntaxErrors}
222
+ showSemanticErrors={showSemanticErrors}
223
+ showSemanticWarnings={showSemanticWarnings}
224
+ />
225
+ </div>
226
+
227
+ <div className="h-full md:p-2 md:pl-0">
228
+ <Settings
229
+ theme={theme}
230
+ onToggleTheme={toggleTheme}
231
+ rulerWidth={rulerWidth}
232
+ onRulerWidthChange={setRulerWidth}
233
+ autoDisplayCompletions={autoDisplayCompletions}
234
+ onAutoDisplayCompletionsChange={setAutoDisplayCompletions}
235
+ enableLinting={enableLinting}
236
+ onEnableLintingChange={setEnableLinting}
237
+ showSyntaxErrors={showSyntaxErrors}
238
+ onShowSyntaxErrorsChange={setShowSyntaxErrors}
239
+ showSemanticErrors={showSemanticErrors}
240
+ onShowSemanticErrorsChange={setShowSemanticErrors}
241
+ showSemanticWarnings={showSemanticWarnings}
242
+ onShowSemanticWarningsChange={setShowSemanticWarnings}
243
+ />
244
+ </div>
245
+ </ResizableLayout>
246
+ </div>
247
+ {toastState && (
248
+ <div className="toast toast-top toast-end mt-14">
249
+ <div
250
+ className={`alert ${
251
+ toastState.type === "success"
252
+ ? "alert-success"
253
+ : toastState.type === "warning"
254
+ ? "alert-warning"
255
+ : "alert-error"
256
+ }`}
257
+ >
258
+ <span>{toastState.message}</span>
259
+ </div>
260
+ </div>
261
+ )}
262
+ </div>
263
+ );
264
+ }
265
+
266
+ export default App;
@@ -0,0 +1,143 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg
3
+ id="Layer_1"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ version="1.1"
6
+ viewBox="0 0 1800 500"
7
+ >
8
+ <!-- Generator: Adobe Illustrator 29.5.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 141) -->
9
+ <defs>
10
+ <style>.st0 {
11
+ fill: #08a6e0;
12
+ }
13
+
14
+ .st1 {
15
+ fill: #006fa9;
16
+ }
17
+
18
+ .st2 {
19
+ fill: #0071bb;
20
+ }
21
+
22
+ .st3 {
23
+ fill: #002a53;
24
+ }</style>
25
+ </defs>
26
+ <path
27
+ class="st3"
28
+ d="M421.2,445.7c0,6.2-5,11.2-11.2,11.2s-11.2-5-11.2-11.2,5-11.2,11.2-11.2,11.2,5,11.2,11.2"
29
+ />
30
+ <g>
31
+ <rect class="st2" x="292.5" y="348.4" width="39.2" height="39.2" />
32
+ <rect class="st1" x="292.5" y="230.9" width="39.2" height="39.2" />
33
+ <rect class="st2" x="292.5" y="113.4" width="39.2" height="39.2" />
34
+ <path
35
+ class="st2"
36
+ d="M292.5,74.2h39.2v-19.6c0-10.8-8.8-19.6-19.6-19.6s-19.6,8.8-19.6,19.6v19.6Z"
37
+ />
38
+ <path
39
+ class="st2"
40
+ d="M463.1,348.4h0c10.8,0,19.6-8.8,19.6-19.6v-19.6h-39.2v19.6c0,10.8,8.8,19.6,19.6,19.6"
41
+ />
42
+ <rect class="st2" x="443.6" y="191.7" width="39.2" height="39.2" />
43
+ <path
44
+ class="st1"
45
+ d="M443.6,152.6h39.2v-19.6c0-10.8-8.8-19.6-19.6-19.6s-19.6,8.8-19.6,19.6v19.6Z"
46
+ />
47
+ <rect class="st2" x="342.8" y="270.1" width="39.2" height="39.2" />
48
+ <rect class="st2" x="342.8" y="152.6" width="39.2" height="39.2" />
49
+ <rect class="st2" x="393.2" y="211.3" width="39.2" height="39.2" />
50
+ <path
51
+ class="st2"
52
+ d="M393.2,133h39.2v-19.6c0-10.8-8.8-19.6-19.6-19.6s-19.6,8.8-19.6,19.6v19.6Z"
53
+ />
54
+ <rect class="st3" x="292.5" y="309.2" width="39.2" height="39.2" />
55
+ <rect class="st3" x="292.5" y="191.7" width="39.2" height="39.2" />
56
+ <rect class="st3" x="292.5" y="74.2" width="39.2" height="39.2" />
57
+ <rect class="st3" x="443.6" y="270.1" width="39.2" height="39.2" />
58
+ <rect class="st3" x="443.6" y="152.6" width="39.2" height="39.2" />
59
+ <path
60
+ class="st3"
61
+ d="M362.4,387.6h0c10.8,0,19.6-8.8,19.6-19.6v-19.6h-39.2v19.6c0,10.8,8.8,19.6,19.6,19.6"
62
+ />
63
+ <rect class="st3" x="342.8" y="230.9" width="39.2" height="39.2" />
64
+ <rect class="st3" x="393.2" y="172.1" width="39.2" height="39.2" />
65
+ <rect class="st2" x="393.2" y="289.6" width="39.2" height="39.2" />
66
+ <rect class="st3" x="393.2" y="250.5" width="39.2" height="39.2" />
67
+ <rect class="st3" x="342.8" y="113.4" width="39.2" height="39.2" />
68
+ <path
69
+ class="st0"
70
+ d="M312.1,426.7h0c10.8,0,19.6-8.8,19.6-19.6v-19.6h-39.2v19.6c0,10.8,8.8,19.6,19.6,19.6"
71
+ />
72
+ <rect class="st0" x="292.5" y="270.1" width="39.2" height="39.2" />
73
+ <rect class="st0" x="292.5" y="152.6" width="39.2" height="39.2" />
74
+ <rect class="st0" x="443.6" y="230.9" width="39.2" height="39.2" />
75
+ <rect class="st0" x="342.8" y="309.2" width="39.2" height="39.2" />
76
+ <path
77
+ class="st3"
78
+ d="M563.9,270.1h0c10.8,0,19.6-8.8,19.6-19.6v-19.6h-39.2v19.6c0,10.8,8.8,19.6,19.6,19.6"
79
+ />
80
+ <path
81
+ class="st2"
82
+ d="M544.3,230.9h39.2v-19.6c0-10.8-8.8-19.6-19.6-19.6s-19.6,8.8-19.6,19.6v19.6Z"
83
+ />
84
+ <g>
85
+ <rect class="st2" x="493.9" y="230.9" width="39.2" height="39.2" />
86
+ <path
87
+ class="st3"
88
+ d="M513.5,152.6h0c-10.8,0-19.6,8.8-19.6,19.6v19.6h39.2v-19.6c0-10.8-8.8-19.6-19.6-19.6"
89
+ />
90
+ <path
91
+ class="st3"
92
+ d="M533.1,270.1h-39.2v19.6c0,10.8,8.8,19.6,19.6,19.6s19.6-8.8,19.6-19.6v-19.6Z"
93
+ />
94
+ <rect class="st0" x="493.9" y="191.7" width="39.2" height="39.2" />
95
+ </g>
96
+ <rect class="st0" x="342.8" y="191.7" width="39.2" height="39.2" />
97
+ <path
98
+ class="st3"
99
+ d="M412.8,368h0c10.8,0,19.6-8.8,19.6-19.6v-19.6h-39.2v19.6c0,10.8,8.8,19.6,19.6,19.6"
100
+ />
101
+ <rect class="st0" x="393.2" y="133" width="39.2" height="39.2" />
102
+ <path
103
+ class="st0"
104
+ d="M342.8,113.4h39.2v-19.6c0-10.8-8.8-19.6-19.6-19.6s-19.6,8.8-19.6,19.6v19.6Z"
105
+ />
106
+ <path
107
+ class="st3"
108
+ d="M482.7,88.2c0,10.8-8.8,19.6-19.6,19.6s-19.6-8.8-19.6-19.6,8.8-19.6,19.6-19.6,19.6,8.8,19.6,19.6"
109
+ />
110
+ <path
111
+ class="st1"
112
+ d="M432.4,398.1c0,10.8-8.8,19.6-19.6,19.6s-19.6-8.8-19.6-19.6,8.8-19.6,19.6-19.6,19.6,8.8,19.6,19.6"
113
+ />
114
+ <path
115
+ class="st0"
116
+ d="M521.9,85.4c0,6.2-5,11.2-11.2,11.2s-11.2-5-11.2-11.2,5-11.2,11.2-11.2,11.2,5,11.2,11.2"
117
+ />
118
+ <path
119
+ class="st1"
120
+ d="M505.1,49c0,7.7-6.3,14-14,14s-14-6.3-14-14,6.3-14,14-14,14,6.3,14,14"
121
+ />
122
+ <path
123
+ class="st0"
124
+ d="M465.9,426.1c0,7.7-6.3,14-14,14s-14-6.3-14-14,6.3-14,14-14,14,6.3,14,14"
125
+ />
126
+ <path
127
+ class="st2"
128
+ d="M1297.6,143.3h0c0,10.3-8.4,18.7-18.7,18.7h-106.3v37.1h87.5c10.3,0,18.7,8.4,18.7,18.7s-8.4,18.7-18.7,18.7h-87.5v41.9h106.3c10.3,0,18.7,8.4,18.7,18.7s-8.4,18.7-18.7,18.7h-143.7V124.5h143.7c10.3,0,18.7,8.4,18.7,18.7"
129
+ />
130
+ <path
131
+ class="st2"
132
+ d="M1464.5,162h-69.5c-20.7,0-37.5,16.8-37.5,37.5v41.5c0,20.7,16.8,37.5,37.5,37.5h35.1c22,0,39.8-17.8,39.8-39.8v-2h-37.5c-10.3,0-18.7-8.4-18.7-18.7s8.4-18.7,18.7-18.7h74.9v39.5c0,42.7-34.6,77.3-77.3,77.3h-35.1c-41.4,0-74.9-33.6-74.9-74.9v-41.5c0-41.4,33.6-74.9,74.9-74.9h69.5c10.3,0,18.7,8.4,18.7,18.7s-8.4,18.7-18.7,18.7"
133
+ />
134
+ <path
135
+ class="st2"
136
+ d="M660.2,199.5v97.7c0,10.3-8.4,18.7-18.7,18.7s-18.7-8.4-18.7-18.7v-97.7c0-41.4,33.6-74.9,74.9-74.9s43.7,6.2,56.2,18.7c12.5-12.5,31.2-18.7,56.2-18.7,41.4,0,75,33.6,75,74.9v97.7c0,10.3-8.4,18.7-18.7,18.7s-18.7-8.4-18.7-18.7v-97.7c0-20.7-16.7-37.5-37.4-37.5-25,0-37.5,12.5-37.5,37.5v97.7c0,10.3-8.4,18.7-18.7,18.7s-18.7-8.4-18.7-18.7v-97.7c0-25-12.5-37.5-37.5-37.5-20.7,0-37.4,16.8-37.4,37.5"
137
+ />
138
+ <path
139
+ class="st2"
140
+ d="M1029.1,124.5c50.4,0,75.3,25.4,74.9,76.2-.3,40.9-34,73.7-74.9,73.7h-74.9v22.8c0,10.3-8.4,18.7-18.7,18.7s-18.7-8.4-18.7-18.7V124.5h112.4ZM1066.6,199.5c0-25-12.5-37.5-37.5-37.5h-74.9v74.9h74.9c25,0,37.5-12.5,37.5-37.5"
141
+ />
142
+ </g>
143
+ </svg>
@@ -0,0 +1,58 @@
1
+ import { type Extension, Facet } from "@codemirror/state";
2
+ import { EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";
3
+
4
+ const baseTheme = EditorView.baseTheme({
5
+ "&light .cm-ruler": { borderRight: "1px dotted black", opacity: 0.2 },
6
+ "&dark .cm-ruler": { borderRight: "1px dotted white", opacity: 0.2 },
7
+ });
8
+
9
+ const rulerWidth = Facet.define<number, number>({
10
+ combine: (values) => values.length ? Math.min(...values) : 10,
11
+ });
12
+
13
+ const showRuler = ViewPlugin.fromClass(
14
+ class {
15
+ rulerDiv: HTMLDivElement;
16
+ currentColumn: number;
17
+
18
+ constructor(view: EditorView) {
19
+ this.currentColumn = view.state.facet(rulerWidth);
20
+
21
+ this.rulerDiv = view.dom.appendChild(document.createElement("div"));
22
+ this.rulerDiv.classList.add("cm-ruler");
23
+ this.rulerDiv.style.cssText =
24
+ "position: absolute; top: 0; height: 100%; width: 1px; pointer-events: none; overflow: hidden;";
25
+
26
+ this.updateRulePosition(view, this.currentColumn);
27
+ }
28
+
29
+ update(update: ViewUpdate) {
30
+ const newColumn = update.state.facet(rulerWidth);
31
+ if (update.geometryChanged || newColumn !== this.currentColumn) {
32
+ this.currentColumn = newColumn;
33
+ this.updateRulePosition(update.view, newColumn);
34
+ }
35
+ }
36
+
37
+ updateRulePosition(view: EditorView, column: number) {
38
+ const defaultCharacterWidth = view.defaultCharacterWidth;
39
+ const gutterWidth = view.contentDOM.getBoundingClientRect().x;
40
+
41
+ this.rulerDiv.style.left = `${
42
+ gutterWidth + (column * defaultCharacterWidth) + 6
43
+ }px`;
44
+ }
45
+
46
+ destroy() {
47
+ this.rulerDiv.remove();
48
+ }
49
+ },
50
+ );
51
+
52
+ export function ruler(column: number): Extension {
53
+ return [
54
+ baseTheme,
55
+ rulerWidth.of(column),
56
+ showRuler,
57
+ ];
58
+ }
@@ -0,0 +1,65 @@
1
+ import React from "react";
2
+
3
+ interface DesktopPanelsProps {
4
+ leftChild: React.ReactNode;
5
+ rightChild: React.ReactNode;
6
+ splitPercentage: number;
7
+ isSettingsShown: boolean;
8
+ isDragging: boolean;
9
+ onMouseDown: (e: React.MouseEvent) => void;
10
+ }
11
+
12
+ export function DesktopPanels({
13
+ leftChild,
14
+ rightChild,
15
+ splitPercentage,
16
+ isSettingsShown,
17
+ isDragging,
18
+ onMouseDown,
19
+ }: DesktopPanelsProps) {
20
+ return (
21
+ <>
22
+ {/* Left panel */}
23
+ <div
24
+ className="flex flex-col"
25
+ style={{ width: !isSettingsShown ? "100%" : `${splitPercentage}%` }}
26
+ >
27
+ {leftChild}
28
+ </div>
29
+
30
+ {/* Divider - only show when panel is not collapsed */}
31
+
32
+ {isSettingsShown && (
33
+ <div
34
+ className={`w-1 bg-base-300 hover:bg-primary/50 cursor-col-resize transition-all duration-150 ${
35
+ isDragging ? "bg-primary/70 w-1.5" : ""
36
+ } relative group`}
37
+ onMouseDown={onMouseDown}
38
+ title="Drag to resize panels"
39
+ >
40
+ <div className="absolute inset-0 flex items-center justify-center">
41
+ <div
42
+ className={`w-0.5 h-12 bg-base-content/30 rounded-full transition-all ${
43
+ isDragging
44
+ ? "bg-primary-content h-16"
45
+ : "group-hover:bg-base-content/50 group-hover:h-16"
46
+ }`}
47
+ >
48
+ </div>
49
+ </div>
50
+ </div>
51
+ )}
52
+
53
+ {/* Right panel - only show when not collapsed */}
54
+
55
+ {isSettingsShown && (
56
+ <div
57
+ className="flex flex-col"
58
+ style={{ width: `${Math.max(100 - splitPercentage, 5)}%` }}
59
+ >
60
+ {rightChild}
61
+ </div>
62
+ )}
63
+ </>
64
+ );
65
+ }