feat(vues): bouton « Copier le lien » + lien de partage en texte stylé (#112)

- Bouton de copie explicite (icône copy.svg + « Copier le lien » + retour
  « ✓ Copié ») sur /tableau et /compte/vues, via dcc.Clipboard (children /
  copied_children), à la place de l'icône seule.
- Le lien direct s'affiche en texte (span#share-url-text) plutôt qu'un
  <input> : évite la troncature d'affichage du champ Bootstrap form-control en
  flex (zone de texte bloquée ~218px), montre tout le lien avec retour à la
  ligne, et prend la largeur du lien. Stylé comme un champ (bordure, coins
  arrondis, texte 90%).
- Instructions /compte/vues simplifiées ; E2E adapté (#share-url-text).
This commit is contained in:
Colin Maudry
2026-07-13 22:08:00 +02:00
parent 770b7cbdf8
commit e0828a7e13
5 changed files with 68 additions and 22 deletions
+10
View File
@@ -1095,3 +1095,13 @@ input[type="number"] {
background-color: var(--bs-primary-bg-subtle); background-color: var(--bs-primary-bg-subtle);
border-color: var(--bs-primary); 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);
}
+8 -2
View File
@@ -31,8 +31,14 @@ def _content():
[ [
html.H2("Mes vues"), html.H2("Mes vues"),
html.P( 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"), html.Div(saved_views_ui.views_table(views), id="vues-list"),
dbc.Modal( dbc.Modal(
+25 -11
View File
@@ -411,21 +411,35 @@ layout = [
className="share-url-box d-none", className="share-url-box d-none",
children=[ children=[
dbc.Label( dbc.Label(
"URL directe vers cette vue :", "Lien direct vers cette vue :",
html_for="share-url-input",
className="mb-0", className="mb-0",
), ),
dcc.Input( # URL affichée comme texte sélectionnable : prend exactement
id="share-url-input", # sa largeur (pas de champ pleine largeur qui encombre) et
type="text", # passe à la ligne si le lien est long (pas de troncature).
readOnly=True, html.Span(
className="form-control form-control-sm", id="share-url-text",
style={"maxWidth": "420px"}, className="share-url-text",
style={"wordBreak": "break-all", "minWidth": 0},
), ),
dcc.Clipboard( dcc.Clipboard(
target_id="share-url-input", target_id="share-url-text",
title="Copier le lien vers cette vue", 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( @callback(
Output("share-url-box", "className"), Output("share-url-box", "className"),
Output("share-url-input", "value"), Output("share-url-text", "children"),
Input("active-view", "data"), Input("active-view", "data"),
) )
def render_share_box(active_view): def render_share_box(active_view):
+17 -3
View File
@@ -79,7 +79,21 @@ def _view_row(view) -> html.Div:
dcc.Clipboard( dcc.Clipboard(
content=share_url, content=share_url,
title="Copier le lien vers cette vue", 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( dbc.Button(
"Renommer", "Renommer",
@@ -102,9 +116,9 @@ def _view_row(view) -> html.Div:
def views_table(views) -> html.Div: def views_table(views) -> html.Div:
if not views: if not views:
return html.Div( return html.Div(
html.P( dcc.Markdown(
"Vous n'avez pas encore de vue enregistrée. " "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]) return html.Div([_view_row(v) for v in views])
+8 -6
View File
@@ -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_text_to_equal(".logo > h1", "colibre", timeout=6)
dash_duo.wait_for_page(dash_duo.server_url + f"/tableau?vue=ma-vue_{token}") 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 # Le bloc de partage est visible et affiche l'URL courte (jeton). Le texte est
# de l'input est renseignée par le même callback que l'affichage : la lire # renseigné par le même callback que l'affichage : le lire non vide prouve que
# non vide prouve que l'écho de l'application n'a PAS masqué le bloc. # 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_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( WebDriverWait(dash_duo.driver, 10).until(
lambda _d: token lambda _d: token in (dash_duo.find_element("#share-url-text").text or "")
in (dash_duo.find_element("#share-url-input").get_attribute("value") 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 # 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 # 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. # peut invalider l'input entre le find et le send_keys.