Files
web3constructor/main.py
DobriyKola 4488e8c02c add main.py
состояние проекта такая чисто демочка
2025-12-31 14:51:29 +03:00

1006 lines
32 KiB
Python

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("<Button-1>", self.start_drag)
self.canvas.bind("<B1-Motion>", self.drag)
self.canvas.bind("<ButtonRelease-1>", self.stop_drag)
self.canvas.bind("<Button-3>", 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 = '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web3 Website</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/web3@1.7.0/dist/web3.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div class="container">
'''
for comp in sorted(self.components, key=lambda x: (x['y'], x['x'])):
if comp['type'] == 'header':
html += f'''
<header class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<h1>{comp.get('text', 'Header')}</h1>
</header>'''
elif comp['type'] == 'paragraph':
html += f'''
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<p>{comp.get('text', 'Paragraph text')}</p>
</div>'''
elif comp['type'] == 'button':
html += f'''
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<button class="custom-btn">{comp.get('text', 'Button')}</button>
</div>'''
elif comp['type'] == 'wallet_connect':
html += f'''
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<button class="wallet-btn" onclick="connectWallet()">
<i class="fab fa-ethereum"></i> {comp.get('text', 'Connect Wallet')}
</button>
<div id="walletInfo" class="wallet-info"></div>
</div>'''
elif comp['type'] == 'nft_gallery':
html += f'''
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<h2>{comp.get('title', 'NFT Gallery')}</h2>
<div class="nft-grid" id="nftGrid">
</div>
</div>'''
elif comp['type'] == 'token_balance':
html += f'''
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<div class="token-card">
<h3><i class="fab fa-ethereum"></i> Token Balance</h3>
<div class="balance" id="ethBalance">0.00 ETH</div>
<div class="balance-usd" id="ethUsd">$0.00</div>
</div>
</div>'''
elif comp['type'] == 'swap_interface':
html += f'''
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<div class="swap-container">
<h3><i class="fas fa-exchange-alt"></i> Swap Tokens</h3>
<div class="swap-input">
<input type="number" id="fromAmount" placeholder="0.0" value="0.1">
<select id="fromToken">
<option value="ETH">ETH</option>
<option value="DAI">DAI</option>
</select>
</div>
<div class="swap-arrow"><i class="fas fa-arrow-down"></i></div>
<div class="swap-input">
<input type="number" id="toAmount" placeholder="0.0" readonly>
<select id="toToken">
<option value="DAI">DAI</option>
<option value="ETH">ETH</option>
</select>
</div>
<button class="swap-btn" onclick="simulateSwap()">Swap</button>
</div>
</div>'''
elif comp['type'] == 'dao_voting':
html += f'''
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<div class="dao-proposal">
<h3><i class="fas fa-vote-yea"></i> {comp.get('title', 'DAO Proposal')}</h3>
<p>Proposal #123: Upgrade protocol to v2.0</p>
<div class="vote-buttons">
<button class="vote-btn yes" onclick="vote('yes')">Yes (68%)</button>
<button class="vote-btn no" onclick="vote('no')">No (32%)</button>
</div>
<div class="vote-info">Connected wallets can vote</div>
</div>
</div>'''
elif comp['type'] == 'image':
html += f'''
<div class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<img src="{comp.get('src', 'https://placehold.co/400x300')}" alt="Image" style="width: 100%; height: 100%; object-fit: cover;">
</div>'''
elif comp['type'] == 'footer':
html += f'''
<footer class="component {comp['id']}" style="left: {comp['x']}px; top: {comp['y']}px; width: {comp['width']}px; height: {comp['height']}px;">
<p>{comp.get('text', '© 2024 Web3 Website')}</p>
</footer>'''
html += '''
</div>
<script src="script.js"></script>
</body>
</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 = `
<p><strong>Connected:</strong> ${shortAddress}</p>
<button onclick="disconnectWallet()" style="background: #f44336; color: white; border: none; padding: 5px 10px; border-radius: 5px; margin-top: 5px;">Disconnect</button>
`;
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 => `
<div class="nft-item">
<img src="${nft.image}" alt="${nft.name}">
<p>${nft.name}</p>
</div>
`).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()