From 3839b4b3357d26db4b92b90a147d93342a90e56c Mon Sep 17 00:00:00 2001 From: chloeskt Date: Thu, 18 Mar 2021 08:44:08 +0100 Subject: [PATCH] feat: create alert screen pages --- .gitignore | 3 + app/alert_screen.py | 229 ++++++++++++++++++++++++++++++--- app/assets/pyro_alert_on.png | Bin 0 -> 13229 bytes app/assets/pyro_fire_logo.png | Bin 0 -> 9427 bytes app/assets/styles.css | 67 ++++++++++ app/homepage.py | 6 - app/main.py | 236 +++++++++++++++++++++++++++------- app/navbar.py | 21 +++ app/utils.py | 4 +- 9 files changed, 490 insertions(+), 76 deletions(-) create mode 100644 app/assets/pyro_alert_on.png create mode 100644 app/assets/pyro_fire_logo.png create mode 100644 app/assets/styles.css diff --git a/.gitignore b/.gitignore index 34c23ff..87cf194 100644 --- a/.gitignore +++ b/.gitignore @@ -114,6 +114,9 @@ venv.bak/ .spyderproject .spyproject +# Pycharm project settings +.idea/ + # Rope project settings .ropeproject diff --git a/app/alert_screen.py b/app/alert_screen.py index 7150de5..e2684a7 100644 --- a/app/alert_screen.py +++ b/app/alert_screen.py @@ -4,8 +4,8 @@ # See LICENSE or go to for full license details. """ -The following Python file is dedicated to the big screen of the web application. -The big screen corresponds to a web page which will be displayed on a big screen in the CODIS room. There will be no +The following Python file is dedicated to the alert screen of the web application. +The alert screen corresponds to a web page which will be displayed on a big screen in the CODIS room. There will be no interaction with the user. The main use of this page is to display a "sober" screen when there are no alerts. When an alert pops out, the screen will automatically change to display various information. @@ -20,34 +20,225 @@ import dash_core_components as dcc import dash_html_components as html -# From navbar.py to add the navigation bar at the top of the page -from navbar import Navbar -# Importing alerts map builder from alerts.py -from alerts import build_alerts_map +# ---------------------------------------------------------------------------------------------------------------------- +# CONTENT +def build_no_alert_detected_screen(): + """ + The following function builds the no alert screen. + """ + # background image as style + style = { + "backgroundImage": 'url("/assets/pyro_alert_off.png")', + "backgroundRepeat": "no-repeat", + "backgroundPosition": "center", + "backgroundSize": "cover", + "position": "fixed", + "height": "100%", + "width": "100%", + } -# Importing risks map and opacity slider builders from risks.py -from risks import build_risks_map, build_opacity_slider + return style -# Importing plotly fig objects from graphs.py -from graphs import generate_meteo_fig -# Importing layer style button builder and fetched API data from utils.py -from utils import build_layer_style_button, build_live_alerts_metadata +def build_alert_detected_screen(img_url, alert_metadata, last_alert): + """ + This function is used in the main.py file to create the alert screen when its on, i.e. when there is an alert + ongoing. + It takes as arguments: -# ---------------------------------------------------------------------------------------------------------------------- -# CONTENT + - 'img_url': the URL address of the alert frame to be displayed on the left of the map; + - 'alert_metadata': a dictionary containing metadata about the ongoing alert + - 'last_alert': pd.DataFrame of the last alert + All these inputs are instantiated in the main.py file via a call to the API. + """ + # Get lat and lon from last_alert + lat, lon = last_alert["lat"], last_alert["lon"] + + # Get device id for last_alert + device_id = last_alert["device_id"] + + # Background image + background_image = html.Img( + id="alert_background", + src="/assets/pyro_alert_on.png", + style={ + "position": "fixed", + "width": "100vw", + "height": "100vh", + }, + ) + + # Fire icon + fire_icon = html.Img( + id="fire_icon", + src="/assets/pyro_fire_logo.png", + className="blink-image", + style={"height": "100%"}, + ) + + # Detection frames + alert_frame = html.Img( + id="alert_frame", + src=img_url, + style={ + "position": "relative", + "width": "100%", + "height": "100%", + "object-fit": "contain", + }, + ) + + # Fire alert image div (right of the screen) + # Multiple frames are rendered here (to make it look like a GIF) + fire_images_div = html.Div( + id="fire_alert_div", + children=[ + alert_frame, + dcc.Interval(id="interval-component-img-refresh", interval=3 * 1000), + ], + style={"display": "flex", "height": "100%", "width": "100%"}, + ) + + # Alert metadata div + alert_metadata_div = html.Div( + children=[ + html.P("Tour: {}".format(alert_metadata["site_name"])), + html.P("Coordonnées de la tour: {}, {}".format(lat, lon)), + html.P("Id de caméra: {}".format(device_id)), + html.P("Azimuth: {}".format(alert_metadata["azimuth"])), + ] + ) + + # Fire text div (left part of screen) + fire_text_div = html.Div( + id="fire_text_div", + children=[ + html.Div( + html.P( + "DFCI: KD62D6.5", + ), + style={ + "font-size": "2vw", + "color": "#054546", + "font-weight": "bold", + }, + ), + html.Div( + alert_metadata_div, + style={ + "font-size": "1.75vw", + "color": "#054546", + }, + ), + ], + style={ + "margin-top": "5%", + }, + ) + + # Final layout: one row containing 2 columns, and each column contains two rows + layout_div = [ + background_image, + dbc.Row( + children=[ + dbc.Col( + id="col_fire_text", + children=[ + dbc.Row( + id="fire_icon_rwo", + children=fire_icon, + style={ + "display": "flex", + "justify-content": "center", + "height": "30%", + }, + ), + dbc.Row( + id="fire_text_row", + children=fire_text_div, + style={ + "display": "flex", + "justify-content": "center", + "margin-right": "2.5%", + }, + ), + ], + style={ + "width": "50%", + "margin": "2.5%", + }, + ), + dbc.Col( + id="col_image_fire", + children=[ + dbc.Row( + html.Div( + html.Div( + html.P("DÉPART DE FEU"), + style={ + "font-size": "4vw", + "color": "#fd4848", + "font-weight": "bold", + }, + className="blink-image", + ), + ), + style={ + "display": "flex", + "justify-content": "center", + "padding-bottom": "7%", + }, + ), + dbc.Row( + id="fire_images_row", + children=fire_images_div, + style={ + "display": "flex", + "justify-content": "center", + "margin-right": "2.5%", + }, + ), + ], + style={ + "width": "50%", + "margin": "2.5%", + }, + ), + ], + style={ + "height": "100%", + }, + ), + ] + + style = { + "height": "100%", + "width": "100%", + "position": "fixed", + } + + return layout_div, style # ---------------------------------------------------------------------------------------------------------------------- # App layout -# The following block gathers elements defined above and returns them via the BigScreen function - - -def Bigscreen(): +# The following block gathers elements defined above and returns them via the alert_screen function +def alert_screen(): """ The following function is used in the main.py file to build the layout of the big screen page. """ - raise NotImplementedError + layout = html.Div( + children=[ + dcc.Interval(id="interval-component-alert-screen", interval=3 * 1000), + html.Div(id="core_layout_alert_screen", children=[]), + ], + style={ + "height": "100%", + "width": "100%", + "position": "fixed", + }, + ) + return layout diff --git a/app/assets/pyro_alert_on.png b/app/assets/pyro_alert_on.png new file mode 100644 index 0000000000000000000000000000000000000000..466787ac30495933c5b2c020956b8d24b186458c GIT binary patch literal 13229 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