|
| 1 | +import tkinter as tk |
| 2 | +from tkinter import ttk, messagebox, scrolledtext |
| 3 | +import pyperclip # Used for the copy-to-clipboard feature |
| 4 | +import datetime # For timestamp generation |
| 5 | + |
| 6 | +# --- JavaScript Generation Functions --- |
| 7 | + |
| 8 | +def generate_select_element(selector_type, selector_value): |
| 9 | + """Generates JS code to select an element.""" |
| 10 | + if not selector_value: |
| 11 | + return "// Error: Selector value cannot be empty." |
| 12 | + if selector_type == "ID": |
| 13 | + return f"const element = document.getElementById('{selector_value}');" |
| 14 | + elif selector_type == "Class Name (First)": |
| 15 | + # Selects the first element with the class |
| 16 | + return f"const element = document.querySelector('.{selector_value}');" |
| 17 | + elif selector_type == "Tag Name (First)": |
| 18 | + # Selects the first element with the tag name |
| 19 | + return f"const element = document.querySelector('{selector_value}');" |
| 20 | + else: |
| 21 | + return "// Error: Invalid selector type." |
| 22 | + |
| 23 | +def generate_event_listener(element_id, event_type, function_body=""): |
| 24 | + """Generates JS code for an event listener.""" |
| 25 | + if not element_id or not event_type: |
| 26 | + return "// Error: Element ID and Event Type are required." |
| 27 | + |
| 28 | + # Basic indentation for the function body placeholder |
| 29 | + indented_body = "\n // Your code here...\n" |
| 30 | + if function_body.strip(): |
| 31 | + indented_body = "\n" + "\n".join([" " + line for line in function_body.splitlines()]) + "\n" |
| 32 | + |
| 33 | + return f""" |
| 34 | +const element_{element_id} = document.getElementById('{element_id}'); |
| 35 | +
|
| 36 | +if (element_{element_id}) {{ |
| 37 | + element_{element_id}.addEventListener('{event_type}', function(event) {{ |
| 38 | + // 'event' object contains information about the event |
| 39 | + console.log('{event_type} triggered on element with ID: {element_id}');{indented_body} |
| 40 | + }}); |
| 41 | +}} else {{ |
| 42 | + console.error('Element with ID "{element_id}" not found.'); |
| 43 | +}} |
| 44 | +""" |
| 45 | + |
| 46 | +def generate_change_content(element_id, content_type, new_content): |
| 47 | + """Generates JS code to change element content.""" |
| 48 | + if not element_id: |
| 49 | + return "// Error: Element ID is required." |
| 50 | + prop = 'textContent' if content_type == 'Text Content' else 'innerHTML' |
| 51 | + # Escape backticks and backslashes in the content for template literals |
| 52 | + escaped_content = new_content.replace('\\', '\\\\').replace('`', '\\`') |
| 53 | + return f""" |
| 54 | +const element_{element_id}_content = document.getElementById('{element_id}'); |
| 55 | +
|
| 56 | +if (element_{element_id}_content) {{ |
| 57 | + element_{element_id}_content.{prop} = `{escaped_content}`; |
| 58 | +}} else {{ |
| 59 | + console.error('Element with ID "{element_id}" not found for changing content.'); |
| 60 | +}} |
| 61 | +""" |
| 62 | + |
| 63 | +def generate_change_style(element_id, style_prop, style_value): |
| 64 | + """Generates JS code to change element style.""" |
| 65 | + if not element_id or not style_prop or not style_value: |
| 66 | + return "// Error: Element ID, Style Property, and Style Value are required." |
| 67 | + # Convert CSS property from dash-case to camelCase if needed |
| 68 | + if '-' in style_prop: |
| 69 | + js_style_prop = ''.join(word.capitalize() for word in style_prop.split('-')) |
| 70 | + js_style_prop = js_style_prop[0].lower() + js_style_prop[1:] |
| 71 | + else: |
| 72 | + js_style_prop = style_prop |
| 73 | + return f""" |
| 74 | +const element_{element_id}_style = document.getElementById('{element_id}'); |
| 75 | +
|
| 76 | +if (element_{element_id}_style) {{ |
| 77 | + element_{element_id}_style.style.{js_style_prop} = '{style_value}'; |
| 78 | +}} else {{ |
| 79 | + console.error('Element with ID "{element_id}" not found for changing style.'); |
| 80 | +}} |
| 81 | +""" |
| 82 | + |
| 83 | +def generate_function_definition(func_name, params_str, function_body=""): |
| 84 | + """Generates a basic JS function definition.""" |
| 85 | + if not func_name: |
| 86 | + return "// Error: Function Name is required." |
| 87 | + # Basic validation for function name |
| 88 | + if not func_name.isidentifier() or func_name in ['function', 'var', 'let', 'const']: |
| 89 | + return f"// Error: '{func_name}' is not a valid JavaScript function name." |
| 90 | + |
| 91 | + # Basic indentation for the function body placeholder |
| 92 | + indented_body = "\n // Your function logic here...\n" |
| 93 | + if function_body.strip(): |
| 94 | + indented_body = "\n" + "\n".join([" " + line for line in function_body.splitlines()]) + "\n" |
| 95 | + |
| 96 | + return f""" |
| 97 | +function {func_name}({params_str}) {{ |
| 98 | + console.log('Function {func_name} called with parameters: ' + Array.from(arguments).join(', '));{indented_body} |
| 99 | + // return result; // Optional return statement |
| 100 | +}} |
| 101 | +
|
| 102 | +// Example usage (optional): |
| 103 | +// {func_name}(/* provide arguments here */); |
| 104 | +""" |
| 105 | + |
| 106 | +# --- GUI Application Class --- |
| 107 | + |
| 108 | +class JsGeneratorApp: |
| 109 | + def __init__(self, master): |
| 110 | + self.master = master |
| 111 | + master.title("JavaScript Snippet Generator") |
| 112 | + master.geometry("650x600") # Adjusted size |
| 113 | + |
| 114 | + # Style configuration |
| 115 | + style = ttk.Style() |
| 116 | + style.theme_use('clam') # Use a theme that looks better on Windows |
| 117 | + style.configure("TLabel", padding=5, font=('Helvetica', 10)) |
| 118 | + style.configure("TButton", padding=5, font=('Helvetica', 10)) |
| 119 | + style.configure("TEntry", padding=5, font=('Helvetica', 10)) |
| 120 | + style.configure("TCombobox", padding=5, font=('Helvetica', 10)) |
| 121 | + |
| 122 | + # --- Top Frame: Task Selection --- |
| 123 | + self.top_frame = ttk.Frame(master, padding="10") |
| 124 | + self.top_frame.pack(fill=tk.X) |
| 125 | + |
| 126 | + self.task_label = ttk.Label(self.top_frame, text="Select JavaScript Task:") |
| 127 | + self.task_label.pack(side=tk.LEFT, padx=(0, 10)) |
| 128 | + |
| 129 | + self.task_options = [ |
| 130 | + "Select Element", |
| 131 | + "Add Event Listener", |
| 132 | + "Change Element Content", |
| 133 | + "Change Element Style", |
| 134 | + "Define Basic Function" |
| 135 | + ] |
| 136 | + self.selected_task = tk.StringVar() |
| 137 | + self.task_combobox = ttk.Combobox(self.top_frame, textvariable=self.selected_task, |
| 138 | + values=self.task_options, state="readonly", width=30) |
| 139 | + self.task_combobox.pack(side=tk.LEFT, fill=tk.X, expand=True) |
| 140 | + self.task_combobox.current(0) # Default selection |
| 141 | + self.task_combobox.bind("<<ComboboxSelected>>", self.update_input_fields) |
| 142 | + |
| 143 | + # --- Middle Frame: Dynamic Inputs --- |
| 144 | + self.input_frame = ttk.Frame(master, padding="10") |
| 145 | + self.input_frame.pack(fill=tk.BOTH, expand=True) |
| 146 | + self.input_widgets = {} # To keep track of dynamic widgets |
| 147 | + |
| 148 | + # --- Bottom Frame: Output and Actions --- |
| 149 | + self.bottom_frame = ttk.Frame(master, padding="10") |
| 150 | + self.bottom_frame.pack(fill=tk.X) |
| 151 | + |
| 152 | + self.generate_button = ttk.Button(self.bottom_frame, text="Generate Code", command=self.generate_code) |
| 153 | + self.generate_button.pack(side=tk.LEFT, padx=(0, 10)) |
| 154 | + |
| 155 | + self.copy_button = ttk.Button(self.bottom_frame, text="Copy to Clipboard", command=self.copy_to_clipboard) |
| 156 | + self.copy_button.pack(side=tk.LEFT) |
| 157 | + |
| 158 | + # --- Output Text Area --- |
| 159 | + self.output_label = ttk.Label(master, text="Generated JavaScript:", padding=(10, 5, 10, 0)) |
| 160 | + self.output_label.pack(fill=tk.X) |
| 161 | + |
| 162 | + self.output_text = scrolledtext.ScrolledText(master, height=15, width=70, wrap=tk.WORD, |
| 163 | + font=("Courier New", 10), relief=tk.SUNKEN, borderwidth=1) |
| 164 | + self.output_text.pack(padx=10, pady=(0, 10), fill=tk.BOTH, expand=True) |
| 165 | + self.output_text.insert(tk.END, "// Select a task and provide inputs to generate JavaScript code.") |
| 166 | + self.output_text.configure(state='disabled') # Make read-only initially |
| 167 | + |
| 168 | + # Initial setup |
| 169 | + self.update_input_fields() # Populate inputs for the default task |
| 170 | + |
| 171 | + def clear_input_frame(self): |
| 172 | + """Removes all widgets from the input frame.""" |
| 173 | + for widget in self.input_frame.winfo_children(): |
| 174 | + widget.destroy() |
| 175 | + self.input_widgets = {} |
| 176 | + |
| 177 | + def add_input_field(self, label_text, key, widget_type='entry', options=None, text_height=3): |
| 178 | + """Adds a labeled input field to the input frame.""" |
| 179 | + frame = ttk.Frame(self.input_frame) |
| 180 | + frame.pack(fill=tk.X, pady=2) |
| 181 | + |
| 182 | + label = ttk.Label(frame, text=label_text, width=18, anchor="w") |
| 183 | + label.pack(side=tk.LEFT, padx=(0, 5)) |
| 184 | + |
| 185 | + if widget_type == 'entry': |
| 186 | + widget = ttk.Entry(frame) |
| 187 | + widget.pack(side=tk.LEFT, fill=tk.X, expand=True) |
| 188 | + elif widget_type == 'combobox': |
| 189 | + var = tk.StringVar() |
| 190 | + # For combobox, set state to "normal" to allow free text input |
| 191 | + widget = ttk.Combobox(frame, textvariable=var, values=options, state="normal") |
| 192 | + if options: |
| 193 | + widget.current(0) |
| 194 | + widget.pack(side=tk.LEFT, fill=tk.X, expand=True) |
| 195 | + self.input_widgets[key + '_var'] = var # Store the variable too |
| 196 | + elif widget_type == 'textarea': |
| 197 | + widget = tk.Text(frame, height=text_height, width=40, relief=tk.SUNKEN, borderwidth=1, font=('Helvetica', 10)) |
| 198 | + widget.pack(side=tk.LEFT, fill=tk.X, expand=True) |
| 199 | + |
| 200 | + self.input_widgets[key] = widget |
| 201 | + |
| 202 | + def update_input_fields(self, event=None): |
| 203 | + """Updates the input fields based on the selected task.""" |
| 204 | + self.clear_input_frame() |
| 205 | + task = self.selected_task.get() |
| 206 | + |
| 207 | + if task == "Select Element": |
| 208 | + self.add_input_field("Selector Type:", "selector_type", widget_type='combobox', |
| 209 | + options=["ID", "Class Name (First)", "Tag Name (First)"]) |
| 210 | + self.add_input_field("Selector Value:", "selector_value") |
| 211 | + elif task == "Add Event Listener": |
| 212 | + self.add_input_field("Element ID:", "element_id") |
| 213 | + self.add_input_field("Event Type:", "event_type", widget_type='combobox', |
| 214 | + options=["click", "mouseover", "mouseout", "keydown", "submit", "change", "load"]) |
| 215 | + self.add_input_field("Function Body (Optional):", "function_body", widget_type='textarea', text_height=4) |
| 216 | + elif task == "Change Element Content": |
| 217 | + self.add_input_field("Element ID:", "element_id") |
| 218 | + self.add_input_field("Content Type:", "content_type", widget_type='combobox', |
| 219 | + options=["Text Content", "HTML Content"]) |
| 220 | + self.add_input_field("New Content:", "new_content", widget_type='textarea', text_height=4) |
| 221 | + elif task == "Change Element Style": |
| 222 | + self.add_input_field("Element ID:", "element_id") |
| 223 | + self.add_input_field("CSS Property:", "style_prop", widget_type='combobox', |
| 224 | + options=['color', 'background-color', 'font-size', 'display', 'border', 'padding', 'margin']) |
| 225 | + self.add_input_field("Style Value:", "style_value") |
| 226 | + elif task == "Define Basic Function": |
| 227 | + self.add_input_field("Function Name:", "func_name") |
| 228 | + self.add_input_field("Parameters (comma-sep):", "params_str") |
| 229 | + self.add_input_field("Function Body (Optional):", "function_body", widget_type='textarea', text_height=5) |
| 230 | + |
| 231 | + # Clear previous output |
| 232 | + self.output_text.configure(state='normal') |
| 233 | + self.output_text.delete('1.0', tk.END) |
| 234 | + self.output_text.insert(tk.END, "// Provide inputs above and click 'Generate Code'.") |
| 235 | + self.output_text.configure(state='disabled') |
| 236 | + |
| 237 | + def get_input_value(self, key, widget_type='entry'): |
| 238 | + """Safely gets the value from an input widget.""" |
| 239 | + widget = self.input_widgets.get(key) |
| 240 | + if not widget: |
| 241 | + return None |
| 242 | + |
| 243 | + if widget_type == 'entry': |
| 244 | + return widget.get().strip() |
| 245 | + elif widget_type == 'combobox': |
| 246 | + var = self.input_widgets.get(key + '_var') |
| 247 | + return var.get() if var else None |
| 248 | + elif widget_type == 'textarea': |
| 249 | + return widget.get("1.0", tk.END).strip() |
| 250 | + |
| 251 | + return None |
| 252 | + |
| 253 | + def generate_code(self): |
| 254 | + """Generates the JavaScript code based on inputs.""" |
| 255 | + task = self.selected_task.get() |
| 256 | + js_code = f"// Code generation for task: {task}\n// Timestamp: {datetime.datetime.now()}\n\n" |
| 257 | + |
| 258 | + try: |
| 259 | + if task == "Select Element": |
| 260 | + selector_type = self.get_input_value("selector_type", 'combobox') |
| 261 | + selector_value = self.get_input_value("selector_value") |
| 262 | + js_code += generate_select_element(selector_type, selector_value) |
| 263 | + elif task == "Add Event Listener": |
| 264 | + element_id = self.get_input_value("element_id") |
| 265 | + event_type = self.get_input_value("event_type", 'combobox') |
| 266 | + function_body = self.get_input_value("function_body", 'textarea') |
| 267 | + js_code += generate_event_listener(element_id, event_type, function_body) |
| 268 | + elif task == "Change Element Content": |
| 269 | + element_id = self.get_input_value("element_id") |
| 270 | + content_type = self.get_input_value("content_type", 'combobox') |
| 271 | + new_content = self.get_input_value("new_content", 'textarea') |
| 272 | + js_code += generate_change_content(element_id, content_type, new_content) |
| 273 | + elif task == "Change Element Style": |
| 274 | + element_id = self.get_input_value("element_id") |
| 275 | + style_prop = self.get_input_value("style_prop") |
| 276 | + style_value = self.get_input_value("style_value") |
| 277 | + js_code += generate_change_style(element_id, style_prop, style_value) |
| 278 | + elif task == "Define Basic Function": |
| 279 | + func_name = self.get_input_value("func_name") |
| 280 | + params_str = self.get_input_value("params_str") |
| 281 | + function_body = self.get_input_value("function_body", 'textarea') |
| 282 | + js_code += generate_function_definition(func_name, params_str, function_body) |
| 283 | + else: |
| 284 | + js_code += "// Task not yet implemented." |
| 285 | + except Exception as e: |
| 286 | + js_code += f"\n// An error occurred during code generation: {e}" |
| 287 | + messagebox.showerror("Generation Error", f"An unexpected error occurred:\n{e}") |
| 288 | + |
| 289 | + self.output_text.configure(state='normal') |
| 290 | + self.output_text.delete('1.0', tk.END) |
| 291 | + self.output_text.insert('1.0', js_code) |
| 292 | + self.output_text.configure(state='disabled') |
| 293 | + |
| 294 | + def copy_to_clipboard(self): |
| 295 | + """Copies the generated code to the clipboard.""" |
| 296 | + code = self.output_text.get('1.0', tk.END).strip() |
| 297 | + if code and not code.startswith("// Provide inputs"): |
| 298 | + try: |
| 299 | + pyperclip.copy(code) |
| 300 | + messagebox.showinfo("Copied", "JavaScript code copied to clipboard!") |
| 301 | + except pyperclip.PyperclipException as e: |
| 302 | + messagebox.showwarning("Copy Error", f"Could not copy to clipboard:\n{e}\n\nYou may need to install the 'pyperclip' library:\n pip install pyperclip") |
| 303 | + except Exception as e: |
| 304 | + messagebox.showerror("Error", f"An unexpected error occurred during copy:\n{e}") |
| 305 | + else: |
| 306 | + messagebox.showwarning("Nothing to Copy", "Generate some code first.") |
| 307 | + |
| 308 | +# --- Main Execution --- |
| 309 | +if __name__ == "__main__": |
| 310 | + root = tk.Tk() |
| 311 | + app = JsGeneratorApp(root) |
| 312 | + root.mainloop() |
0 commit comments