Skip to content

Commit 5043fca

Browse files
committed
Initial commit
1 parent 09063dd commit 5043fca

File tree

3 files changed

+87
-62
lines changed

3 files changed

+87
-62
lines changed

Client-side/src/DocumentEditor.js

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,38 @@ import FileManager from './FileManager';
66
DocumentEditorContainerComponent.Inject(DocumentEditorToolbar);
77

88
const DocumentEditor = () => {
9-
const [maxDocId, setMaxDocId] = useState(null);
10-
const containerRef = useRef(null);
11-
const [selectedDocId, setSelectedDocId] = useState(null);
12-
const [selectedDocName, setSelectedDocName] = useState(null);
13-
const [showDialog, setShowDialog] = useState(false);
14-
const [showFileManager, setShowFileManager] = React.useState(true);
15-
const fileManagerRef = useRef(null);
16-
const inputRef = useRef(null);
17-
const contentChanged = useRef(false);
18-
const [errorMessage, setErrorMessage] = useState('');
19-
const randomDefaultName = 'New Document'
20-
21-
const newToolItem = {
22-
prefixIcon: "e-de-ctnr-new",
23-
tooltipText: "New",
24-
text: "New",
25-
id: "CreateNewDoc"
26-
};
27-
const openToolItem = {
28-
prefixIcon: "e-de-ctnr-open",
29-
tooltipText: "Open file manager",
30-
text: "Open",
31-
id: "OpenFileManager"
32-
};
33-
const downloadToolItem = {
34-
prefixIcon: "e-de-ctnr-download",
35-
tooltipText: "Download",
36-
text: "Download",
37-
id: "DownloadToLocal"
38-
};
9+
// State and refs
10+
const [maxDocId, setMaxDocId] = useState(null); // Track maximum doc ID for new document creation
11+
const containerRef = useRef(null); // Reference to DocumentEditor container
12+
const [selectedDocId, setSelectedDocId] = useState(null); // Selected document ID
13+
const [selectedDocName, setSelectedDocName] = useState(null); // Selected document name
14+
const [showDialog, setShowDialog] = useState(false); // Controls "New Document" dialog
15+
const [showFileManager, setShowFileManager] = useState(true); // Controls FileManager dialog visibility
16+
const fileManagerRef = useRef(null); // Reference to FileManager
17+
const inputRef = useRef(null); // Input reference for new document name
18+
const contentChanged = useRef(false); // Flag to track content changes
19+
const [errorMessage, setErrorMessage] = useState(''); // Error message for name validation
20+
const randomDefaultName = 'New_Document'; // Default document name
21+
22+
// Toolbar custom items
23+
const newToolItem = { prefixIcon: "e-de-ctnr-new", tooltipText: "New", text: "New", id: "CreateNewDoc" };
24+
const openToolItem = { prefixIcon: "e-de-ctnr-open", tooltipText: "Open file manager", text: "Open", id: "OpenFileManager" };
25+
const downloadToolItem = { prefixIcon: "e-de-ctnr-download", tooltipText: "Download", text: "Download", id: "DownloadToLocal" };
3926

4027
const hostUrl = "https://localhost:44305/";
41-
const toolbarItems = [newToolItem, openToolItem, downloadToolItem, 'Separator', 'Undo', 'Redo', 'Separator', 'Image', 'Table', 'Hyperlink', 'Bookmark', 'TableOfContents', 'Separator', 'Header', 'Footer', 'PageSetup', 'PageNumber', 'Break', 'InsertFootnote', 'InsertEndnote', 'Separator', 'Find', 'Separator', 'Comments', 'TrackChanges', 'Separator', 'LocalClipboard', 'RestrictEditing', 'Separator', 'FormFields', 'UpdateFields', 'ContentControl'];
4228

43-
const SaveDocument = () => {
29+
// Complete toolbar items list
30+
const toolbarItems = [
31+
newToolItem, openToolItem, downloadToolItem, 'Separator',
32+
'Undo', 'Redo', 'Separator', 'Image', 'Table', 'Hyperlink', 'Bookmark',
33+
'TableOfContents', 'Separator', 'Header', 'Footer', 'PageSetup', 'PageNumber',
34+
'Break', 'InsertFootnote', 'InsertEndnote', 'Separator', 'Find', 'Separator',
35+
'Comments', 'TrackChanges', 'Separator', 'LocalClipboard', 'RestrictEditing',
36+
'Separator', 'FormFields', 'UpdateFields', 'ContentControl'
37+
];
38+
39+
// Save document to the server as base64
40+
const autoSaveDocument = () => {
4441
const editor = containerRef.current?.documentEditor;
4542
editor.saveAsBlob('Docx').then((blob) => {
4643
const reader = new FileReader();
@@ -52,13 +49,8 @@ const DocumentEditor = () => {
5249
try {
5350
await fetch(hostUrl + `api/documents/${selectedDocId}/saveDocumentAsync`, {
5451
method: 'POST',
55-
headers: {
56-
'Content-Type': 'application/json'
57-
},
58-
body: JSON.stringify({
59-
Base64Content: base64Data,
60-
FileName: fileName
61-
})
52+
headers: { 'Content-Type': 'application/json' },
53+
body: JSON.stringify({ Base64Content: base64Data, FileName: fileName })
6254
});
6355
} catch (err) {
6456
console.error('Auto-save error:', err);
@@ -68,7 +60,8 @@ const DocumentEditor = () => {
6860
});
6961
};
7062

71-
const handleDialogSave = async () => {
63+
// Create new document logic from dialog
64+
const handleFileNamePromptOk = async () => {
7265
const documentName = inputRef.current?.value?.trim();
7366
if (!documentName) {
7467
setErrorMessage("Document name cannot be empty.");
@@ -86,7 +79,7 @@ const DocumentEditor = () => {
8679
return;
8780
}
8881

89-
// Proceed to save
82+
// Proceed with creation
9083
setErrorMessage("");
9184
setShowDialog(false);
9285
const newId = maxDocId + 1;
@@ -97,7 +90,7 @@ const DocumentEditor = () => {
9790
containerRef.current.documentEditor.openBlank();
9891
};
9992

100-
// Check if a document with a given name already exists on the Azure storage
93+
// Check if a document with a given name already exists on database
10194
const checkDocumentExistence = async (fileName) => {
10295
try {
10396
const response = await fetch(hostUrl + 'api/documents/CheckDocumentExistence', {
@@ -116,9 +109,11 @@ const DocumentEditor = () => {
116109
}
117110
};
118111

112+
// Handle toolbar item clicks
119113
const handleToolbarItemClick = (args) => {
120114
let documentName = containerRef.current.documentEditor.documentName;
121115
const baseDocName = documentName.replace(/\.[^/.]+$/, '');
116+
122117
switch (args.item.id) {
123118
case 'CreateNewDoc':
124119
setSelectedDocId(0);
@@ -146,26 +141,31 @@ const DocumentEditor = () => {
146141
}
147142
};
148143

144+
// Auto-save effect runs every second
149145
React.useEffect(() => {
150146
const intervalId = setInterval(() => {
151147
if (contentChanged.current) {
152-
SaveDocument();
148+
autoSaveDocument();
153149
contentChanged.current = false;
154150
}
155151
}, 1000);
156152
return () => clearInterval(intervalId);
157153
});
158154

155+
// Auto-focus on dialog input
159156
React.useEffect(() => {
160-
if (showDialog && inputRef.current) {
157+
if (showDialog && inputRef.current) {
161158
inputRef.current.focus();
162159
inputRef.current.select();
163160
}
164-
}, [showDialog ]);
161+
}, [showDialog]);
165162

163+
// Handle content changes
166164
const handleContentChange = () => {
167165
contentChanged.current = true;
168166
};
167+
168+
// Load document from FileManager selection
169169
const loadFileFromFileManager = (fileId, fileName) => {
170170
setSelectedDocId(fileId);
171171
containerRef.current.documentEditor.documentName = fileName;
@@ -174,6 +174,7 @@ const DocumentEditor = () => {
174174

175175
return (
176176
<div>
177+
{/* FileManager dialog for opening files */}
177178
<FileManager
178179
onFileSelect={loadFileFromFileManager}
179180
onFileManagerLoaded={(id) => setMaxDocId(id)}
@@ -182,9 +183,13 @@ const DocumentEditor = () => {
182183
visible={showFileManager}
183184
setVisible={setShowFileManager}
184185
/>
186+
187+
{/* Document name display */}
185188
<div id="document-header">
186189
{selectedDocName || (inputRef?.current?.value ? inputRef.current.value + '.docx' : '')}
187190
</div>
191+
192+
{/* Main document editor container */}
188193
<DocumentEditorContainerComponent
189194
ref={containerRef}
190195
height="calc(100vh - 65px)"
@@ -194,6 +199,8 @@ const DocumentEditor = () => {
194199
toolbarClick={handleToolbarItemClick}
195200
contentChange={handleContentChange}
196201
/>
202+
203+
{/* Dialog for creating new documents */}
197204
<DialogComponent
198205
visible={showDialog}
199206
header='New Document'
@@ -203,8 +210,8 @@ const DocumentEditor = () => {
203210
close={() => setShowDialog(false)}
204211
buttons={[
205212
{
206-
click: handleDialogSave,
207-
buttonModel: { content: 'Save', isPrimary: true }
213+
click: handleFileNamePromptOk ,
214+
buttonModel: { content: 'Ok', isPrimary: true }
208215
},
209216
{
210217
click: () => setShowDialog(false),

Client-side/src/FileManager.js

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,47 @@
11
import { FileManagerComponent, Inject, NavigationPane, DetailsView, Toolbar as FileManagerToolbar} from '@syncfusion/ej2-react-filemanager';
22
import { DialogComponent } from '@syncfusion/ej2-react-popups';
33

4+
// FileManager component props:
5+
// - onFileSelect: callback when a file is selected
6+
// - onFileManagerLoaded: callback on initial load with document count
7+
// - editorRef: reference to the Syncfusion DocumentEditor
8+
// - fileManagerRef: reference to this FileManager component
9+
// - visible: controls visibility of the dialog
10+
// - setVisible: function to toggle dialog visibility
411
const FileManager = ({ onFileSelect, onFileManagerLoaded, editorRef, fileManagerRef, visible, setVisible }) => {
12+
13+
// Called after FileManager successfully loads
514
const onSuccess = (args) => {
6-
const maxId = args.result.docCount;
7-
if (onFileManagerLoaded) onFileManagerLoaded(maxId);
15+
const maxId = args.result.docCount; // Retrieve document count
16+
if (onFileManagerLoaded) onFileManagerLoaded(maxId); // Call parent callback
817
};
18+
19+
// Base API URL
920
const hostUrl = 'https://localhost:44305/';
1021

22+
// Loads a document by its ID into the DocumentEditor
1123
const loadDocument = async (docId) => {
1224
try {
1325
const response = await fetch(hostUrl + `api/documents/${docId}/getDocumentAsync`);
1426
const data = await response.text();
1527
if (editorRef?.current?.documentEditor) {
16-
editorRef.current.documentEditor.open(data);
28+
editorRef.current.documentEditor.open(data); // Load content into the editor
1729
}
1830
} catch (err) {
1931
console.error('Error loading document', err);
2032
}
2133
};
2234

35+
// Triggered when a file is opened via double-click or context menu
2336
const handleFileOpen = (args) => {
2437
if (args.fileDetails.isFile) {
2538
const fileId = args.fileDetails.id;
2639
const fileName = args.fileDetails.name;
2740
if (typeof onFileSelect === 'function') {
28-
onFileSelect(fileId, fileName);
41+
onFileSelect(fileId, fileName); // Call parent callback with file info
2942
}
30-
loadDocument(fileId);
31-
setVisible(false); // Close dialog
43+
loadDocument(fileId); // Load the selected document
44+
setVisible(false); // Close the dialog
3245
}
3346
};
3447

@@ -42,27 +55,28 @@ const FileManager = ({ onFileSelect, onFileManagerLoaded, editorRef, fileManager
4255
showCloseIcon={true}
4356
closeOnEscape={true}
4457
target="body"
45-
beforeClose={() => setVisible(false)}
46-
onClose={() => setVisible(false)}
58+
beforeClose={() => setVisible(false)} // Set dialog visibility to false before closing
59+
onClose={() => setVisible(false)} // Ensure dialog state is updated on close
4760
>
4861
<FileManagerComponent
4962
id="azure-file-manager"
5063
ref={fileManagerRef}
5164
ajaxSettings={{
52-
url: hostUrl + 'api/documents',
53-
downloadUrl: hostUrl + 'api/documents/downloadAsync',
65+
url: hostUrl + 'api/documents', // API for file management
66+
downloadUrl: hostUrl + 'api/documents/downloadAsync', // API for file download
5467
}}
5568
toolbarSettings={{
5669
items: ['SortBy', 'Copy', 'Paste', 'Delete', 'Refresh', 'Download', 'Selection', 'View', 'Details'],
5770
}}
5871
contextMenuSettings={{
59-
file: ['Open', 'Copy', '|', 'Delete', 'Download', '|', 'Details'],
60-
layout: ['SortBy', 'View', 'Refresh', '|', 'Paste', '|', '|', 'Details', '|', 'SelectAll'],
72+
file: ['Open', 'Copy', '|', 'Delete', 'Download', '|', 'Details'], // Context menu options for files
73+
layout: ['SortBy', 'View', 'Refresh', '|', 'Paste', '|', '|', 'Details', '|', 'SelectAll'], // Layout menu options
6174
visible: true,
6275
}}
63-
fileOpen={handleFileOpen}
64-
success={onSuccess}
76+
fileOpen={handleFileOpen} // Callback for opening a file
77+
success={onSuccess} // Callback for successful data load
6578
>
79+
{/* Inject necessary services for FileManager */}
6680
<Inject services={[NavigationPane, DetailsView, FileManagerToolbar]} />
6781
</FileManagerComponent>
6882
</DialogComponent>

Client-side/src/index.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ body {
2121
-moz-osx-font-smoothing: grayscale;
2222
}
2323

24+
/* Code elements font - uses monospaced fonts for code readability */
2425
code {
2526
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
2627
monospace;
2728
}
2829

30+
/* Header styling for the document editor section */
2931
#document-header {
3032
height: 40px;
3133
padding: 8px;
@@ -40,13 +42,15 @@ code {
4042
align-items: center;
4143
}
4244

45+
/* Centering dialog component with fixed positioning */
4346
#dialog-component-sample {
4447
max-height: max-content !important;
4548
top: 50% !important;
4649
left: 50% !important;
4750
transform: translate(-50%, -50%) !important;
4851
}
4952

50-
#postgres-file{
53+
/* Fixed height for file manager container */
54+
#postgres-file {
5155
height: 800px !important;
5256
}

0 commit comments

Comments
 (0)