diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 05c44e1..64d1e6a 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -1095,3 +1095,13 @@ input[type="number"] { background-color: var(--bs-primary-bg-subtle); border-color: var(--bs-primary); } + +/* Lien de partage affiché en texte (span) mais stylé comme un champ : + bordure gris clair, coins arrondis, texte légèrement plus petit. */ +.share-url-text { + font-size: 90%; + padding: 0.2rem 0.5rem; + border: 1px solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + background-color: var(--bs-body-bg); +} diff --git a/src/pages/compte/vues.py b/src/pages/compte/vues.py index 04103ac..f6da005 100644 --- a/src/pages/compte/vues.py +++ b/src/pages/compte/vues.py @@ -31,8 +31,14 @@ def _content(): [ html.H2("Mes vues"), html.P( - "Les vues que vous enregistrez depuis le Tableau apparaissent ici. " - "Cliquez sur « Ouvrir » pour appliquer une vue." + [ + "Les vues que vous enregistrez depuis le Tableau apparaissent " + "ici. Cliquez sur ", + html.B("Ouvrir"), + " pour appliquer une vue, ou sur ", + html.B("Copier le lien"), + " pour copier son adresse Web et la partager par mail ou par message, y compris avec une personne qui n'a pas de compte colibre.", + ] ), html.Div(saved_views_ui.views_table(views), id="vues-list"), dbc.Modal( diff --git a/src/pages/tableau.py b/src/pages/tableau.py index 4844aaf..a0d377e 100644 --- a/src/pages/tableau.py +++ b/src/pages/tableau.py @@ -411,21 +411,35 @@ layout = [ className="share-url-box d-none", children=[ dbc.Label( - "URL directe vers cette vue :", - html_for="share-url-input", + "Lien direct vers cette vue :", className="mb-0", ), - dcc.Input( - id="share-url-input", - type="text", - readOnly=True, - className="form-control form-control-sm", - style={"maxWidth": "420px"}, + # URL affichée comme texte sélectionnable : prend exactement + # sa largeur (pas de champ pleine largeur qui encombre) et + # passe à la ligne si le lien est long (pas de troncature). + html.Span( + id="share-url-text", + className="share-url-text", + style={"wordBreak": "break-all", "minWidth": 0}, ), dcc.Clipboard( - target_id="share-url-input", + target_id="share-url-text", title="Copier le lien vers cette vue", - style={"cursor": "pointer", "fontSize": "1.1rem"}, + className="btn btn-outline-secondary btn-sm " + "d-inline-flex align-items-center", + children=[ + html.Img( + src="/assets/copy.svg", + alt="", + style={ + "height": "1em", + "verticalAlign": "-0.15em", + "marginRight": "0.35em", + }, + ), + "Copier le lien", + ], + copied_children="✓ Copié", ), ], ), @@ -714,7 +728,7 @@ _SHARE_BOX_HIDDEN = "share-url-box d-none" @callback( Output("share-url-box", "className"), - Output("share-url-input", "value"), + Output("share-url-text", "children"), Input("active-view", "data"), ) def render_share_box(active_view): diff --git a/src/saved_views/ui.py b/src/saved_views/ui.py index 8a0ec98..63e04a5 100644 --- a/src/saved_views/ui.py +++ b/src/saved_views/ui.py @@ -79,7 +79,21 @@ def _view_row(view) -> html.Div: dcc.Clipboard( content=share_url, title="Copier le lien vers cette vue", - style={"cursor": "pointer", "fontSize": "1.1rem"}, + className="btn btn-outline-secondary btn-sm " + "d-inline-flex align-items-center", + children=[ + html.Img( + src="/assets/copy.svg", + alt="", + style={ + "height": "1em", + "verticalAlign": "-0.15em", + "marginRight": "0.35em", + }, + ), + "Copier le lien", + ], + copied_children="✓ Copié", ), dbc.Button( "Renommer", @@ -102,9 +116,9 @@ def _view_row(view) -> html.Div: def views_table(views) -> html.Div: if not views: return html.Div( - html.P( + dcc.Markdown( "Vous n'avez pas encore de vue enregistrée. " - "Créez-en une depuis le Tableau, bouton « Sauvegarder la vue »." + "Créez-en une depuis le Tableau, bouton **Sauvegarder la vue**." ) ) return html.Div([_view_row(v) for v in views]) diff --git a/tests/saved_views/test_tableau_share.py b/tests/saved_views/test_tableau_share.py index d6777d5..e8e23a9 100644 --- a/tests/saved_views/test_tableau_share.py +++ b/tests/saved_views/test_tableau_share.py @@ -168,16 +168,18 @@ def test_open_shared_view_applies_and_shows_box(dash_duo, users_db_path): dash_duo.wait_for_text_to_equal(".logo > h1", "colibre", timeout=6) dash_duo.wait_for_page(dash_duo.server_url + f"/tableau?vue=ma-vue_{token}") - # Le bloc de partage est visible et contient l'URL courte (jeton). La valeur - # de l'input est renseignée par le même callback que l'affichage : la lire - # non vide prouve que l'écho de l'application n'a PAS masqué le bloc. + # Le bloc de partage est visible et affiche l'URL courte (jeton). Le texte est + # renseigné par le même callback que l'affichage : le lire non vide prouve que + # l'écho de l'application n'a PAS masqué le bloc. dash_duo.wait_for_style_to_equal("#share-url-box", "display", "flex", timeout=10) - dash_duo.wait_for_element("#share-url-input", timeout=6) + dash_duo.wait_for_element("#share-url-text", timeout=6) WebDriverWait(dash_duo.driver, 10).until( - lambda _d: token - in (dash_duo.find_element("#share-url-input").get_attribute("value") or "") + lambda _d: token in (dash_duo.find_element("#share-url-text").text or "") ) + # Le bouton de copie porte un libellé explicite (UX : pas d'icône seule). + assert "Copier le lien" in dash_duo.find_element("#share-url-box").text + # Une action utilisateur (filtre) masque la box. On re-tente la saisie tant # que le bloc n'est pas masqué : la fenêtre de re-render post-application # peut invalider l'input entre le find et le send_keys.