Spaces:
Sleeping
Sleeping
charbel-malo
commited on
Commit
β’
22741e9
1
Parent(s):
4b666fa
Upload folder using huggingface_hub
Browse files- app.py +85 -46
- shortcuts.json +1 -1
app.py
CHANGED
@@ -4,26 +4,46 @@ from datetime import datetime
|
|
4 |
import json
|
5 |
import pandas as pd
|
6 |
import os
|
|
|
|
|
|
|
7 |
|
8 |
shortcuts_list = []
|
9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
def save_shortcuts():
|
11 |
-
global shortcuts_list
|
12 |
with open('shortcuts.json', 'w') as f:
|
13 |
json.dump(shortcuts_list, f, default=str)
|
|
|
14 |
|
15 |
def load_shortcuts():
|
16 |
global shortcuts_list
|
17 |
if os.path.exists('shortcuts.json'):
|
18 |
-
with
|
19 |
-
|
|
|
|
|
20 |
for shortcut in shortcuts_list:
|
21 |
shortcut['date_added'] = datetime.fromisoformat(shortcut['date_added'])
|
22 |
else:
|
23 |
shortcuts_list = []
|
24 |
|
|
|
25 |
def add_shortcut(name, tags, link, emojis, color_from, color_to, short_description):
|
26 |
-
global shortcuts_list
|
27 |
new_shortcut = {
|
28 |
'name': name.strip(),
|
29 |
'tags': [tag.strip() for tag in tags.split('/') if tag.strip()],
|
@@ -41,8 +61,8 @@ def add_shortcut(name, tags, link, emojis, color_from, color_to, short_descripti
|
|
41 |
# Return updated HTML
|
42 |
return update_display()
|
43 |
|
|
|
44 |
def get_shortcuts_dataframe(sort_by='Recently Added', search_query='', filter_tags=[]):
|
45 |
-
global shortcuts_list
|
46 |
datafra = pd.DataFrame(shortcuts_list)
|
47 |
if datafra.empty:
|
48 |
return datafra
|
@@ -63,8 +83,8 @@ def get_shortcuts_dataframe(sort_by='Recently Added', search_query='', filter_ta
|
|
63 |
datafra = datafra.reset_index(drop=True)
|
64 |
return datafra
|
65 |
|
|
|
66 |
def generate_cards_html(datafra):
|
67 |
-
global shortcuts_list
|
68 |
if datafra.empty:
|
69 |
return "<p>No shortcuts available.</p>"
|
70 |
cards_html = '<div style="display: flex; flex-wrap: wrap;">'
|
@@ -102,51 +122,63 @@ def generate_cards_html(datafra):
|
|
102 |
</div>
|
103 |
"""
|
104 |
card_html = f"""
|
105 |
-
<div style="{style}"
|
|
|
|
|
106 |
{labels_html}
|
107 |
<div style='font-size: 40px; text-align: center;'>{shortcut['emojis']}</div>
|
108 |
<h3 style='text-align: center;'>{shortcut['name']}</h3>
|
109 |
<p style='text-align: center;'>{shortcut['short_description']}</p>
|
110 |
<div style='text-align: center;'>
|
111 |
-
<button style="background: none; border: none; cursor: pointer; {pin_style}" onclick="togglePin({idx})">{pin_icon}</button>
|
112 |
-
<button style="background: none; border: none; cursor: pointer;" onclick="toggleFavorite({idx})">{favorite_icon}</button>
|
113 |
<button onclick="window.open('{shortcut['link']}', '_blank')">π Open</button>
|
114 |
</div>
|
115 |
-
<div id="delete-{idx}" style="display: none; position: absolute; top: 10px; left: 10px; cursor: pointer;" onclick="deleteShortcut({idx})">
|
116 |
ποΈ
|
117 |
</div>
|
118 |
</div>
|
119 |
"""
|
120 |
cards_html += card_html
|
121 |
cards_html += '</div>'
|
122 |
-
|
123 |
-
# Add JavaScript for handling hover and command/control key
|
124 |
-
|
125 |
return cards_html
|
126 |
|
|
|
127 |
def update_display(sort_by='Recently Added', search_query='', filter_tags=[]):
|
128 |
datafra = get_shortcuts_dataframe(sort_by, search_query, filter_tags)
|
129 |
return generate_cards_html(datafra)
|
130 |
|
|
|
131 |
def toggle_pin(index):
|
132 |
-
|
133 |
-
index
|
134 |
-
|
135 |
-
|
136 |
# Return updated HTML
|
137 |
return update_display()
|
138 |
|
|
|
139 |
def toggle_favorite(index):
|
140 |
-
|
141 |
-
index = int(index) - 1
|
142 |
if 0 <= index < len(shortcuts_list):
|
143 |
shortcuts_list[index]['favorited'] = not shortcuts_list[index]['favorited']
|
144 |
save_shortcuts()
|
145 |
# Return updated HTML
|
146 |
return update_display()
|
147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
load_shortcuts()
|
149 |
|
|
|
|
|
150 |
js_code = f"""
|
151 |
function my_func() {{
|
152 |
window.isCmdOrCtrl = false;
|
@@ -172,7 +204,7 @@ window.handleHover = function(event, idx) {{
|
|
172 |
}};
|
173 |
|
174 |
window.handleHoverOut = function(event, idx) {{
|
175 |
-
document.getElementById('card-'
|
176 |
}};
|
177 |
|
178 |
window.showDeleteIcons = function() {{
|
@@ -189,7 +221,6 @@ window.hideDeleteIcons = function() {{
|
|
189 |
}});
|
190 |
}};
|
191 |
|
192 |
-
|
193 |
window.togglePin = function(idx) {{
|
194 |
// Implement the delete functionality, e.g., call an API endpoint
|
195 |
fetch('/toggle_pin', {{
|
@@ -199,6 +230,7 @@ window.togglePin = function(idx) {{
|
|
199 |
}},
|
200 |
body: JSON.stringify({{ index: idx }})
|
201 |
}})
|
|
|
202 |
.then(data => {{
|
203 |
// Update the grid display
|
204 |
document.getElementById('grid_output').innerHTML = data.grid_html;
|
@@ -222,8 +254,9 @@ window.deleteShortcut = function(idx) {{
|
|
222 |
}};
|
223 |
}}
|
224 |
"""
|
|
|
225 |
# Build the Gradio App
|
226 |
-
with gr.Blocks(theme="charbelgrower/Crystal",js=js_code) as demo:
|
227 |
|
228 |
gr.Markdown("## Website Shortcuts")
|
229 |
with gr.Row():
|
@@ -251,8 +284,10 @@ with gr.Blocks(theme="charbelgrower/Crystal",js=js_code) as demo:
|
|
251 |
add_button = gr.Button("Add Shortcut")
|
252 |
|
253 |
# Update display when filters change
|
254 |
-
def refresh_display(search_query, sort_by, filter_tags):
|
255 |
-
|
|
|
|
|
256 |
|
257 |
search_bar.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output)
|
258 |
sort_options.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output)
|
@@ -264,39 +299,43 @@ with gr.Blocks(theme="charbelgrower/Crystal",js=js_code) as demo:
|
|
264 |
inputs=[name, tags, link, emojis, color_from, color_to, short_description],
|
265 |
outputs=grid_output
|
266 |
)
|
267 |
-
|
|
|
268 |
api = FastAPI()
|
269 |
|
270 |
-
@api.post(
|
271 |
-
async def
|
272 |
data = await request.json()
|
273 |
-
index = data
|
274 |
-
grid_html =
|
275 |
return {'grid_html': grid_html}
|
276 |
|
277 |
-
@api.post(
|
278 |
-
async def
|
279 |
-
global shortcuts_list
|
280 |
data = await request.json()
|
281 |
-
index =
|
282 |
-
|
283 |
-
del shortcuts_list[index]
|
284 |
-
save_shortcuts()
|
285 |
-
# Return updated HTML
|
286 |
-
grid_html = update_display()
|
287 |
return {'grid_html': grid_html}
|
288 |
|
289 |
-
@api.post(
|
290 |
-
async def
|
291 |
data = await request.json()
|
292 |
-
index = data
|
293 |
grid_html = toggle_favorite(index)
|
294 |
return {'grid_html': grid_html}
|
295 |
|
296 |
-
|
297 |
-
|
298 |
-
demo.launch(ssr_mode=False)
|
299 |
if __name__ == "__main__":
|
300 |
import uvicorn
|
301 |
-
load_shortcuts
|
302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
import json
|
5 |
import pandas as pd
|
6 |
import os
|
7 |
+
from watchdog.observers import Observer
|
8 |
+
from watchdog.events import FileSystemEventHandler
|
9 |
+
from functools import wraps
|
10 |
|
11 |
shortcuts_list = []
|
12 |
|
13 |
+
# Add file change handler
|
14 |
+
class JSONFileHandler(FileSystemEventHandler):
|
15 |
+
def on_modified(self, event):
|
16 |
+
if event.src_path.endswith('shortcuts.json'):
|
17 |
+
load_shortcuts()
|
18 |
+
|
19 |
+
# Add decorator for file operations
|
20 |
+
def ensure_fresh_data(func):
|
21 |
+
@wraps(func)
|
22 |
+
def wrapper(*args, **kwargs):
|
23 |
+
load_shortcuts() # Reload before each operation
|
24 |
+
result = func(*args, **kwargs)
|
25 |
+
return result
|
26 |
+
return wrapper
|
27 |
+
|
28 |
def save_shortcuts():
|
|
|
29 |
with open('shortcuts.json', 'w') as f:
|
30 |
json.dump(shortcuts_list, f, default=str)
|
31 |
+
load_shortcuts()
|
32 |
|
33 |
def load_shortcuts():
|
34 |
global shortcuts_list
|
35 |
if os.path.exists('shortcuts.json'):
|
36 |
+
# Open the file in binary mode with no buffering to avoid cached data
|
37 |
+
with open('shortcuts.json', 'rb', buffering=0) as f:
|
38 |
+
data = f.read()
|
39 |
+
shortcuts_list = json.loads(data.decode('utf-8'))
|
40 |
for shortcut in shortcuts_list:
|
41 |
shortcut['date_added'] = datetime.fromisoformat(shortcut['date_added'])
|
42 |
else:
|
43 |
shortcuts_list = []
|
44 |
|
45 |
+
@ensure_fresh_data
|
46 |
def add_shortcut(name, tags, link, emojis, color_from, color_to, short_description):
|
|
|
47 |
new_shortcut = {
|
48 |
'name': name.strip(),
|
49 |
'tags': [tag.strip() for tag in tags.split('/') if tag.strip()],
|
|
|
61 |
# Return updated HTML
|
62 |
return update_display()
|
63 |
|
64 |
+
@ensure_fresh_data
|
65 |
def get_shortcuts_dataframe(sort_by='Recently Added', search_query='', filter_tags=[]):
|
|
|
66 |
datafra = pd.DataFrame(shortcuts_list)
|
67 |
if datafra.empty:
|
68 |
return datafra
|
|
|
83 |
datafra = datafra.reset_index(drop=True)
|
84 |
return datafra
|
85 |
|
86 |
+
@ensure_fresh_data
|
87 |
def generate_cards_html(datafra):
|
|
|
88 |
if datafra.empty:
|
89 |
return "<p>No shortcuts available.</p>"
|
90 |
cards_html = '<div style="display: flex; flex-wrap: wrap;">'
|
|
|
122 |
</div>
|
123 |
"""
|
124 |
card_html = f"""
|
125 |
+
<div style="{style}"
|
126 |
+
onmouseover="window.handleHover(event, {idx})"
|
127 |
+
onmouseout="window.handleHoverOut(event, {idx})">
|
128 |
{labels_html}
|
129 |
<div style='font-size: 40px; text-align: center;'>{shortcut['emojis']}</div>
|
130 |
<h3 style='text-align: center;'>{shortcut['name']}</h3>
|
131 |
<p style='text-align: center;'>{shortcut['short_description']}</p>
|
132 |
<div style='text-align: center;'>
|
133 |
+
<button style="background: none; border: none; cursor: pointer; {pin_style}" onclick="window.togglePin({idx})">{pin_icon}</button>
|
134 |
+
<button style="background: none; border: none; cursor: pointer;" onclick="window.toggleFavorite({idx})">{favorite_icon}</button>
|
135 |
<button onclick="window.open('{shortcut['link']}', '_blank')">π Open</button>
|
136 |
</div>
|
137 |
+
<div id="delete-{idx}" style="display: none; position: absolute; top: 10px; left: 10px; cursor: pointer;" onclick="window.deleteShortcut({idx})">
|
138 |
ποΈ
|
139 |
</div>
|
140 |
</div>
|
141 |
"""
|
142 |
cards_html += card_html
|
143 |
cards_html += '</div>'
|
|
|
|
|
|
|
144 |
return cards_html
|
145 |
|
146 |
+
@ensure_fresh_data
|
147 |
def update_display(sort_by='Recently Added', search_query='', filter_tags=[]):
|
148 |
datafra = get_shortcuts_dataframe(sort_by, search_query, filter_tags)
|
149 |
return generate_cards_html(datafra)
|
150 |
|
151 |
+
@ensure_fresh_data
|
152 |
def toggle_pin(index):
|
153 |
+
index = int(index)
|
154 |
+
if 0 <= index < len(shortcuts_list):
|
155 |
+
shortcuts_list[index]['pinned'] = not shortcuts_list[index]['pinned']
|
156 |
+
save_shortcuts()
|
157 |
# Return updated HTML
|
158 |
return update_display()
|
159 |
|
160 |
+
@ensure_fresh_data
|
161 |
def toggle_favorite(index):
|
162 |
+
index = int(index)
|
|
|
163 |
if 0 <= index < len(shortcuts_list):
|
164 |
shortcuts_list[index]['favorited'] = not shortcuts_list[index]['favorited']
|
165 |
save_shortcuts()
|
166 |
# Return updated HTML
|
167 |
return update_display()
|
168 |
|
169 |
+
@ensure_fresh_data
|
170 |
+
def delete_shortcut(index):
|
171 |
+
index = int(index)
|
172 |
+
if 0 <= index < len(shortcuts_list):
|
173 |
+
del shortcuts_list[index]
|
174 |
+
save_shortcuts()
|
175 |
+
# Return updated HTML
|
176 |
+
return update_display()
|
177 |
+
|
178 |
load_shortcuts()
|
179 |
|
180 |
+
# JavaScript code attached to window object
|
181 |
+
|
182 |
js_code = f"""
|
183 |
function my_func() {{
|
184 |
window.isCmdOrCtrl = false;
|
|
|
204 |
}};
|
205 |
|
206 |
window.handleHoverOut = function(event, idx) {{
|
207 |
+
document.getElementById('card-'+ idx).style.display = 'none';
|
208 |
}};
|
209 |
|
210 |
window.showDeleteIcons = function() {{
|
|
|
221 |
}});
|
222 |
}};
|
223 |
|
|
|
224 |
window.togglePin = function(idx) {{
|
225 |
// Implement the delete functionality, e.g., call an API endpoint
|
226 |
fetch('/toggle_pin', {{
|
|
|
230 |
}},
|
231 |
body: JSON.stringify({{ index: idx }})
|
232 |
}})
|
233 |
+
.then(response => response.json())
|
234 |
.then(data => {{
|
235 |
// Update the grid display
|
236 |
document.getElementById('grid_output').innerHTML = data.grid_html;
|
|
|
254 |
}};
|
255 |
}}
|
256 |
"""
|
257 |
+
|
258 |
# Build the Gradio App
|
259 |
+
with gr.Blocks(theme="charbelgrower/Crystal", js=js_code) as demo:
|
260 |
|
261 |
gr.Markdown("## Website Shortcuts")
|
262 |
with gr.Row():
|
|
|
284 |
add_button = gr.Button("Add Shortcut")
|
285 |
|
286 |
# Update display when filters change
|
287 |
+
def refresh_display(search_query='', sort_by='Recently Added', filter_tags=[]):
|
288 |
+
grid_html = update_display(sort_by, search_query, filter_tags)
|
289 |
+
filter_tags_options = get_all_tags()
|
290 |
+
return grid_html, gr.update(choices=filter_tags_options)
|
291 |
|
292 |
search_bar.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output)
|
293 |
sort_options.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output)
|
|
|
299 |
inputs=[name, tags, link, emojis, color_from, color_to, short_description],
|
300 |
outputs=grid_output
|
301 |
)
|
302 |
+
|
303 |
+
# Expose endpoints for custom functions using FastAPI
|
304 |
api = FastAPI()
|
305 |
|
306 |
+
@api.post('/delete_shortcut')
|
307 |
+
async def delete_shortcut_endpoint(request: Request):
|
308 |
data = await request.json()
|
309 |
+
index = data.get('index')
|
310 |
+
grid_html = delete_shortcut(index)
|
311 |
return {'grid_html': grid_html}
|
312 |
|
313 |
+
@api.post('/toggle_pin')
|
314 |
+
async def toggle_pin_endpoint(request: Request):
|
|
|
315 |
data = await request.json()
|
316 |
+
index = data.get('index')
|
317 |
+
grid_html = toggle_pin(index)
|
|
|
|
|
|
|
|
|
318 |
return {'grid_html': grid_html}
|
319 |
|
320 |
+
@api.post('/toggle_favorite')
|
321 |
+
async def toggle_favorite_endpoint(request: Request):
|
322 |
data = await request.json()
|
323 |
+
index = data.get('index')
|
324 |
grid_html = toggle_favorite(index)
|
325 |
return {'grid_html': grid_html}
|
326 |
|
327 |
+
app = gr.mount_gradio_app(api, demo,"/")
|
328 |
+
# Initialize file watcher
|
|
|
329 |
if __name__ == "__main__":
|
330 |
import uvicorn
|
331 |
+
demo.unload(fn=load_shortcuts)
|
332 |
+
event_handler = JSONFileHandler()
|
333 |
+
observer = Observer()
|
334 |
+
observer.schedule(event_handler, path='shortcuts.json', recursive=False)
|
335 |
+
observer.start()
|
336 |
+
|
337 |
+
try:
|
338 |
+
uvicorn.run(app, host="127.0.0.1", port=7860)
|
339 |
+
finally:
|
340 |
+
observer.stop()
|
341 |
+
observer.join()
|
shortcuts.json
CHANGED
@@ -1 +1 @@
|
|
1 |
-
[{"name": "Charbel", "tags": ["Personal"], "link": "example.com", "emojis": "\ud83e\udd70", "color_from": "rgba(157.78125, 125.2561677631579, 125.2561677631579, 1)", "color_to": "rgba(255, 0, 0, 1)", "short_description": "ssadasd", "pinned":
|
|
|
1 |
+
[{"name": "Charbel", "tags": ["Personal"], "link": "example.com", "emojis": "\ud83e\udd70", "color_from": "rgba(157.78125, 125.2561677631579, 125.2561677631579, 1)", "color_to": "rgba(255, 0, 0, 1)", "short_description": "ssadasd", "pinned": true, "favorited": false, "date_added": "2024-11-13 16:25:36.058089"}]
|