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)