Odilink / app.py
Jofax's picture
Update app.py
dcb612d verified
import gradio as gr
# ─────────────────────────────────────────────
# VIRI MASTER DATA
# ─────────────────────────────────────────────
VESSELS = [
{"id": "18365", "name": "Ice-9", "atoll": "Noonu", "type": "Supply", "contact": "7781552"},
{"id": "18585", "name": "Ilaa", "atoll": "Alif Alif", "type": "Supply", "contact": "7500505"},
{"id": "18231", "name": "Landaa Giraavaru 1", "atoll": "Baa", "type": "Resort", "contact": "Four Seasons"},
{"id": "11054", "name": "Maafahi", "atoll": "Raa", "type": "Supply", "contact": "7775151"},
{"id": "18652", "name": "Manta Cruise", "atoll": "Central", "type": "Safari", "contact": "Liveaboard"},
{"id": "17316", "name": "Hope", "atoll": "Male", "type": "Supply", "contact": "7772439"},
{"id": "14595", "name": "Huvafen 1", "atoll": "K. Kaafu", "type": "Speedboat", "contact": "7771715"}
]
# ─────────────────────────────────────────────
# MOBILE CSS & VIEWPORT UI
# ─────────────────────────────────────────────
# Injecting the viewport and styles via HTML to avoid launch() errors
MOBILE_UI_SETUP = """
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
.gradio-container { max-width: 100% !important; padding: 0 !important; background-color: #f8fafc !important; }
.tabs { border: none !important; }
.tab-nav {
position: fixed !important; bottom: 0 !important; width: 100% !important;
background: white !important; z-index: 1000 !important;
border-top: 1px solid #e2e8f0 !important; height: 70px !important;
display: flex !important; justify-content: space-around !important;
}
footer { display: none !important; }
#vessel_list_container { padding-bottom: 80px !important; }
</style>
"""
def get_radar_overlay(v_id):
if not v_id: return ""
iframe_url = f"https://m.followme.mv/public/?id={v_id}"
return f"""
<div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #000; z-index: 99999;">
<button onclick="window.location.reload();" style="position: absolute; top: 25px; right: 20px; background: #ef4444; color: white; border: none; padding: 15px 25px; border-radius: 50px; font-weight: 900; z-index: 100000; box-shadow: 0 5px 20px rgba(0,0,0,0.5);">βœ• CLOSE</button>
<iframe src="{iframe_url}" width="100%" height="100%" style="border: none;"></iframe>
</div>
"""
def render_list(search_txt):
html = '<div id="vessel_list_container" style="padding: 15px; display: flex; flex-direction: column; gap: 10px;">'
for v in VESSELS:
if search_txt and search_txt.lower() not in v["name"].lower(): continue
html += f"""
<div style="background: white; border-radius: 15px; padding: 15px; border: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center;">
<div>
<h4 style="margin: 0; color: #0c4a6e; font-size: 15px; font-weight: 900;">{v['name']}</h4>
<p style="margin: 0; font-size: 10px; color: #0ea5e9; font-weight: 800;">{v['atoll'].upper()} β€’ {v['type']}</p>
</div>
<button onclick="document.getElementById('nav_trigger_input').querySelector('input').value='{v['id']}'; document.getElementById('nav_trigger_input').querySelector('input').dispatchEvent(new Event('input', {{bubbles: true}}));"
style="background: #0ea5e9; color: white; border: none; padding: 10px 15px; border-radius: 10px; font-weight: 800; font-size: 11px;">
TRACK
</button>
</div>
"""
html += "</div>"
return html
# ─────────────────────────────────────────────
# APP INTERFACE
# ─────────────────────────────────────────────
with gr.Blocks(title="Viri | Maldives Fleet") as app:
# Inject CSS and Viewport scaling directly into the page
gr.HTML(MOBILE_UI_SETUP)
# Hidden input for Javascript-to-Python bridge
nav_trigger = gr.Textbox(visible=False, elem_id="nav_trigger_input")
radar_container = gr.HTML()
with gr.Tabs() as tabs:
with gr.Tab("πŸ›°οΈ LIVE MAP", id="tab_home"):
# Main Front Page Map (Full Coverage)
gr.HTML('<iframe src="https://m.followme.mv/public/" width="100%" height="380px" style="border:none; border-radius: 0 0 20px 20px;"></iframe>')
with gr.Group():
search = gr.Textbox(placeholder="πŸ” Search Vessel Name...", label=None, container=False)
list_view = gr.HTML(render_list(""))
search.change(render_list, search, list_view)
with gr.Tab("🎫 TICKETS"):
gr.HTML("""
<div style='padding:50px; text-align:center; color:#94a3b8;'>
<h3>BML Gateway</h3>
<p>No active sessions found.</p>
</div>
""")
# When nav_trigger changes, it injects the radar iframe
nav_trigger.change(get_radar_overlay, nav_trigger, radar_container)
# Launch with zero unsupported arguments
app.launch()