import gradio as gr
from fastapi import FastAPI, Request
from datetime import datetime
import json
import pandas as pd
import os
shortcuts_list = []
def save_shortcuts():
with open('shortcuts.json', 'w') as f:
json.dump(shortcuts_list, f, default=str)
def load_shortcuts():
global shortcuts_list
if os.path.exists('shortcuts.json'):
with open('shortcuts.json', 'r') as f:
shortcuts_list = json.load(f)
for shortcut in shortcuts_list:
shortcut['date_added'] = datetime.fromisoformat(shortcut['date_added'])
else:
shortcuts_list = []
def add_shortcut(name, tags, link, emojis, color_from, color_to, short_description):
new_shortcut = {
'name': name.strip(),
'tags': [tag.strip() for tag in tags.split('/') if tag.strip()],
'link': link.strip(),
'emojis': emojis.strip(),
'color_from': color_from,
'color_to': color_to,
'short_description': short_description.strip(),
'pinned': False,
'favorited': False,
'date_added': datetime.now().isoformat()
}
shortcuts_list.append(new_shortcut)
save_shortcuts()
# Return updated HTML
return update_display()
def get_shortcuts_dataframe(sort_by='Recently Added', search_query='', filter_tags=[]):
datafra = pd.DataFrame(shortcuts_list)
if datafra.empty:
return datafra
# Apply search filter
if search_query:
datafra = datafra[datafra['name'].str.contains(search_query, case=False)]
# Apply tag filters
if filter_tags:
datafra = datafra[datafra['tags'].apply(lambda tags: any(tag in tags for tag in filter_tags))]
# Sort the DataFrame
if sort_by == 'Alphabetical':
datafra = datafra.sort_values('name')
elif sort_by == 'Recently Added':
datafra = datafra.sort_values('date_added', ascending=False)
elif sort_by == 'Favorites':
datafra = datafra.sort_values('favorited', ascending=False)
# Reset index
datafra = datafra.reset_index(drop=True)
return datafra
def generate_cards_html(datafra):
if datafra.empty:
return "
No shortcuts available.
"
cards_html = ''
for idx, shortcut in datafra.iterrows():
# Determine pin icon based on state
if shortcut['pinned']:
pin_icon = "📌"
pin_style = "opacity: 1;"
else:
pin_icon = "📍"
pin_style = "opacity: 0.3; text-decoration: line-through;"
# Determine favorite icon based on state
if shortcut['favorited']:
favorite_icon = "❤️"
else:
favorite_icon = "🤍"
style = f"""
background: linear-gradient(135deg, {shortcut['color_from']}, {shortcut['color_to']});
padding: 20px;
border-radius: 10px;
position: relative;
color: white;
width: 300px;
margin: 10px;
cursor: pointer;
"""
labels_html = ""
if shortcut['pinned'] or shortcut['favorited']:
labels_html = f"""
{pin_icon if shortcut['pinned'] else ''}
{favorite_icon if shortcut['favorited'] else ''}
"""
card_html = f"""
{labels_html}
{shortcut['emojis']}
{shortcut['name']}
{shortcut['short_description']}
🗑️
"""
cards_html += card_html
cards_html += '
'
# Add JavaScript for handling hover and command/control key
return cards_html
def update_display(sort_by='Recently Added', search_query='', filter_tags=[]):
datafra = get_shortcuts_dataframe(sort_by, search_query, filter_tags)
return generate_cards_html(datafra)
def toggle_pin(index):
index = int(index)
if 0 <= index < len(shortcuts_list):
shortcuts_list[index]['pinned'] = not shortcuts_list[index]['pinned']
save_shortcuts()
# Return updated HTML
return update_display()
def toggle_favorite(index):
index = int(index)
if 0 <= index < len(shortcuts_list):
shortcuts_list[index]['favorited'] = not shortcuts_list[index]['favorited']
save_shortcuts()
# Return updated HTML
return update_display()
load_shortcuts()
js_code = f"""
function my_func() {{
window.isCmdOrCtrl = false;
window.addEventListener('keydown', function(e) {{
if (e.key === 'Meta' || e.key === 'Control') {{
window.isCmdOrCtrl = true;
window.showDeleteIcons();
}}
}});
window.addEventListener('keyup', function(e) {{
if (e.key === 'Meta' || e.key === 'Control') {{
window.isCmdOrCtrl = false;
window.hideDeleteIcons();
}}
}});
window.handleHover = function(event, idx) {{
if (window.isCmdOrCtrl) {{
document.getElementById('card-' + idx).style.display = 'block';
}}
}};
window.handleHoverOut = function(event, idx) {{
document.getElementById('card-'+ idx).style.display = 'none';
}};
window.showDeleteIcons = function() {{
const deleteIcons = document.querySelectorAll('[id^="delete-"]');
deleteIcons.forEach(icon => {{
icon.style.display = 'block';
}});
}};
window.hideDeleteIcons = function() {{
const deleteIcons = document.querySelectorAll('[id^="delete-"]');
deleteIcons.forEach(icon => {{
icon.style.display = 'none';
}});
}};
window.togglePin = function(idx) {{
// Implement the delete functionality, e.g., call an API endpoint
fetch('/toggle_pin', {{
method: 'POST',
headers: {{
'Content-Type': 'application/json'
}},
body: JSON.stringify({{ index: idx }})
}})
.then(response => response.json())
.then(data => {{
// Update the grid display
document.getElementById('grid_output').innerHTML = data.grid_html;
}});
}};
window.deleteShortcut = function(idx) {{
// Implement the delete functionality, e.g., call an API endpoint
fetch('/delete_shortcut', {{
method: 'POST',
headers: {{
'Content-Type': 'application/json'
}},
body: JSON.stringify({{ index: idx }})
}})
.then(response => response.json())
.then(data => {{
// Update the grid display
document.getElementById('grid_output').innerHTML = data.grid_html;
}});
}};
}}
"""
# Build the Gradio App
with gr.Blocks(theme="charbelgrower/Crystal",js=js_code) as demo:
gr.Markdown("## Website Shortcuts")
with gr.Row():
search_bar = gr.Textbox(label="Search")
sort_options = gr.Dropdown(choices=['Recently Added', 'Alphabetical', 'Favorites'], label="Sort By", value='Recently Added')
# Collect all unique tags
def get_all_tags():
return list(set(tag for shortcut in shortcuts_list for tag in shortcut['tags']))
filter_tags = gr.CheckboxGroup(choices=get_all_tags(), label="Filter Tags")
grid_output = gr.HTML(value=update_display(), elem_id="grid_output")
gr.Markdown("## Add a New Website Shortcut")
with gr.Row():
name = gr.Textbox(label="Name")
link = gr.Textbox(label="Link")
with gr.Row():
tags = gr.Textbox(label="Tags (separate levels with '/')")
emojis = gr.Textbox(label="Emojis")
color_from = gr.ColorPicker(label="Gradient Color From")
color_to = gr.ColorPicker(label="Gradient Color To")
short_description = gr.Textbox(label="Short Description")
add_button = gr.Button("Add Shortcut")
# Update display when filters change
def refresh_display(search_query, sort_by, filter_tags):
return update_display(sort_by, search_query, filter_tags)
search_bar.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output)
sort_options.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output)
filter_tags.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output)
# Add shortcut action
add_button.click(
fn=add_shortcut,
inputs=[name, tags, link, emojis, color_from, color_to, short_description],
outputs=grid_output
)
# Expose endpoints for toggle functions
api = FastAPI()
@api.post("/toggle_pin")
async def toggle_pin_route(request: Request):
data = await request.json()
index = data['index']
grid_html = toggle_pin(index)
return {'grid_html': grid_html}
@api.post("/delete_shortcut")
async def delete_shortcut_route(request: Request):
data = await request.json()
index = int(data['index'])
if 0 <= index < len(shortcuts_list):
del shortcuts_list[index]
save_shortcuts()
# Return updated HTML
grid_html = update_display()
return {'grid_html': grid_html}
@api.post("/toggle_favorite")
async def toggle_favorite_route(request: Request):
data = await request.json()
index = data['index']
grid_html = toggle_favorite(index)
return {'grid_html': grid_html}
# Mount the Gradio app onto the FastAPI app
app = gr.mount_gradio_app(api, demo, path="/")
demo.launch(ssr_mode=False)
if __name__ == "__main__":
import uvicorn
load_shortcuts()
uvicorn.run(app, host="127.0.0.1", port=7860)