@@ -3,12 +3,26 @@ import 'ace-builds/src-noconflict/mode-yaml';
33import 'ace-builds/src-noconflict/theme-github' ;
44import Yaml from 'yaml' ;
55import { Builder } from "./Builder.js" ;
6- import { ImageEmbed } from "./ImageEmbed.js" ;
6+ import { ImageEmbed } from "./ImageEmbed.js" ;
77
8+ /**
9+ * Manages the Ace editor instance, handles YAML parsing,
10+ * updates the SVG preview, and manages content loading/saving.
11+ */
812export class Editor {
9-
13+ /** @type { string } The key used for storing YAML content in localStorage. */
1014 STORAGE_KEY = 'pcb-diagram-yaml' ;
1115
16+ /** @type {ace.Ace.Editor } The Ace editor instance. */
17+ ace ;
18+ /** @type {HTMLElement } The HTML element where the SVG output is rendered. */
19+ output ;
20+
21+ /**
22+ * Creates an Editor instance.
23+ * @param {string|HTMLElement } editor - The ID or HTML element for the Ace editor container.
24+ * @param {HTMLElement } output - The HTML element to render the SVG output into.
25+ */
1226 constructor ( editor , output ) {
1327 this . output = output ;
1428
@@ -27,25 +41,30 @@ export class Editor {
2741 tabSize : 2
2842 } ) ;
2943
30- // Defer content loading and event binding
3144 this . initializeEditor ( ) ;
32-
33- // register download handler
3445 this . output . addEventListener ( 'click' , this . onDownloadClick ) ;
3546 }
3647
48+ /**
49+ * Asynchronously initializes the editor by loading content
50+ * and setting up event listeners.
51+ * @private
52+ */
3753 async initializeEditor ( ) {
38- // Load content first
3954 await this . loadInitialContent ( ) ;
4055
41- // Then register change handler and trigger initial processing
4256 this . ace . session . on ( 'change' , this . debounce ( this . onChange . bind ( this ) , 300 ) ) ;
43- // Check if editor has content before triggering initial processing
4457 if ( this . ace . getValue ( ) ) {
45- this . onChange ( ) ; // Trigger initial processing only if content loaded
58+ this . onChange ( ) ; // Trigger initial processing only if content loaded
4659 }
4760 }
4861
62+ /**
63+ * Loads the initial content into the editor.
64+ * It prioritizes loading from a 'load' URL parameter,
65+ * falling back to localStorage if the parameter is not present.
66+ * @private
67+ */
4968 async loadInitialContent ( ) {
5069 const urlParams = new URLSearchParams ( window . location . search ) ;
5170 const loadUrl = urlParams . get ( 'load' ) ;
@@ -60,27 +79,33 @@ export class Editor {
6079 this . ace . setValue ( yamlContent , - 1 ) ; // -1 moves cursor to the start
6180 console . log ( `Loaded content from: ${ loadUrl } ` ) ;
6281
63- // Remove the 'load' parameter from the URL without reloading
6482 const currentUrlParams = new URLSearchParams ( window . location . search ) ;
6583 currentUrlParams . delete ( 'load' ) ;
6684 const newRelativePathQuery = window . location . pathname + ( currentUrlParams . toString ( ) ? '?' + currentUrlParams . toString ( ) : '' ) ;
6785 window . history . replaceState ( { path : newRelativePathQuery } , '' , newRelativePathQuery ) ;
6886 } catch ( error ) {
6987 console . error ( 'Failed to load content from URL:' , loadUrl , error ) ;
70- // Display error to the user in the output div and editor
7188 const errorMessage = `# Failed to load content from: ${ loadUrl } \n# Error: ${ error . message } ` ;
7289 this . output . innerHTML = `<div class="error">Failed to load content from <a href="${ loadUrl } " target="_blank">${ loadUrl } </a>: ${ error . message } </div>` ;
7390 this . ace . setValue ( errorMessage , - 1 ) ;
7491 }
7592 } else {
76- // Load initial content from localStorage if no loadUrl
7793 const savedYaml = localStorage . getItem ( this . STORAGE_KEY ) ;
7894 if ( savedYaml ) {
7995 this . ace . setValue ( savedYaml , - 1 ) ;
8096 }
8197 }
8298 }
8399
100+ /**
101+ * Creates a debounced version of a function that delays invoking the function
102+ * until after `wait` milliseconds have elapsed since the last time the
103+ * debounced function was invoked.
104+ * @param {Function } func The function to debounce.
105+ * @param {number } wait The number of milliseconds to delay.
106+ * @returns {Function } The debounced function.
107+ * @private
108+ */
84109 debounce ( func , wait ) {
85110 let timeout ;
86111 return function ( ...args ) {
@@ -89,21 +114,25 @@ export class Editor {
89114 } ;
90115 }
91116
117+ /**
118+ * Handles the 'change' event from the Ace editor.
119+ * Parses the YAML content, saves valid content to localStorage,
120+ * triggers the SVG update, and displays parsing errors.
121+ * @private
122+ */
92123 onChange ( ) {
93124 const yaml = this . ace . getValue ( ) ;
94125 try {
95126 const parsed = Yaml . parse ( yaml , {
96127 prettyErrors : true ,
97128 } ) ;
98- // Save valid YAML to localStorage
99129 localStorage . setItem ( this . STORAGE_KEY , yaml ) ;
100130 this . onUpdate ( parsed ) ;
101131 this . clearErrors ( ) ;
102132 } catch ( e ) {
103- // Don't save invalid YAML
104133 if ( e . linePos ) {
105134 this . showError (
106- e . linePos [ 0 ] . line - 1 ,
135+ e . linePos [ 0 ] . line - 1 , // Ace lines are 0-indexed
107136 e . linePos [ 0 ] . col - 1 ,
108137 e . message ,
109138 e . name === 'YAMLWarning' ? 'error' : 'warning'
@@ -114,8 +143,15 @@ export class Editor {
114143 }
115144 }
116145
146+ /**
147+ * Called when the YAML content is successfully parsed.
148+ * Embeds images, builds the front and back SVG representations,
149+ * and updates the output element.
150+ * @param {object } setup - The parsed YAML configuration object.
151+ * @private
152+ */
117153 async onUpdate ( setup ) {
118- const embed = new ImageEmbed ( 'pinouts' ) ;
154+ const embed = new ImageEmbed ( 'pinouts' ) ; // Assuming 'pinouts' is the base path for local images
119155 setup = await embed . embedImages ( setup ) ;
120156
121157 const builder = new Builder ( setup ) ;
@@ -129,8 +165,15 @@ export class Editor {
129165 this . output . appendChild ( back ) ;
130166 }
131167
168+ /**
169+ * Handles click events on the output area to trigger SVG download.
170+ * @param {MouseEvent } e - The click event object.
171+ * @private
172+ */
132173 onDownloadClick ( e ) {
133174 const svg = e . target . closest ( 'svg' ) ;
175+ if ( ! svg ) return ; // Click was not on an SVG or its child
176+
134177 const serializer = new XMLSerializer ( ) ;
135178 const source = serializer . serializeToString ( svg ) ;
136179 const blob = new Blob ( [ source ] , { type : 'image/svg+xml;charset=utf-8' } ) ;
@@ -146,9 +189,15 @@ export class Editor {
146189 URL . revokeObjectURL ( url ) ;
147190 }
148191
192+ /**
193+ * Displays an error or warning annotation in the Ace editor gutter.
194+ * @param {number } lineNumber - The 0-indexed line number for the annotation.
195+ * @param {number } column - The 0-indexed column number for the annotation.
196+ * @param {string } message - The error or warning message text.
197+ * @param {boolean } isWarning - True if the annotation is a warning, false for an error.
198+ * @private
199+ */
149200 showError ( lineNumber , column , message , isWarning ) {
150- console . error ( 'ace' , lineNumber , column , message ) ;
151-
152201 this . ace . session . setAnnotations ( [ {
153202 row : lineNumber ,
154203 column : column ,
@@ -157,6 +206,10 @@ export class Editor {
157206 } ] ) ;
158207 }
159208
209+ /**
210+ * Clears all annotations (errors and warnings) from the Ace editor.
211+ * @private
212+ */
160213 clearErrors ( ) {
161214 this . ace . session . clearAnnotations ( ) ;
162215 }
0 commit comments