import panel as pn
import pandas as pd
from pathlib import Path
# Enable Panel extensions
pn.extension('tabulator')
class FileDownloadManager:
def __init__(self):
# Sample data
self.data = pd.DataFrame({
'id': [1, 2],
'filename': ['report1.py', 'data.py'], # Display names
'size': ['2.3 MB', '1.1 MB'],
'modified': ['2024-01-15', '2024-01-14'],
'file_path': ['test.py', 'test_process_fix.py'], # replace these
'download': ['🔽 Download', '🔽 Download'] # Simple text buttons
})
self.setup_tabulator()
def setup_tabulator(self):
"""Setup the Tabulator widget with click handling"""
self.tabulator = pn.widgets.Tabulator(
self.data,
formatters={
'download': {'type': 'html'} # Allow HTML rendering
},
titles={
'filename': 'File Name',
'size': 'Size',
'modified': 'Last Modified',
'download': 'Action'
},
widths={
'filename': 200,
'size': 100,
'modified': 120,
'download': 120
},
text_align={
'download': 'center',
'size': 'right'
},
pagination='remote',
page_size=10,
height=200,
disabled=True, # Prevent edits while allowing clicks
hidden_columns=['id', 'file_path'] # Hide utility columns
)
# Handle clicks on the download column
self.tabulator.on_click(self.handle_download_click)
def handle_download_click(self, event):
"""Handle clicks on the download column"""
if event.column == 'download':
row_data = self.tabulator.value.iloc[event.row]
file_id = row_data['id']
filename = row_data['filename']
file_path = row_data['file_path']
print(f"Downloading file: {filename} (ID: {file_id})")
# Get file content
content = self.get_file_content(file_path, filename)
# Create download using JavaScript
self.trigger_download(filename, content)
def get_file_content(self, file_path, filename):
"""Get file content, with fallback to dummy content"""
try:
# Try to read the actual file
if Path(file_path).exists():
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
print(f"Successfully read file: {file_path}")
return content
else:
print(f"File not found: {file_path}, using dummy content")
except Exception as e:
print(f"Error reading file {file_path}: {e}")
# Fallback to dummy content
return f"""# Content of {filename}
# This is a demo file
def main():
print("Hello from {filename}")
return "Demo content"
if __name__ == "__main__":
main()
"""
def trigger_download(self, filename, content):
"""Trigger file download using JavaScript"""
# Escape content for JavaScript
escaped_content = content.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r')
js_code = f"""
<script>
(function() {{
const content = "{escaped_content}";
const blob = new Blob([content], {{type: 'text/plain'}});
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '{filename}';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
console.log('Downloaded: {filename}');
}})();
</script>
"""
# Update the status and trigger download
self.status_pane.object = f"<div style='color: green; padding: 10px; background: #f0f8f0; border-radius: 4px;'>📁 Downloaded: <strong>{filename}</strong></div>" + js_code
def create_layout(self):
"""Create the main layout"""
# Status pane for download feedback
self.status_pane = pn.pane.HTML("Click a download button to download a file.", width=800)
return pn.Column(
pn.pane.Markdown("## File Manager with Download Buttons"),
pn.pane.Markdown("Click the **🔽 Download** button in any row to download the file."),
self.tabulator,
pn.pane.Markdown("### Status:"),
self.status_pane,
pn.pane.Markdown("""
### Features:
- Table editing disabled (`disabled=True`)
- Click-based download functionality
- Works with multiple clicks
- Real file reading with fallback content
"""),
width=800
)
# Usage
file_manager = FileDownloadManager()
layout = file_manager.create_layout()
layout.show()