feat: Added ElectronJS app to display the app

feat: Removed fullscreen dialogs to display them in popup window
This commit is contained in:
2024-06-28 13:42:41 +02:00
parent bdbb75cca8
commit 4b7f1f301e
26 changed files with 7512 additions and 570 deletions

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
/3d-app/
/electron-3d-app/
/express-app/
/react-app/
/.vscode/

8
.hintrc Normal file
View File

@ -0,0 +1,8 @@
{
"extends": [
"development"
],
"hints": {
"no-inline-styles": "off"
}
}

BIN
3D_app/assets/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

View File

@ -1,4 +1,5 @@
from dash import State, Output, Input, callback
from tkinter import NONE
from dash import State, Output, Input, callback, clientside_callback
import dash
import numpy as np
import plotly.graph_objects as go
@ -74,50 +75,19 @@ def get_callbacks():
)
return [fig, True]
# callback pour le plot 3D en plein écran
@callback(
Output("3dplot-fullscreen", "figure"),
[
Input("iso-slider-fullscreen", "value"),
Input("y-slider-fullscreen", "value"),
],
# callback pour le plein écran du plot 3D
clientside_callback(
"""
function toggle_fullscreen_3dplot(n1) {
if (n1) {
window.open("fullscreen/3dplot", "", "width=800,height=600,popup=true");
}
}
""",
Output("js", "children", allow_duplicate=True),
[Input("fullscreen-button-3dplot", "n_clicks")],
prevent_initial_call=True,
)
def update_3dplot_fullscreen(iso_value, y_values):
y_min, y_max = y_values
selected_volume = volume[0:dim_x, int(y_min) : int(y_max), 0:dim_z]
X, Y, Z = np.mgrid[
0 : selected_volume.shape[0],
0 : selected_volume.shape[1],
0 : selected_volume.shape[2],
]
fig = go.Figure(
data=go.Volume(
x=X.flatten(),
y=Y.flatten(),
z=Z.flatten(),
value=selected_volume.flatten(),
isomin=iso_value,
isomax=selected_volume.max(),
opacity=0.1,
surface_count=20,
colorscale="Jet",
)
)
return fig
# callback pour le plein écran du plot 3D
@callback(
Output("modal-3dplot", "is_open"),
[Input("fullscreen-button-3dplot", "n_clicks")],
[dash.dependencies.State("modal-3dplot", "is_open")],
)
def toggle_fullscreen_3dplot(n1, is_open):
if n1:
return not is_open
return is_open
# callback pour les paramètres du plot 3D
@callback(
@ -152,8 +122,6 @@ def get_callbacks():
[
Output("iso-slider-container", "children"),
Output("y-slider-container", "children"),
Output("iso-slider-fullscreen-container", "children"),
Output("y-slider-fullscreen-container", "children"),
],
Input("3dplot-settings", "data"),
)
@ -197,47 +165,8 @@ def get_callbacks():
),
]
)
iso_slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Iso value: "), width="auto"),
dbc.Col(
dcc.Slider(
id="iso-slider-fullscreen",
min=0,
max=volume.max() / 2,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(
0,
int(volume.max() / 2) + 1,
int((volume.max() / 2) / 20),
)
},
)
),
]
)
y_slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Y crop: "), width="auto"),
dbc.Col(
dcc.RangeSlider(
id="y-slider-fullscreen",
min=0,
max=dim_y,
value=[0, dim_y],
marks={
str(i): str(i)
for i in range(0, dim_y + 1, max(1, int(dim_y / 20)))
},
)
),
]
)
if data is None:
return [None, None, None, None]
return [None, None]
else:
if (
data["card_settings"] is None
@ -246,22 +175,10 @@ def get_callbacks():
iso_slider = None
if data["card_settings"] is None or "y_slider" not in data["card_settings"]:
y_slider = None
if (
data["fullscreen_settings"] is None
or "iso_slider" not in data["fullscreen_settings"]
):
iso_slider_fullscreen = None
if (
data["fullscreen_settings"] is None
or "y_slider" not in data["fullscreen_settings"]
):
y_slider_fullscreen = None
return [
iso_slider,
y_slider,
iso_slider_fullscreen,
y_slider_fullscreen,
]
# ----------------------------------------------------------------------
@ -295,40 +212,19 @@ def get_callbacks():
return [fig, True]
# callback pour le A-scan en plein écran
@callback(
Output("heatmap-ascan-fullscreen", "figure"),
[
Input("layer-slider-x-ascan-fullscreen", "value"),
Input("layer-slider-z-ascan-fullscreen", "value"),
Input("crop-slider-fullscreen", "value"),
],
State("store-settings", "data"),
)
def update_heatmap_ascan_fullscreen(layer_x, layer_z, crop, settings):
y_min, y_max = crop or [0, dim_y]
layer_x = layer_x or 0
layer_z = layer_z or 0
if settings["apply_sampling_everywhere"]:
data = volume
else:
data = pre_volume
fig = px.line(
y=data[layer_x - 1, int(y_min) : int(y_max), layer_z], title="A-scan"
)
return fig
# callback pour le plein écran du A-scan
@callback(
Output("modal-ascan", "is_open"),
# callback pour le plein écran du plot 3D
clientside_callback(
"""
function toggle_fullscreen_ascant(n1) {
if (n1) {
window.open("fullscreen/ascan", "", "width=800,height=600,popup=true");
}
}
""",
Output("js", "children", allow_duplicate=True),
[Input("fullscreen-button-ascan", "n_clicks")],
[dash.dependencies.State("modal-ascan", "is_open")],
prevent_initial_call=True,
)
def toggle_fullscreen_ascan(n1, is_open):
if n1:
return not is_open
return is_open
# callback pour les paramètres du A-scan
@callback(
@ -362,9 +258,6 @@ def get_callbacks():
@callback(
[
Output("crop-slider-container", "children"),
Output("layer-slider-x-ascan-fullscreen-container", "children"),
Output("layer-slider-z-ascan-fullscreen-container", "children"),
Output("crop-slider-fullscreen-container", "children"),
],
Input("ascan-settings", "data"),
)
@ -390,92 +283,16 @@ def get_callbacks():
),
]
)
slider_x_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Layer X: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-x-ascan-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(0, dim_x, max(1, int(dim_x / 50)))
},
)
),
]
)
slider_z_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Layer Z: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-z-ascan-fullscreen",
min=1,
max=dim_z - 1,
value=1,
marks={
str(i): str(i)
for i in range(1, dim_z - 1, max(1, int(dim_z / 20)))
},
step=1,
)
),
]
)
crop_slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Crop: "), width="auto"),
dbc.Col(
dcc.RangeSlider(
id="crop-slider-fullscreen",
min=0,
max=dim_y,
value=[0, dim_y],
marks={
str(i): str(i)
for i in range(
0,
dim_y + 1,
max(1, int(dim_y / 20)),
)
},
step=1,
)
),
]
)
if data is None:
return [None, None, None, None]
return [None]
else:
if (
data["card_settings"] is None
or "crop_slider" not in data["card_settings"]
):
crop_slider = None
if (
data["fullscreen_settings"] is None
or "layer_slider_x" not in data["fullscreen_settings"]
):
slider_x_fullscreen = None
if (
data["fullscreen_settings"] is None
or "layer_slider_z" not in data["fullscreen_settings"]
):
slider_z_fullscreen = None
if (
data["fullscreen_settings"] is None
or "crop_slider" not in data["fullscreen_settings"]
):
crop_slider_fullscreen = None
return [
crop_slider,
slider_x_fullscreen,
slider_z_fullscreen,
crop_slider_fullscreen,
]
# -------------------------------------------------------------------------
@ -512,37 +329,19 @@ def get_callbacks():
return [fig, layer, True]
# callback pour les B-scan ZX en plein écran
@callback(
Output("heatmap-bscan-zx-fullscreen", "figure"),
Input("layer-slider-bscan-zx-fullscreen", "value"),
State("store-settings", "data"),
)
def update_heatmap_bscan_zx_fullscreen(layer, settings):
if settings["apply_sampling_everywhere"]:
data = volume
else:
data = pre_volume
fig = px.imshow(
data[layer - 1, :, :],
color_continuous_scale="Jet",
aspect="auto",
title="B-scan ZX",
)
return fig
# callback pour le plein écran du B-scan ZX
@callback(
Output("modal-bscan-zx", "is_open"),
clientside_callback(
"""
function toggle_fullscreen_bscan_zx(n1) {
if (n1) {
window.open("fullscreen/bscan-zx", "", "width=800,height=600,popup=true");
}
}
""",
Output("js", "children", allow_duplicate=True),
[Input("fullscreen-button-bscan-zx", "n_clicks")],
[dash.dependencies.State("modal-bscan-zx", "is_open")],
prevent_initial_call=True,
)
def toggle_fullscreen_bscan_zx(n1, is_open):
if n1:
return not is_open
return is_open
# callback pour les paramètres du B-scan ZX
@callback(
@ -576,7 +375,6 @@ def get_callbacks():
@callback(
[
Output("layer-slider-bscan-zx-container", "children"),
Output("layer-slider-bscan-zx-fullscreen-container", "children"),
],
Input("bscan-zx-settings", "data"),
)
@ -599,56 +397,14 @@ def get_callbacks():
),
]
)
x_slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("X layer: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-bscan-zx-fullscreen",
min=0,
max=dim_z,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(0, dim_z, max(1, int(dim_z / 50)))
},
)
),
]
)
y_slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Y layer: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-bscan-xy-fullscreen",
min=0,
max=dim_z,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(0, dim_z, max(1, int(dim_z / 50)))
},
)
),
]
)
if data is None:
return [None, None]
return [None]
else:
if data["card_settings"] is None or "x_slider" not in data["card_settings"]:
slider = None
if (
data["fullscreen_settings"] is None
or "layer_slider" not in data["fullscreen_settings"]
):
slider_fullscreen = None
return [
slider,
slider_fullscreen,
]
# -------------------------------------------------------------------------
@ -682,34 +438,20 @@ def get_callbacks():
fig.update_layout(title="B-scan XY")
return [fig, layer, True]
# callback pour les B-scan ZX en plein écran
@callback(
Output("heatmap-bscan-xy-fullscreen", "figure"),
Input("layer-slider-bscan-xy-fullscreen", "value"),
State("store-settings", "data"),
)
def update_heatmap_bscan_xy_fullscreen(layer, settings):
if settings["apply_sampling_everywhere"]:
data = volume
else:
data = pre_volume
fig = go.Figure(data=go.Heatmap(z=data[:, :, layer], colorscale="Jet"))
fig.update_layout(title="B-scan XY")
return fig
# callback pour le plein écran du B-scan XY
@callback(
Output("modal-bscan-xy", "is_open"),
clientside_callback(
"""
function toggle_fullscreen_bscan_xy(n1) {
if (n1) {
window.open("fullscreen/bscan-xy", "", "width=800,height=600,popup=true");
}
}
""",
Output("js", "children", allow_duplicate=True),
[Input("fullscreen-button-bscan-xy", "n_clicks")],
[dash.dependencies.State("modal-bscan-xy", "is_open")],
prevent_initial_call=True,
)
def toggle_fullscreen_bscan_xy(n1, is_open):
if n1:
return not is_open
return is_open
@callback(
[
@ -758,7 +500,6 @@ def get_callbacks():
@callback(
[
Output("layer-slider-bscan-xy-container", "children"),
Output("layer-slider-bscan-xy-fullscreen-container", "children"),
],
Input("bscan-xy-settings", "data"),
)
@ -781,56 +522,14 @@ def get_callbacks():
),
]
)
x_slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("X layer: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-bscan-xy-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(0, dim_x, max(1, int(dim_x / 50)))
},
)
),
]
)
y_slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Y layer: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-bscan-zx-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(0, dim_x, max(1, int(dim_x / 50)))
},
)
),
]
)
if data is None:
return [None, None]
return [None]
else:
if data["card_settings"] is None or "x_slider" not in data["card_settings"]:
slider = None
if (
data["fullscreen_settings"] is None
or "layer_slider" not in data["fullscreen_settings"]
):
slider_fullscreen = None
return [
slider,
slider_fullscreen,
]
# ---------------------------------------------------------------------------------
@ -1107,9 +806,9 @@ def get_callbacks():
# Mettre à jour les graphiques
update_3dplot(0, [0, dim_y // 2], settings, False)
update_heatmap_ascan(0, 0, False, settings)
update_heatmap_bscan_zx(0, False, settings)
update_heatmap_bscan_xy(0, False, settings)
update_heatmap_ascan(0, 0, False, settings) # type: ignore
update_heatmap_bscan_zx(0, False, settings) # type: ignore
update_heatmap_bscan_xy(0, False, settings) # type: ignore
return None

View File

@ -4,6 +4,7 @@ from os import listdir
from os.path import isfile, join
import dash_bootstrap_components as dbc
def get_callbacks():
# callback pour le modal
@callback(
@ -16,7 +17,6 @@ def get_callbacks():
return not is_open
return is_open
@callback(
Output("settings-modal", "is_open"),
[Input("settings-open", "n_clicks"), Input("settings-close", "n_clicks")],
@ -27,7 +27,6 @@ def get_callbacks():
return not is_open
return is_open
# callback pour le navmenu
@callback(
Output("offcanvas-menu", "is_open"),
@ -39,10 +38,13 @@ def get_callbacks():
return not is_open
return is_open
@callback(
Output("open-modal", "is_open"),
[Input("open-button", "n_clicks"), Input("open-close", "n_clicks"), Input("open-refresh", "n_clicks")],
[
Input("open-button", "n_clicks"),
Input("open-close", "n_clicks"),
Input("open-refresh", "n_clicks"),
],
[dash.dependencies.State("open-modal", "is_open")],
)
def toggle_open(n1, n2, n3, is_open):
@ -52,7 +54,6 @@ def get_callbacks():
return True
return is_open
@callback(
Output("save-modal", "is_open"),
[
@ -67,7 +68,6 @@ def get_callbacks():
return not is_open
return is_open
@callback(
Output("file-list", "children"),
[Input("open-refresh", "n_clicks")],
@ -85,7 +85,6 @@ def get_callbacks():
if isfile(join("Dataset/saves", file))
]
@callback(
[
Output("open-modal", "is_open", allow_duplicate=True),
@ -105,7 +104,6 @@ def get_callbacks():
filename = filenames[file_index["index"]]
return [False, filename, f"Opened file: {filename}"]
@callback(
Output("save-format", "options"),
[Input("store-filters", "data")],
@ -121,3 +119,11 @@ def get_callbacks():
{"label": "Save filtered dataset", "value": "filt", "disabled": True},
]
@callback(
Output("navbar", "style"),
Input("loc", "pathname"),
)
def hide_navbar(pathname):
if not str.startswith(pathname, "/fullscreen"):
return {"display": "block"}
return {"display": "none"}

View File

@ -1,7 +1,7 @@
import dash
from dash import dcc, html, ALL, DiskcacheManager
import dash_bootstrap_components as dbc
from os import listdir, mkdir, path
from os import listdir, mkdir
from os.path import isfile, join
import diskcache
from json import load
@ -69,7 +69,7 @@ modal_overlay = dbc.Modal(
modal_settings = dbc.Modal(
[
dbc.ModalHeader("Settings"),
dbc.ModalHeader(dbc.ModalTitle("Settings")),
dbc.ModalBody(
[
dbc.Switch(
@ -146,7 +146,7 @@ modal_settings = dbc.Modal(
modal_open = dbc.Modal(
[
dbc.ModalHeader("Open a file"),
dbc.ModalHeader(dbc.ModalTitle("Open a file")),
dbc.ModalBody(
[
dbc.ListGroup(
@ -177,7 +177,7 @@ modal_open = dbc.Modal(
modal_save = dbc.Modal(
[
dbc.ModalHeader("Save a file"),
dbc.ModalHeader(dbc.ModalTitle("Save a file")),
dbc.ModalBody(
[
dbc.Input(id="save-input", placeholder="Filename"),
@ -383,6 +383,8 @@ nav_bar = dbc.Navbar(
style={"--bs-gutter-x": "0"},
),
dark=True,
style={"display": "none"},
id="navbar",
)
# on défini le layout de l'application
@ -408,8 +410,10 @@ app.layout = dbc.Container(
dcc.Store(id="3dplot-settings"),
dcc.Store(id="bscan-zx-settings"),
dcc.Store(id="bscan-xy-settings"),
html.Div(id="js", hidden=True),
],
id="app-container",
style={"paddingLeft": "0 !important", "paddingRight": "0 !important"},
fluid=True,
)
@ -418,5 +422,5 @@ get_callbacks()
# on lance l'application
if __name__ == "__main__":
app.run(
debug=config["debug"] or False, port=config["port"] or "8051", threaded=True
debug=config["debug"] or False, dev_tools_ui=config['debug'] or False, port=config["port"] or "8051", threaded=True
)

View File

@ -0,0 +1,194 @@
from dash import html, dcc, callback, Output, Input, clientside_callback
import dash_bootstrap_components as dbc
import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
import dash
from callbacks.home import pre_volume, volume, dim_x, dim_y, dim_z, X, Y, Z
dash.register_page(
__name__,
path="/fullscreen/3dplot",
title="3D Plot",
)
# on défini le thème de l'application
pio.templates.default = "plotly_dark"
# on crée le plot 3D
fig = go.Figure(
data=go.Volume(
x=X.flatten(),
y=Y.flatten(),
z=Z.flatten(),
value=volume.flatten(),
isomin=5000,
isomax=volume.max(),
opacity=0.1, # needs to be small to see through all surfaces
surface_count=20, # needs to be a large number for good volume rendering
colorscale="Jet",
)
)
# on défini les configurations des plots
config3DPlot = {
"toImageButtonOptions": {
"format": "svg", # one of png, svg, jpeg, webp
"filename": "3D-Plot",
"height": 1000,
"width": 1400,
"scale": 1, # Multiply title/legend/axis/canvas sizes by this factor
},
"displaylogo": False,
}
layout = html.Div(
[
html.Div(id="3dplot-js", hidden=True),
dbc.Spinner(id="loading-3dplot", color="primary", fullscreen=True, fullscreen_style={"backgroundColor": "var(--bs-body-bg)"}),
dcc.Graph(
id="3dplot-fullscreen",
figure=fig,
config=config3DPlot,
style={"marginBottom": "15px", "height": "85vh"},
), # 'fig' is your 3D plotly figure
html.Div(
dcc.Slider(
id="iso-slider-fullscreen",
min=volume.min(),
max=volume.max() / 2,
value=volume.min(),
marks={
str(i): str(i)
for i in range(
int(volume.min()),
int(volume.max() / 2) + 1,
int((volume.max() / 2 - volume.min()) / 20),
)
},
step=1,
),
id="iso-slider-fullscreen-container",
),
html.Div(
dcc.RangeSlider(
id="y-slider-fullscreen",
min=0,
max=dim_y,
value=[0, dim_y / 2],
marks={
str(i): str(i)
for i in range(
0,
int(dim_y) + 1,
max(1, int(dim_y / 50)),
)
},
step=1,
),
id="y-slider-fullscreen-container",
),
]
)
# callback pour le plot 3D en plein écran
@callback(
[Output("3dplot-fullscreen", "figure"), Output("loading-3dplot", "children")],
[
Input("iso-slider-fullscreen", "value"),
Input("y-slider-fullscreen", "value"),
],
)
def update_3dplot_fullscreen(iso_value, y_values):
y_min, y_max = y_values
selected_volume = volume[0:dim_x, int(y_min) : int(y_max), 0:dim_z]
X, Y, Z = np.mgrid[
0 : selected_volume.shape[0],
0 : selected_volume.shape[1],
0 : selected_volume.shape[2],
]
fig = go.Figure(
data=go.Volume(
x=X.flatten(),
y=Y.flatten(),
z=Z.flatten(),
value=selected_volume.flatten(),
isomin=iso_value,
isomax=selected_volume.max(),
opacity=0.1,
surface_count=20,
colorscale="Jet",
)
)
return [fig, html.Div()]
@callback(
[
Output("iso-slider-fullscreen-container", "children"),
Output("y-slider-fullscreen-container", "children"),
],
Input("3dplot-settings", "data"),
)
def update_3dplot_sliders(data):
print(data)
iso_slider = dbc.Row(
[
dbc.Col(html.Span("Iso value: "), width="auto"),
dbc.Col(
dcc.Slider(
id="iso-slider-fullscreen",
min=0,
max=volume.max() / 2,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(
0,
int(volume.max() / 2) + 1,
int((volume.max() / 2) / 20),
)
},
)
),
]
)
y_slider = dbc.Row(
[
dbc.Col(html.Span("Y crop: "), width="auto"),
dbc.Col(
dcc.RangeSlider(
id="y-slider-fullscreen",
min=0,
max=dim_y,
value=[0, dim_y],
marks={
str(i): str(i)
for i in range(0, dim_y + 1, max(1, int(dim_y / 20)))
},
)
),
]
)
if data is None:
return [None, None]
else:
if (
data["fullscreen_settings"] is None
or "iso_slider" not in data["fullscreen_settings"]
):
iso_slider = None
if (
data["fullscreen_settings"] is None
or "y_slider" not in data["fullscreen_settings"]
):
y_slider = None
return [
iso_slider,
y_slider,
]

View File

@ -0,0 +1,199 @@
import dash
from dash import dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc
import plotly.express as px
from callbacks.home import pre_volume, volume, dim_x, dim_y, dim_z, X, Y, Z
dash.register_page(__name__, path="/fullscreen/ascan", title="A-Scan")
configAScan = {
"toImageButtonOptions": {
"format": "svg", # one of png, svg, jpeg, webp
"filename": "A-Scan",
"height": 1000,
"width": 1400,
"scale": 1, # Multiply title/legend/axis/canvas sizes by this factor
},
"displaylogo": False,
}
layout = html.Div(
[
dbc.Spinner(
id="loading-ascan",
color="primary",
fullscreen=True,
fullscreen_style={"backgroundColor": "var(--bs-body-bg)"},
),
dcc.Graph(
id="heatmap-ascan-fullscreen",
config=configAScan,
style={"marginBottom": "15px", "height": "85vh"},
), # 'fig' is your 2D plotly figure
html.Div(
dcc.Slider(
id="layer-slider-x-ascan-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(
0,
dim_x,
max(1, int(dim_x / 50)),
)
},
),
id="layer-slider-x-ascan-fullscreen-container",
),
html.Div(
dcc.Slider(
id="layer-slider-z-ascan-fullscreen",
min=1,
max=dim_z,
value=1,
step=1,
marks={
str(i): str(i)
for i in range(
1,
dim_z,
max(1, int(dim_z / 20)),
)
},
),
id="layer-slider-z-ascan-fullscreen-container",
),
html.Div(
dcc.RangeSlider(
id="crop-slider-fullscreen",
min=0,
max=dim_y,
value=[0, dim_y],
marks={
str(i): str(i)
for i in range(
0,
dim_y + 1,
max(1, int(dim_y / 20)),
)
},
step=1,
),
id="crop-slider-fullscreen-container",
),
]
)
# callback pour le A-scan en plein écran
@callback(
[Output("heatmap-ascan-fullscreen", "figure"), Output("loading-ascan", "children")],
[
Input("layer-slider-x-ascan-fullscreen", "value"),
Input("layer-slider-z-ascan-fullscreen", "value"),
Input("crop-slider-fullscreen", "value"),
],
State("store-settings", "data"),
)
def update_heatmap_ascan_fullscreen(layer_x, layer_z, crop, settings):
y_min, y_max = crop or [0, dim_y]
layer_x = layer_x or 0
layer_z = layer_z or 0
if settings["apply_sampling_everywhere"]:
data = volume
else:
data = pre_volume
fig = px.line(y=data[layer_x - 1, int(y_min) : int(y_max), layer_z], title="A-scan")
return [fig, html.Div()]
@callback(
[
Output("layer-slider-x-ascan-fullscreen-container", "children"),
Output("layer-slider-z-ascan-fullscreen-container", "children"),
Output("crop-slider-fullscreen-container", "children"),
],
Input("ascan-settings", "data"),
)
def update_sliders(data):
slider_x_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Layer X: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-x-ascan-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i) for i in range(0, dim_x, max(1, int(dim_x / 50)))
},
)
),
]
)
slider_z_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Layer Z: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-z-ascan-fullscreen",
min=1,
max=dim_z - 1,
value=1,
marks={
str(i): str(i)
for i in range(1, dim_z - 1, max(1, int(dim_z / 20)))
},
step=1,
)
),
]
)
crop_slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("Crop: "), width="auto"),
dbc.Col(
dcc.RangeSlider(
id="crop-slider-fullscreen",
min=0,
max=dim_y,
value=[0, dim_y],
marks={
str(i): str(i)
for i in range(
0,
dim_y + 1,
max(1, int(dim_y / 20)),
)
},
step=1,
)
),
]
)
if data is None:
return [None, None, None]
else:
if (
data["fullscreen_settings"] is None
or "layer_slider_x" not in data["fullscreen_settings"]
):
slider_x_fullscreen = None
if (
data["fullscreen_settings"] is None
or "layer_slider_z" not in data["fullscreen_settings"]
):
slider_z_fullscreen = None
if (
data["fullscreen_settings"] is None
or "crop_slider" not in data["fullscreen_settings"]
):
crop_slider_fullscreen = None
return [slider_x_fullscreen, slider_z_fullscreen, crop_slider_fullscreen]

View File

@ -0,0 +1,104 @@
import re
import dash
from dash import html, dcc, Input, Output, State, callback
import plotly.graph_objects as go
import dash_bootstrap_components as dbc
from callbacks.home import pre_volume, volume, dim_x, dim_y, dim_z, X, Y, Z
dash.register_page(__name__, path="/fullscreen/bscan-xy", title="B-Scan XY")
configBScanXY = {
"toImageButtonOptions": {
"format": "svg", # one of png, svg, jpeg, webp
"filename": "B-Scan XY",
"height": 1000,
"width": 1400,
"scale": 1, # Multiply title/legend/axis/canvas sizes by this factor
},
"displaylogo": False,
}
layout = html.Div(
[
dbc.Spinner(
id="loading-bscan-xy",
color="primary",
fullscreen=True,
fullscreen_style={"backgroundColor": "var(--bs-body-bg)"},
),
dcc.Graph(
id="heatmap-bscan-xy-fullscreen",
config=configBScanXY,
style={"marginBottom": "15px", "height": "85vh"},
), # 'fig' is your 2D plotly figure
html.Div(
dcc.Slider(
id="layer-slider-bscan-xy-fullscreen",
min=1,
max=dim_z - 1,
value=1,
step=1,
marks={
str(i): str(i)
for i in range(
1,
dim_z + 1,
max(1, int(dim_z / 50)),
)
},
),
id="layer-slider-bscan-xy-fullscreen-container",
),
]
)
# callback pour les B-scan ZX en plein écran
@callback(
[Output("heatmap-bscan-xy-fullscreen", "figure"), Output("loading-bscan-xy", "children")],
Input("layer-slider-bscan-xy-fullscreen", "value"),
State("store-settings", "data"),
)
def update_heatmap_bscan_xy_fullscreen(layer, settings):
if settings["apply_sampling_everywhere"]:
data = volume
else:
data = pre_volume
fig = go.Figure(data=go.Heatmap(z=data[:, :, layer], colorscale="Jet"))
fig.update_layout(title="B-scan XY")
return [fig, html.Div()]
@callback(
Output("layer-slider-bscan-xy-fullscreen-container", "children"),
Input("bscan-xy-settings", "data"),
)
def update_sliders(data):
slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("X layer: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-bscan-xy-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i) for i in range(0, dim_x, max(1, int(dim_x / 50)))
},
)
),
]
)
if data is None:
return [None]
else:
if (
data["fullscreen_settings"] is None
or "layer_slider" not in data["fullscreen_settings"]
):
slider_fullscreen = None
return [slider_fullscreen]

View File

@ -0,0 +1,107 @@
import dash
from dash import html, dcc, Input, Output, State, callback
import plotly.express as px
import dash_bootstrap_components as dbc
from callbacks.home import pre_volume, volume, dim_x, dim_y, dim_z, X, Y, Z
dash.register_page(__name__, path="/fullscreen/bscan-zx", title="B-Scan ZX")
configBScanXY = {
"toImageButtonOptions": {
"format": "svg", # one of png, svg, jpeg, webp
"filename": "B-Scan ZX",
"height": 1000,
"width": 1400,
"scale": 1, # Multiply title/legend/axis/canvas sizes by this factor
},
"displaylogo": False,
}
layout = html.Div(
[
dbc.Spinner(
id="loading-bscan-zx",
color="primary",
fullscreen=True,
fullscreen_style={"backgroundColor": "var(--bs-body-bg)"},
),
dcc.Graph(
id="heatmap-bscan-zx-fullscreen",
config=configBScanXY,
style={"marginBottom": "15px", "height": "85vh"},
), # 'fig' is your 2D plotly figure
html.Div(
dcc.Slider(
id="layer-slider-bscan-zx-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(
0,
dim_x + 1,
max(1, int(dim_x / 50)),
)
},
),
id="layer-slider-bscan-zx-fullscreen-container",
),
]
)
# callback pour les B-scan ZX en plein écran
@callback(
[Output("heatmap-bscan-zx-fullscreen", "figure"), Output("loading-bscan-zx", "children")],
Input("layer-slider-bscan-zx-fullscreen", "value"),
State("store-settings", "data"),
)
def update_heatmap_bscan_zx_fullscreen(layer, settings):
if settings["apply_sampling_everywhere"]:
data = volume
else:
data = pre_volume
fig = px.imshow(
data[layer - 1, :, :],
color_continuous_scale="Jet",
aspect="auto",
title="B-scan ZX",
)
return [fig, html.Div()]
@callback(
Output("layer-slider-bscan-zx-fullscreen-container", "children"),
Input("bscan-zx-settings", "data"),
)
def update_sliders(data):
slider_fullscreen = dbc.Row(
[
dbc.Col(html.Span("X layer: "), width="auto"),
dbc.Col(
dcc.Slider(
id="layer-slider-bscan-zx-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(0, dim_x, max(1, int(dim_x / 50)))
},
)
),
]
)
if data is None:
return None
else:
if (
data["fullscreen_settings"] is None
or "layer_slider" not in data["fullscreen_settings"]
):
slider_fullscreen = None
return slider_fullscreen

View File

@ -1,5 +1,5 @@
import dash
from dash import html, dcc, DiskcacheManager
from dash import html, dcc, DiskcacheManager, get_asset_url
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import numpy as np
@ -178,66 +178,7 @@ mesh_card = dbc.Fade(
),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("3D Plot")),
dbc.ModalBody(
[
dcc.Graph(
id="3dplot-fullscreen",
figure=fig,
config=config3DPlot,
style={"marginBottom": "15px", "height": "90%"},
), # 'fig' is your 3D plotly figure
html.Div(
dcc.Slider(
id="iso-slider-fullscreen",
min=volume.min(),
max=volume.max() / 2,
value=volume.min(),
marks={
str(i): str(i)
for i in range(
int(volume.min()),
int(volume.max() / 2) + 1,
int(
(
volume.max() / 2
- volume.min()
)
/ 20
),
)
},
step=1,
),
id="iso-slider-fullscreen-container",
),
html.Div(
dcc.RangeSlider(
id="y-slider-fullscreen",
min=0,
max=dim_y,
value=[0, dim_y / 2],
marks={
str(i): str(i)
for i in range(
0,
int(dim_y) + 1,
max(1, int(dim_y / 50)),
)
},
step=1,
),
id="y-slider-fullscreen-container",
),
]
),
],
id="modal-3dplot",
fullscreen=True,
),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("3D plot settings")),
dbc.ModalHeader([html.Img(src=get_asset_url("settings.png"), height="32px", style={"marginRight": "5px"}), dbc.ModalTitle("3D plot settings")]),
dbc.ModalBody(
[
html.Span("Card display: "),
@ -352,77 +293,7 @@ Ascan_card = dbc.Fade(
),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("A-Scan")),
dbc.ModalBody(
[
dcc.Graph(
id="heatmap-ascan-fullscreen",
config=configAScan,
style={"marginBottom": "15px"},
), # 'fig' is your 2D plotly figure
html.Div(
dcc.Slider(
id="layer-slider-x-ascan-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(
0,
dim_x,
max(1, int(dim_x / 50)),
)
},
),
id="layer-slider-x-ascan-fullscreen-container",
),
html.Div(
dcc.Slider(
id="layer-slider-z-ascan-fullscreen",
min=1,
max=dim_z,
value=1,
step=1,
marks={
str(i): str(i)
for i in range(
1,
dim_z,
max(1, int(dim_z / 20)),
)
},
),
id="layer-slider-z-ascan-fullscreen-container",
),
html.Div(
dcc.RangeSlider(
id="crop-slider-fullscreen",
min=0,
max=dim_y,
value=[0, dim_y],
marks={
str(i): str(i)
for i in range(
0,
dim_y + 1,
max(1, int(dim_y / 20)),
)
},
step=1,
),
id="crop-slider-fullscreen-container",
),
]
),
],
id="modal-ascan",
fullscreen=True,
),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("A-Scan settings")),
dbc.ModalHeader([html.Img(src=get_asset_url("settings.png"), height="32px", style={"marginRight": "5px"}), dbc.ModalTitle("A-Scan settings")]),
dbc.ModalBody(
[
html.Span("Card display: "),
@ -537,41 +408,7 @@ Bscan_card_xy = dbc.Fade(
),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("B-Scan ZX")),
dbc.ModalBody(
[
dcc.Graph(
id="heatmap-bscan-zx-fullscreen",
config=configBScanXY,
style={"marginBottom": "15px", "height": "90%"},
), # 'fig' is your 2D plotly figure
html.Div(
dcc.Slider(
id="layer-slider-bscan-zx-fullscreen",
min=0,
max=dim_x,
value=0,
step=1,
marks={
str(i): str(i)
for i in range(
0,
dim_x + 1,
max(1, int(dim_x / 50)),
)
},
),
id="layer-slider-bscan-zx-fullscreen-container",
),
]
),
],
id="modal-bscan-zx",
fullscreen=True,
),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("B-Scan ZX settings")),
dbc.ModalHeader([html.Img(src=get_asset_url("settings.png"), height="32px", style={"marginRight": "5px"}), dbc.ModalTitle("B-Scan ZX settings")]),
dbc.ModalBody(
[
html.Span("Card display: "),
@ -689,41 +526,7 @@ Bscan_card_zx = dbc.Fade(
),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("B-Scan XY")),
dbc.ModalBody(
[
dcc.Graph(
id="heatmap-bscan-xy-fullscreen",
config=configBScanXY,
style={"marginBottom": "15px", "height": "90%"},
), # 'fig' is your 2D plotly figure
html.Div(
dcc.Slider(
id="layer-slider-bscan-xy-fullscreen",
min=1,
max=dim_z - 1,
value=1,
step=1,
marks={
str(i): str(i)
for i in range(
1,
dim_z + 1,
max(1, int(dim_z / 50)),
)
},
),
id="layer-slider-bscan-xy-fullscreen-container",
),
],
),
],
id="modal-bscan-xy",
fullscreen=True,
),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("B-Scan XY settings")),
dbc.ModalHeader([html.Img(src=get_asset_url("settings.png"), height="32px", style={"marginRight": "5px"}), dbc.ModalTitle("B-Scan XY settings")]),
dbc.ModalBody(
[
html.Span("Card display: "),

View File

@ -1,5 +1,6 @@
dash==2.17.0
dash_bootstrap_components==1.6.0
dash_extensions==1.0.16
diskcache==5.6.3
matplotlib==3.8.4
numpy==2.0.0
@ -7,5 +8,5 @@ pandas==2.2.2
plotly==5.22.0
progress==1.6
python_igraph==0.11.5
scipy==1.13.1
scipy==1.14.0
tqdm==4.66.4

92
electron-3d-app/.gitignore vendored Normal file
View File

@ -0,0 +1,92 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
.DS_Store
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Webpack
.webpack/
# Vite
.vite/
# Electron-Forge
out/

34
electron-3d-app/README.md Normal file
View File

@ -0,0 +1,34 @@
# Electron app
## Run or build from source
### Requirements:
- Node.JS LTS (tested with 20.14.0)
- NPM (tested with 10.8.1)
### Run
1. Navigate to this folder
2. Run `npm install` to install dependencies
3. Run `npm start` to start the application
### Build
1. Navigate to this folder
2. Run `npm install` to install dependencies
3. Run `npm run make` to build the application and package it in an installer
4. Run `npm run package` to build the application binaries
Once this is done, you will find the installer in `/path/to/electron/app/out/make/your_arch/` and the binaries in `/path/to/electron/app/out/your_arch`
## Use the app
At first run, the app will ask you to enter the URL of the server
![1719573286190](image/README/1719573286190.png)
If the server is unavailable, a message will be displayed to tell you the app cannot contact the server. You have two buttons: Reload and Change config. The second button is to modify the server URL in the config, if your server have new IP.
![1719573426594](image/README/1719573426594.png)

View File

@ -0,0 +1,54 @@
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
module.exports = {
packagerConfig: {
asar: true,
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {
options: {
author: "Florian Goussot",
description: "Stage IJL",
}
},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin', 'linux', 'windows'],
},
{
name: '@electron-forge/maker-deb',
config: {
options: {
maintainer: 'Florian Goussot',
homepage: 'https://github.com/mathur04/stage_IJL'
}
},
},
{
name: '@electron-forge/maker-dmg',
config: {},
},
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

6424
electron-3d-app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
{
"name": "electron-3d-app",
"productName": "electron-3d-app",
"version": "1.0.0",
"description": "My Electron application description",
"main": "src/index.js",
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"lint": "echo \"No linting configured\""
},
"devDependencies": {
"@electron-forge/cli": "^7.4.0",
"@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/maker-rpm": "^7.4.0",
"@electron-forge/maker-squirrel": "^7.4.0",
"@electron-forge/maker-zip": "^7.4.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
"@electron-forge/plugin-fuses": "^7.4.0",
"@electron/fuses": "^1.8.0",
"electron": "30.0.6"
},
"keywords": [],
"author": {
"name": "Le Stagiaire",
"email": "florian.goussot@ac-nancy-metz.fr"
},
"license": "MIT",
"dependencies": {
"@electron-forge/maker-dmg": "^7.4.0",
"custom-electron-prompt": "^1.5.7",
"electron-squirrel-startup": "^1.0.1",
"jspython-interpreter": "^2.1.15",
"python-shell": "^5.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<title>ERROR!</title>
</head>
<body style="padding: 8px;">
<img src="connection_error.png" alt="Error" height="128"/>
<h1>Cannot connect to the server</h1>
<p>Cannot access URL <span id="url"></span><br>
Please be sure the server is running and the URL is correct!</p>
<button id="reload" class="btn btn-primary">Reload</button>&nbsp;<button id="chg_conf" class="btn btn-secondary">Change config</button>
<script>
document.getElementById('url').innerText = location.search.substring(1);
document.getElementById('reload').onclick = () => {
document.getElementById('reload').innerHTML = `<span class="spinner-border spinner-border-sm" arria-hidden=true></span>`;
window.electronApi.reloadServer();
}
document.getElementById('chg_conf').onclick = () => {
document.getElementById('chg_conf').innerHTML = `<span class="spinner-border spinner-border-sm" arria-hidden=true></span>`;
window.electronApi.changeConfig();
}
</script>
</body>
</html>

View File

@ -0,0 +1,7 @@
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
Arial, sans-serif;
width: 100%;
height: 100%;
padding: 2rem;
}

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World!</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="index.css" />
<script type="module" src="renderer.js"></script>
</head>
<body>
<button id="open-files">Ouvrir</button>
<div class="card" style="width: 50%;">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<div id="plot1"></div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,111 @@
const { app, BrowserWindow, nativeTheme, Menu, dialog, ipcMain } = require('electron/main');
const path = require('node:path');
const fs = require('node:fs')
const prompt = require('custom-electron-prompt');
const configPath = app.getPath('userData');
const configFile = path.join(configPath, 'config.json');
if (!fs.existsSync(configFile)) {
fs.writeFileSync(configFile, JSON.stringify({}), 'utf8');
}
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit();
}
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1280,
height: 720,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
nodeIntegration: true
});
mainWindow.setMenu(null);
mainWindow.loadURL(config.serverUrl);
nativeTheme.themeSource = 'dark';
mainWindow.webContents.on('before-input-event', (event, input) => {
if (input.control && input.key.toLocaleLowerCase() === 'r') {
mainWindow.reload();
console.log("Reloading window...");
}
if (input.key.toLocaleLowerCase() === 'f11') {
mainWindow.setFullScreen(!mainWindow.isFullScreen());
}
})
ipcMain.on("config-change", (event) => {
prompt({ title: "Configuration", label: "Please provide the server URL and port:", inputAttrs: { type: 'url' }, type: "input", value: config.serverUrl }).then((value) => {
if (value !== null) {
config.serverUrl = value;
fs.writeFileSync(configFile, JSON.stringify(config));
mainWindow.loadURL(config.serverUrl);
}
});
});
ipcMain.on("get-server", () => {
mainWindow.loadURL(config.serverUrl);
});
mainWindow.webContents.on('did-fail-load', (evt, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessIdn, frameRoutingId) => {
if (errorCode === -102) {
dialog.showErrorBox("ERROR!", `Could not find the server at ${validatedURL}!\nPlease be sure the server is running and the URL is correct!`);
mainWindow.loadFile("src/error.html", { search: encodeURI(validatedURL) });
} else {
dialog.showErrorBox("ERROR!", errorDescription);
}
});
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
Menu.setApplicationMenu(null);
app.whenReady().then(() => {
if (!config.serverUrl) {
// Open a dialog to select the python executable
prompt({ title: "Configuration", label: "Please provide the server URL and port:", inputAttrs: { type: 'url' }, type: "input" }).then((value) => {
if (value !== null) {
config.serverUrl = value;
fs.writeFileSync(configFile, JSON.stringify(config));
createWindow();
} else {
dialog.showErrorBox("ERROR!", "You must specify a server URL!\nThe application will exit!");
app.quit();
}
});
} else {
createWindow();
}
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

View File

@ -0,0 +1,9 @@
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
const { contextBridge, ipcRenderer } = require("electron/renderer");
contextBridge.exposeInMainWorld("electronApi", {
changeConfig: () => ipcRenderer.send('config-change'),
reloadServer: () => ipcRenderer.send('get-server'),
})

View File