Skip to content

Commit 4194206

Browse files
committed
feat: enhance HTML editor integration and toolbar functionality
- Replaced HtmlEditor component with a more versatile Editor component for HTML content rendering. - Introduced useHtmlEditor hook to manage HTML editor state and functionality. - Updated Editor.scss to improve toolbar layout and added new HTML-specific controls. - Enhanced Editor component to conditionally display HTML controls based on the selected language. - Refactored HtmlEditor to provide reusable controls for HTML editing, improving code organization and maintainability.
1 parent b26a603 commit 4194206

File tree

5 files changed

+125
-92
lines changed

5 files changed

+125
-92
lines changed

src/frontend/src/CustomEmbeddableRenderer.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
Dashboard,
88
StateIndicator,
99
ControlButton,
10-
HtmlEditor,
1110
Editor,
1211
Terminal,
1312
} from './pad';
@@ -28,7 +27,11 @@ export const renderCustomEmbeddable = (
2827

2928
switch (path) {
3029
case 'html':
31-
content = <HtmlEditor element={element} appState={appState} excalidrawAPI={excalidrawAPI} />;
30+
content = <Editor
31+
element={element}
32+
language="html"
33+
excalidrawAPI={excalidrawAPI}
34+
/>;
3235
title = "HTML Editor";
3336
break;
3437
case 'editor':

src/frontend/src/pad/editors/Editor.scss

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
&__toolbar {
1212
display: flex;
13-
justify-content: right;
13+
justify-content: space-between; /* Space between HTML controls and right-side controls */
1414
padding: 8px;
1515
position: absolute; /* Position at bottom */
1616
bottom: 0;
@@ -19,7 +19,47 @@
1919
z-index: 10; /* Keep toolbar above other elements */
2020
// background-color: #191919; /* Match editor background */
2121
border-top: 2px solid #3c3c3c;
22+
}
23+
24+
&__toolbar-right {
25+
display: flex;
26+
align-items: center;
2227
gap: 8px; /* Add spacing between toolbar items */
28+
29+
.excalidraw-tooltip-wrapper {
30+
height: 100%;
31+
display: flex;
32+
align-items: center;
33+
}
34+
}
35+
36+
&__html-controls {
37+
display: flex;
38+
align-items: center;
39+
gap: 8px;
40+
margin-right: auto; /* Push to the left side */
41+
42+
.html-editor__label {
43+
font-size: 12px;
44+
color: #e0e0e0;
45+
display: flex;
46+
align-items: center;
47+
gap: 4px;
48+
}
49+
50+
.html-editor__button {
51+
padding: 6px 12px;
52+
background: #5294f6;
53+
color: white;
54+
border: none;
55+
border-radius: 4px;
56+
cursor: pointer;
57+
font-size: 12px;
58+
59+
&:hover {
60+
background: #4285e7;
61+
}
62+
}
2363
}
2464

2565
&__format-button {
@@ -32,7 +72,7 @@
3272
display: flex;
3373
align-items: center;
3474
justify-content: center;
35-
75+
height: 100%;
3676
&:hover {
3777
background-color: #2a2d2e;
3878
color: #ffffff;

src/frontend/src/pad/editors/Editor.tsx

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useRef, useState, useEffect, useCallback } from 'react';
22
import MonacoEditor from '@monaco-editor/react';
33
import { Tooltip, updateTooltipPosition, getTooltipDiv } from '@atyrode/excalidraw';
44
import SearchableLanguageSelector from './SearchableLanguageSelector';
5+
import { useHtmlEditor, HtmlEditorControls, defaultHtml } from './HtmlEditor';
56
import './Editor.scss';
67

78
// Custom tooltip wrapper that positions the tooltip at the top
@@ -294,12 +295,20 @@ const Editor: React.FC<EditorProps> = ({
294295
}
295296
};
296297

298+
// Initialize HTML editor functionality if the language is HTML
299+
const isHtml = currentLanguage === 'html';
300+
301+
// Only initialize HTML editor hooks if we're in HTML mode and have the necessary props
302+
const htmlEditor = isHtml && element && excalidrawAPI
303+
? useHtmlEditor(element, editorRef, excalidrawAPI)
304+
: null;
305+
297306
return (
298307
<div className="editor__wrapper">
299308
<MonacoEditor
300309
height={height}
301310
language={currentLanguage}
302-
defaultValue={defaultValue}
311+
defaultValue={defaultValue || (isHtml ? defaultHtml : '')}
303312
theme={theme}
304313
options={options}
305314
onMount={handleEditorDidMount}
@@ -308,22 +317,37 @@ const Editor: React.FC<EditorProps> = ({
308317
/>
309318
{showLanguageSelector && (
310319
<div className="editor__toolbar">
311-
<TopTooltip label="Format" children={
312-
<button
313-
className="editor__format-button"
314-
onClick={formatDocument}
315-
aria-label="Format Document"
316-
>
317-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
318-
<path d="M2 4H14M4 8H12M6 12H10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
319-
</svg>
320-
</button>
321-
} />
322-
<SearchableLanguageSelector
323-
value={currentLanguage}
324-
onChange={handleLanguageChange}
325-
className="editor__language-selector"
326-
/>
320+
{/* Show HTML-specific controls when language is HTML */}
321+
{isHtml && htmlEditor && (
322+
<div className="editor__html-controls">
323+
<HtmlEditorControls
324+
createNew={htmlEditor.createNew}
325+
setCreateNew={htmlEditor.setCreateNew}
326+
applyHtml={htmlEditor.applyHtml}
327+
/>
328+
</div>
329+
)}
330+
331+
{/* Group format button and language selector together on the right */}
332+
<div className="editor__toolbar-right">
333+
<TopTooltip label="Format" children={
334+
<button
335+
className="editor__format-button"
336+
onClick={formatDocument}
337+
aria-label="Format Document"
338+
>
339+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
340+
<path d="M2 4H14M4 8H12M6 12H10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
341+
</svg>
342+
</button>
343+
} />
344+
345+
<SearchableLanguageSelector
346+
value={currentLanguage}
347+
onChange={handleLanguageChange}
348+
className="editor__language-selector"
349+
/>
350+
</div>
327351
</div>
328352
)}
329353
</div>
Lines changed: 36 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,18 @@
1-
import React, { useState, useRef, useEffect } from 'react';
1+
import React, { useState } from 'react';
22
import type { NonDeleted, ExcalidrawEmbeddableElement } from '@atyrode/excalidraw/element/types';
3-
import type { AppState } from '@atyrode/excalidraw/types';
4-
import Editor from './Editor';
53
import { ExcalidrawElementFactory } from '../../lib/ExcalidrawElementFactory';
64
import './HtmlEditor.scss';
75

8-
interface HtmlEditorProps {
9-
element: NonDeleted<ExcalidrawEmbeddableElement>;
10-
appState: AppState;
11-
excalidrawAPI?: any;
12-
}
6+
// Default HTML content for new HTML elements
7+
export const defaultHtml = '<button style="padding: 8px; background: #5294f6; color: white; border: none; border-radius: 4px;">Example Button</button>';
138

14-
export const HtmlEditor: React.FC<HtmlEditorProps> = ({
15-
element,
16-
appState,
17-
excalidrawAPI
18-
}) => {
9+
// Hook to manage HTML editor state and functionality
10+
export const useHtmlEditor = (
11+
element: NonDeleted<ExcalidrawEmbeddableElement>,
12+
editorRef: React.RefObject<any>,
13+
excalidrawAPI?: any
14+
) => {
1915
const [createNew, setCreateNew] = useState(true);
20-
const defaultHtml = '<button style="padding: 8px; background: #5294f6; color: white; border: none; border-radius: 4px;">Example Button</button>';
21-
const [editorValue, setEditorValue] = useState(
22-
element.customData?.editorContent || defaultHtml
23-
);
24-
const editorRef = useRef<any>(null);
25-
const elementIdRef = useRef(element.id);
26-
27-
// Load content from customData when element changes (e.g., when cloned or pasted)
28-
useEffect(() => {
29-
// Check if element ID has changed (indicating a new element)
30-
if (element.id !== elementIdRef.current) {
31-
elementIdRef.current = element.id;
32-
33-
// If element has customData with editorContent, update the state
34-
if (element.customData?.editorContent) {
35-
setEditorValue(element.customData.editorContent);
36-
} else {
37-
setEditorValue(defaultHtml);
38-
}
39-
40-
// Note: We don't need to update language here since HtmlEditor always uses 'html'
41-
// But we still save it in customData for consistency
42-
}
43-
}, [element.id, element.customData, defaultHtml]);
44-
45-
const handleEditorMount = (editor: any) => {
46-
editorRef.current = editor;
47-
};
4816

4917
const applyHtml = () => {
5018
if (!excalidrawAPI || !editorRef.current) return;
@@ -65,7 +33,7 @@ export const HtmlEditor: React.FC<HtmlEditorProps> = ({
6533
id: createNew ? undefined : element.id,
6634
customData: {
6735
editorContent: currentContent,
68-
editorLanguage: 'html' // Always set to html for HtmlEditor
36+
editorLanguage: 'html' // Always set to html for HTML content
6937
}
7038
});
7139

@@ -93,34 +61,32 @@ export const HtmlEditor: React.FC<HtmlEditorProps> = ({
9361
excalidrawAPI.setActiveTool({ type: "selection" });
9462
};
9563

64+
return {
65+
createNew,
66+
setCreateNew,
67+
applyHtml
68+
};
69+
};
70+
71+
// HTML-specific toolbar controls component
72+
export const HtmlEditorControls: React.FC<{
73+
createNew: boolean;
74+
setCreateNew: (value: boolean) => void;
75+
applyHtml: () => void;
76+
}> = ({ createNew, setCreateNew, applyHtml }) => {
9677
return (
97-
<div className="html-editor__container">
98-
<div className="html-editor__content">
99-
<Editor
100-
height="100%"
101-
language="html"
102-
defaultValue={editorValue}
103-
onChange={(value) => value && setEditorValue(value)}
104-
onMount={handleEditorMount}
105-
element={element}
106-
excalidrawAPI={excalidrawAPI}
107-
showLanguageSelector={false}
108-
className="html-editor__monaco-container"
109-
/>
110-
<div className="html-editor__controls">
111-
<label className="html-editor__label">
112-
<input
113-
type="checkbox"
114-
checked={createNew}
115-
onChange={(e) => setCreateNew(e.target.checked)}
116-
/>
117-
Create new element
118-
</label>
119-
<button className="html-editor__button" onClick={applyHtml}>
120-
Apply HTML
121-
</button>
122-
</div>
123-
</div>
124-
</div>
78+
<>
79+
<label className="html-editor__label">
80+
<input
81+
type="checkbox"
82+
checked={createNew}
83+
onChange={(e) => setCreateNew(e.target.checked)}
84+
/>
85+
Create new element
86+
</label>
87+
<button className="html-editor__button" onClick={applyHtml}>
88+
Apply HTML
89+
</button>
90+
</>
12591
);
12692
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export { HtmlEditor } from './HtmlEditor';
1+
export { useHtmlEditor, HtmlEditorControls, defaultHtml } from './HtmlEditor';
22
export { default as Editor } from './Editor';
33
export { default as LanguageSelector } from './LanguageSelector';

0 commit comments

Comments
 (0)