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);
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.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(
+25 -11
View File
@@ -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):
+17 -3
View File
@@ -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])
+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_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.