import tkinter as tk from tkinter import ttk, filedialog, messagebox import os import json import shutil class Web3Builder: def __init__(self, root): self.root = root self.root.title("Web3 Site Builder") self.root.geometry("1200x700") self.components = [] self.selected_component = None self.drag_data = {"x": 0, "y": 0, "item": None} self.bg_color = "#1a1a2e" self.sidebar_color = "#16213e" self.text_color = "#ffffff" self.setup_ui() def setup_ui(self): style = ttk.Style() style.theme_use('clam') main_container = tk.Frame(self.root, bg=self.bg_color) main_container.pack(fill=tk.BOTH, expand=True) sidebar = tk.Frame(main_container, width=200, bg=self.sidebar_color) sidebar.pack(side=tk.LEFT, fill=tk.Y) sidebar.pack_propagate(False) tk.Label(sidebar, text="Web3 Components", bg=self.sidebar_color, fg=self.text_color, font=('Arial', 12, 'bold')).pack(pady=20) components = [ ("Wallet Connect", "🦊", "wallet_connect"), ("NFT Gallery", "🖼️", "nft_gallery"), ("Token Balance", "💰", "token_balance"), ("Swap Interface", "🔄", "swap_interface"), ("DAO Voting", "🗳️", "dao_voting"), ("Header", "📝", "header"), ("Paragraph", "📄", "paragraph"), ("Button", "🔘", "button"), ("Image", "🖼️", "image"), ("Footer", "👇", "footer") ] for name, icon, tag in components: btn = tk.Button(sidebar, text=f"{icon} {name}", bg="#0f3460", fg=self.text_color, relief=tk.FLAT, font=('Arial', 10), command=lambda t=tag: self.add_component(t)) btn.pack(pady=5, padx=10, fill=tk.X) self.properties_panel = tk.Frame(main_container, width=300, bg=self.sidebar_color) self.properties_panel.pack(side=tk.RIGHT, fill=tk.Y) self.properties_panel.pack_propagate(False) tk.Label(self.properties_panel, text="Properties", bg=self.sidebar_color, fg=self.text_color, font=('Arial', 12, 'bold')).pack(pady=20) self.canvas = tk.Canvas(main_container, bg=self.bg_color, highlightthickness=0) self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10) self.canvas.bind("", self.start_drag) self.canvas.bind("", self.drag) self.canvas.bind("", self.stop_drag) self.canvas.bind("", self.select_component) export_btn = tk.Button(sidebar, text="🚀 Export Website", bg="#4CAF50", fg="white", font=('Arial', 11, 'bold'), command=self.export_site) export_btn.pack(side=tk.BOTTOM, pady=20, padx=10, fill=tk.X) self.init_properties_panel() self.add_default_components() def add_default_components(self): header = { 'id': 'header_default', 'type': 'header', 'x': 50, 'y': 30, 'width': 800, 'height': 80, 'text': 'My Web3 Website', 'color': '#ffffff', 'bg_color': '#0f3460', 'font_size': 36 } self.components.append(header) self.draw_component(header) def add_component(self, component_type): component_id = f"{component_type}_{len(self.components) + 1}" defaults = { 'header': { 'type': 'header', 'text': 'New Header', 'color': '#ffffff', 'bg_color': '#0f3460', 'font_size': 24, 'width': 400, 'height': 60 }, 'paragraph': { 'type': 'paragraph', 'text': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'color': '#cccccc', 'font_size': 14, 'width': 400, 'height': 100 }, 'button': { 'type': 'button', 'text': 'Click Me', 'color': '#ffffff', 'bg_color': '#4CAF50', 'width': 120, 'height': 40 }, 'wallet_connect': { 'type': 'wallet_connect', 'text': 'Connect Wallet', 'bg_color': '#f6851b', 'width': 200, 'height': 50 }, 'nft_gallery': { 'type': 'nft_gallery', 'title': 'NFT Gallery', 'width': 600, 'height': 400 }, 'token_balance': { 'type': 'token_balance', 'token': 'ETH', 'width': 300, 'height': 150 }, 'swap_interface': { 'type': 'swap_interface', 'width': 400, 'height': 300 }, 'dao_voting': { 'type': 'dao_voting', 'title': 'DAO Proposal', 'width': 500, 'height': 200 }, 'image': { 'type': 'image', 'src': 'https://placehold.co/400x300', 'width': 400, 'height': 300 }, 'footer': { 'type': 'footer', 'text': '© 2024 Web3 Website', 'bg_color': '#0f3460', 'width': 800, 'height': 60 } } component = defaults.get(component_type, defaults['header']).copy() component['id'] = component_id component['x'] = 100 component['y'] = 150 + len(self.components) * 20 self.components.append(component) self.draw_component(component) def draw_component(self, component): x, y = component['x'], component['y'] width, height = component['width'], component['height'] comp_id = component['id'] rect = self.canvas.create_rectangle( x, y, x + width, y + height, fill=component.get('bg_color', '#2d4059'), outline='#4a69bd', width=2, tags=('component', comp_id) ) label_y = y + height + 15 self.canvas.create_text( x + width/2, label_y, text=component['type'].replace('_', ' ').title(), fill='#cccccc', font=('Arial', 9), tags=('label', comp_id) ) if component['type'] in ['header', 'button', 'wallet_connect']: self.canvas.create_text( x + width/2, y + height/2, text=component.get('text', ''), fill=component.get('color', '#ffffff'), font=('Arial', component.get('font_size', 14)), tags=('text', comp_id) ) elif component['type'] == 'paragraph': self.canvas.create_text( x + width/2, y + height/2, text=component.get('text', ''), fill=component.get('color', '#cccccc'), font=('Arial', component.get('font_size', 14)), width=width - 20, tags=('text', comp_id) ) icons = { 'wallet_connect': '🦊', 'nft_gallery': '🖼️', 'token_balance': '💰', 'swap_interface': '🔄', 'dao_voting': '🗳️' } if component['type'] in icons: self.canvas.create_text( x + 30, y + 25, text=icons[component['type']], font=('Arial', 24), tags=('icon', comp_id) ) self.canvas.create_text( x + width/2, y + height/2, text=component.get('title', component['type'].replace('_', ' ').title()), fill='#ffffff', font=('Arial', 16, 'bold'), tags=('title', comp_id) ) def start_drag(self, event): items = self.canvas.find_overlapping(event.x-1, event.y-1, event.x+1, event.y+1) for item in items: tags = self.canvas.gettags(item) for tag in tags: if tag in [comp['id'] for comp in self.components]: self.drag_data["item"] = tag self.drag_data["x"] = event.x self.drag_data["y"] = event.y self.selected_component = tag self.show_properties(tag) self.highlight_component(tag) return def drag(self, event): if self.drag_data["item"]: dx = event.x - self.drag_data["x"] dy = event.y - self.drag_data["y"] items = self.canvas.find_withtag(self.drag_data["item"]) for item in items: self.canvas.move(item, dx, dy) self.drag_data["x"] = event.x self.drag_data["y"] = event.y def stop_drag(self, event): if self.drag_data["item"]: for comp in self.components: if comp['id'] == self.drag_data["item"]: items = self.canvas.find_withtag(self.drag_data["item"]) for item in items: if self.canvas.type(item) == 'rectangle': coords = self.canvas.coords(item) comp['x'] = coords[0] comp['y'] = coords[1] comp['width'] = coords[2] - coords[0] comp['height'] = coords[3] - coords[1] break self.drag_data["item"] = None def select_component(self, event): items = self.canvas.find_overlapping(event.x-1, event.y-1, event.x+1, event.y+1) for item in items: tags = self.canvas.gettags(item) for tag in tags: if tag in [comp['id'] for comp in self.components]: self.selected_component = tag self.show_properties(tag) self.highlight_component(tag) return self.selected_component = None self.clear_highlights() def highlight_component(self, component_id): self.clear_highlights() items = self.canvas.find_withtag(component_id) for item in items: if self.canvas.type(item) == 'rectangle': self.canvas.itemconfig(item, outline='#00ff00', width=3) def clear_highlights(self): for item in self.canvas.find_withtag('component'): self.canvas.itemconfig(item, outline='#4a69bd', width=2) def init_properties_panel(self): self.property_widgets = {} properties_frame = tk.Frame(self.properties_panel, bg=self.sidebar_color) properties_frame.pack(fill=tk.X, padx=10, pady=5) tk.Label(properties_frame, text="Text:", bg=self.sidebar_color, fg=self.text_color).grid(row=0, column=0, sticky='w', pady=5) self.property_widgets['text'] = tk.Entry(properties_frame, width=25) self.property_widgets['text'].grid(row=0, column=1, pady=5) tk.Label(properties_frame, text="Width:", bg=self.sidebar_color, fg=self.text_color).grid(row=1, column=0, sticky='w', pady=5) self.property_widgets['width'] = tk.Scale(properties_frame, from_=50, to=800, orient=tk.HORIZONTAL, length=180) self.property_widgets['width'].grid(row=1, column=1, pady=5) tk.Label(properties_frame, text="Height:", bg=self.sidebar_color, fg=self.text_color).grid(row=2, column=0, sticky='w', pady=5) self.property_widgets['height'] = tk.Scale(properties_frame, from_=30, to=600, orient=tk.HORIZONTAL, length=180) self.property_widgets['height'].grid(row=2, column=1, pady=5) tk.Label(properties_frame, text="Text Color:", bg=self.sidebar_color, fg=self.text_color).grid(row=3, column=0, sticky='w', pady=5) self.property_widgets['color'] = tk.Entry(properties_frame, width=25) self.property_widgets['color'].grid(row=3, column=1, pady=5) self.property_widgets['color'].insert(0, "#ffffff") tk.Label(properties_frame, text="BG Color:", bg=self.sidebar_color, fg=self.text_color).grid(row=4, column=0, sticky='w', pady=5) self.property_widgets['bg_color'] = tk.Entry(properties_frame, width=25) self.property_widgets['bg_color'].grid(row=4, column=1, pady=5) self.property_widgets['bg_color'].insert(0, "#0f3460") update_btn = tk.Button(properties_frame, text="Update Properties", command=self.update_properties, bg="#4CAF50", fg="white") update_btn.grid(row=5, column=0, columnspan=2, pady=20) delete_btn = tk.Button(properties_frame, text="Delete Component", command=self.delete_component, bg="#f44336", fg="white") delete_btn.grid(row=6, column=0, columnspan=2, pady=10) def show_properties(self, component_id): component = next((c for c in self.components if c['id'] == component_id), None) if not component: return self.property_widgets['text'].delete(0, tk.END) self.property_widgets['text'].insert(0, component.get('text', '')) self.property_widgets['width'].set(component.get('width', 200)) self.property_widgets['height'].set(component.get('height', 50)) self.property_widgets['color'].delete(0, tk.END) self.property_widgets['color'].insert(0, component.get('color', '#ffffff')) self.property_widgets['bg_color'].delete(0, tk.END) self.property_widgets['bg_color'].insert(0, component.get('bg_color', '#0f3460')) def update_properties(self): if not self.selected_component: return for comp in self.components: if comp['id'] == self.selected_component: comp['text'] = self.property_widgets['text'].get() comp['width'] = self.property_widgets['width'].get() comp['height'] = self.property_widgets['height'].get() comp['color'] = self.property_widgets['color'].get() comp['bg_color'] = self.property_widgets['bg_color'].get() self.redraw_component(comp) break def delete_component(self): if not self.selected_component: return items = self.canvas.find_withtag(self.selected_component) for item in items: self.canvas.delete(item) self.components = [c for c in self.components if c['id'] != self.selected_component] self.selected_component = None def redraw_component(self, component): items = self.canvas.find_withtag(component['id']) for item in items: self.canvas.delete(item) self.draw_component(component) def export_site(self): export_dir = filedialog.askdirectory(title="Select Export Directory") if not export_dir: return site_dir = os.path.join(export_dir, "web3_site") os.makedirs(site_dir, exist_ok=True) html_content = self.generate_html() css_content = self.generate_css() js_content = self.generate_js() with open(os.path.join(site_dir, "index.html"), "w", encoding="utf-8") as f: f.write(html_content) with open(os.path.join(site_dir, "style.css"), "w", encoding="utf-8") as f: f.write(css_content) with open(os.path.join(site_dir, "script.js"), "w", encoding="utf-8") as f: f.write(js_content) self.copy_assets(site_dir) messagebox.showinfo("Export Complete", f"Website exported to:\n{site_dir}\n\nOpen index.html in your browser!") def generate_html(self): html = ''' Web3 Website
''' for comp in sorted(self.components, key=lambda x: (x['y'], x['x'])): if comp['type'] == 'header': html += f'''

{comp.get('text', 'Header')}

''' elif comp['type'] == 'paragraph': html += f'''

{comp.get('text', 'Paragraph text')}

''' elif comp['type'] == 'button': html += f'''
''' elif comp['type'] == 'wallet_connect': html += f'''
''' elif comp['type'] == 'nft_gallery': html += f'''

{comp.get('title', 'NFT Gallery')}

''' elif comp['type'] == 'token_balance': html += f'''

Token Balance

0.00 ETH
$0.00
''' elif comp['type'] == 'swap_interface': html += f'''

Swap Tokens

''' elif comp['type'] == 'dao_voting': html += f'''

{comp.get('title', 'DAO Proposal')}

Proposal #123: Upgrade protocol to v2.0

Connected wallets can vote
''' elif comp['type'] == 'image': html += f'''
Image
''' elif comp['type'] == 'footer': html += f'''

{comp.get('text', '© 2024 Web3 Website')}

''' html += '''
''' return html def generate_css(self): return '''* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); color: #ffffff; min-height: 100vh; padding: 20px; } .container { position: relative; min-height: 800px; margin: 0 auto; max-width: 1200px; } .component { position: absolute; border-radius: 10px; padding: 20px; transition: all 0.3s ease; } .component:hover { box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } header { background: linear-gradient(90deg, #0f3460 0%, #1a5f9c 100%); display: flex; align-items: center; justify-content: center; border-radius: 15px; } header h1 { font-size: 2.5rem; background: linear-gradient(45deg, #00dbde, #fc00ff); -webkit-background-clip: text; background-clip: text; color: transparent; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } .custom-btn { background: linear-gradient(45deg, #4CAF50, #2E7D32); color: white; border: none; padding: 12px 24px; border-radius: 25px; font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; width: 100%; height: 100%; } .custom-btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4); } .wallet-btn { background: linear-gradient(45deg, #f6851b, #e2761b); color: white; border: none; padding: 15px 30px; border-radius: 25px; font-size: 18px; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; height: 100%; transition: all 0.3s ease; } .wallet-btn:hover { transform: translateY(-2px); box-shadow: 0 5px 20px rgba(246, 133, 27, 0.4); } .wallet-info { margin-top: 15px; padding: 15px; background: rgba(255, 255, 255, 0.1); border-radius: 10px; display: none; } .nft-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 20px; margin-top: 20px; } .nft-item { background: rgba(255, 255, 255, 0.1); border-radius: 15px; padding: 15px; text-align: center; transition: transform 0.3s ease; } .nft-item:hover { transform: translateY(-5px); background: rgba(255, 255, 255, 0.15); } .nft-item img { width: 100%; border-radius: 10px; margin-bottom: 10px; } .token-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; padding: 25px; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; } .balance { font-size: 2.5rem; font-weight: bold; margin: 10px 0; } .balance-usd { font-size: 1.2rem; opacity: 0.9; } .swap-container { background: rgba(255, 255, 255, 0.1); border-radius: 20px; padding: 25px; height: 100%; } .swap-input { background: rgba(255, 255, 255, 0.05); border-radius: 15px; padding: 15px; margin: 15px 0; display: flex; align-items: center; } .swap-input input { background: transparent; border: none; color: white; font-size: 1.5rem; width: 70%; outline: none; } .swap-input select { background: rgba(255, 255, 255, 0.1); color: white; border: none; padding: 10px; border-radius: 10px; width: 30%; cursor: pointer; } .swap-arrow { text-align: center; margin: 10px 0; color: #4CAF50; font-size: 1.5rem; } .swap-btn { background: linear-gradient(45deg, #2196F3, #1976D2); color: white; border: none; padding: 15px; border-radius: 15px; font-size: 1.1rem; font-weight: bold; cursor: pointer; width: 100%; margin-top: 20px; transition: all 0.3s ease; } .swap-btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(33, 150, 243, 0.4); } .dao-proposal { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: 20px; padding: 25px; height: 100%; } .vote-buttons { display: flex; gap: 15px; margin: 20px 0; } .vote-btn { flex: 1; padding: 15px; border: none; border-radius: 15px; font-size: 1.1rem; font-weight: bold; cursor: pointer; transition: all 0.3s ease; } .vote-btn.yes { background: linear-gradient(45deg, #4CAF50, #2E7D32); color: white; } .vote-btn.no { background: linear-gradient(45deg, #f44336, #c62828); color: white; } .vote-btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } .vote-info { font-size: 0.9rem; opacity: 0.8; margin-top: 15px; } footer { background: linear-gradient(90deg, #0f3460 0%, #1a5f9c 100%); display: flex; align-items: center; justify-content: center; border-radius: 15px; } footer p { font-size: 1.1rem; opacity: 0.9; } @media (max-width: 768px) { .component { position: relative !important; left: 0 !important; top: 0 !important; width: 100% !important; margin-bottom: 20px; } .container { min-height: auto; } } ''' def generate_js(self): return '''let web3; let connectedAccount = null; async function connectWallet() { try { if (window.ethereum) { web3 = new Web3(window.ethereum); await window.ethereum.request({ method: 'eth_requestAccounts' }); const accounts = await web3.eth.getAccounts(); connectedAccount = accounts[0]; const walletInfo = document.getElementById('walletInfo'); const shortAddress = connectedAccount.substring(0, 6) + '...' + connectedAccount.substring(38); walletInfo.innerHTML = `

Connected: ${shortAddress}

`; walletInfo.style.display = 'block'; updateBalance(); alert('Wallet connected successfully!'); } else { alert('Please install MetaMask!'); } } catch (error) { console.error('Error connecting wallet:', error); alert('Error connecting wallet'); } } function disconnectWallet() { connectedAccount = null; document.getElementById('walletInfo').style.display = 'none'; document.getElementById('ethBalance').textContent = '0.00 ETH'; document.getElementById('ethUsd').textContent = '$0.00'; } async function updateBalance() { if (!connectedAccount || !web3) return; try { const balanceWei = await web3.eth.getBalance(connectedAccount); const balanceEth = web3.utils.fromWei(balanceWei, 'ether'); const formattedBalance = parseFloat(balanceEth).toFixed(4); const ethPrice = 3500; const usdValue = (parseFloat(formattedBalance) * ethPrice).toFixed(2); document.getElementById('ethBalance').textContent = `${formattedBalance} ETH`; document.getElementById('ethUsd').textContent = `$${usdValue} USD`; loadMockNFTs(); } catch (error) { console.error('Error updating balance:', error); } } function loadMockNFTs() { const nftGrid = document.getElementById('nftGrid'); if (!nftGrid) return; const mockNFTs = [ { name: 'CryptoPunk #1234', image: 'https://placehold.co/150x150/00ffaa/000000?text=CryptoPunk' }, { name: 'Bored Ape #5678', image: 'https://placehold.co/150x150/ffaa00/000000?text=Bored+Ape' }, { name: 'Art Blocks #9012', image: 'https://placehold.co/150x150/aa00ff/000000?text=Art+Blocks' }, { name: 'Doodle #3456', image: 'https://placehold.co/150x150/00aaff/000000?text=Doodle' } ]; nftGrid.innerHTML = mockNFTs.map(nft => `
${nft.name}

${nft.name}

`).join(''); } function simulateSwap() { const fromAmount = document.getElementById('fromAmount').value; const fromToken = document.getElementById('fromToken').value; const toToken = document.getElementById('toToken').value; if (!fromAmount || parseFloat(fromAmount) <= 0) { alert('Please enter a valid amount'); return; } const rates = { 'ETH_DAI': 3500, 'DAI_ETH': 0.0002857 }; const rateKey = `${fromToken}_${toToken}`; const rate = rates[rateKey] || 1; const toAmount = (parseFloat(fromAmount) * rate).toFixed(4); document.getElementById('toAmount').value = toAmount; alert(`Swap successful! ${fromAmount} ${fromToken} = ${toAmount} ${toToken}`); } function vote(choice) { if (!connectedAccount) { alert('Please connect your wallet to vote'); return; } const votes = { 'yes': Math.floor(Math.random() * 1000) + 700, 'no': Math.floor(Math.random() * 500) + 300 }; const totalVotes = votes.yes + votes.no; const yesPercent = Math.round((votes.yes / totalVotes) * 100); const noPercent = 100 - yesPercent; document.querySelector('.vote-btn.yes').textContent = `Yes (${yesPercent}%)`; document.querySelector('.vote-btn.no').textContent = `No (${noPercent}%)`; alert(`Vote recorded: ${choice.toUpperCase()}`); } window.addEventListener('DOMContentLoaded', () => { if (window.ethereum) { web3 = new Web3(window.ethereum); window.ethereum.on('accountsChanged', (accounts) => { if (accounts.length > 0) { connectedAccount = accounts[0]; updateBalance(); } else { disconnectWallet(); } }); window.ethereum.on('chainChanged', () => { window.location.reload(); }); } document.querySelectorAll('.custom-btn').forEach(btn => { btn.addEventListener('click', function() { alert('Button clicked!'); }); }); loadMockNFTs(); }); ''' def copy_assets(self, site_dir): assets_dir = os.path.join(site_dir, "assets") os.makedirs(assets_dir, exist_ok=True) favicon_path = os.path.join(assets_dir, "favicon.ico") readme = """# Web3 Website This website was generated using Web3 Site Builder. ## Features: - Wallet connection (MetaMask) - NFT gallery display - Token balance tracking - Token swap interface - DAO voting simulation ## How to Use: 1. Open index.html in your browser 2. Connect your wallet (MetaMask recommended) 3. Interact with Web3 components ## Note: This is a demo site. For production use, implement proper security and connect to real blockchain networks. """ with open(os.path.join(site_dir, "README.md"), "w", encoding="utf-8") as f: f.write(readme) def main(): root = tk.Tk() app = Web3Builder(root) root.mainloop() if __name__ == "__main__": main()