Surn commited on
Commit
96b8e12
·
1 Parent(s): 61f620d

Enhance UI and share link functionality

Browse files

0.2.26
Updated `README.md` with details on persistent storage, challenge mode, and version updates.
Adjusted tooltip margin in `_render_guess_form` for better spacing.
Refined `_game_over_content` to improve difficulty rendering and share URL handling:
- Replaced copy-to-clipboard button with robust implementation supporting modern and legacy browsers.
- Added safe encoding for share URLs using `json` and `html` modules.
- Introduced new HTML structure for share URL display with improved styling.
- Enhanced JavaScript logic for copying URLs with multiple fallbacks.
- Increased HTML component height to accommodate layout changes.
These updates improve user experience, accessibility, and cross-browser compatibility.

Files changed (3) hide show
  1. README.md +4 -0
  2. battlewords/__init__.py +1 -1
  3. battlewords/ui.py +87 -26
README.md CHANGED
@@ -121,8 +121,12 @@ docker run -p8501:8501 battlewords
121
  - Persistent Storage: all game results saved locally for personal statistics without accounts.
122
  - Challenge Mode: remote storage of challenge results, multi-user leaderboard, and shareable links.
123
 
 
 
 
124
  -0.2.25
125
  - Share challenge from expander
 
126
 
127
  -0.2.24
128
  - compress height
 
121
  - Persistent Storage: all game results saved locally for personal statistics without accounts.
122
  - Challenge Mode: remote storage of challenge results, multi-user leaderboard, and shareable links.
123
 
124
+ -02.26
125
+ - fix copy/share link button
126
+
127
  -0.2.25
128
  - Share challenge from expander
129
+ - fix incorrect guess overlap of guess box
130
 
131
  -0.2.24
132
  - compress height
battlewords/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.2.25"
2
  __all__ = ["models", "generator", "logic", "ui", "game_storage"]
 
1
+ __version__ = "0.2.26"
2
  __all__ = ["models", "generator", "logic", "ui", "game_storage"]
battlewords/ui.py CHANGED
@@ -1182,7 +1182,7 @@ def _render_guess_form(state: GameState):
1182
  text-align: left !important;
1183
  max-width: 320px !important;
1184
  line-height: 1.2;
1185
- margin-bottom: 25px;
1186
  }
1187
  /* Nudge tooltip popover below the trigger when possible */
1188
  div[data-testid="stTooltipPopover"] {
@@ -1427,7 +1427,7 @@ def _game_over_content(state: GameState) -> None:
1427
 
1428
  # Render difficulty line only if we have a value
1429
  difficulty_html = (
1430
- f'<div class="mb-2">Word list difficulty: <strong>{difficulty_value:.2f}</strong></div>'
1431
  if difficulty_value is not None else ""
1432
  )
1433
 
@@ -1449,6 +1449,7 @@ def _game_over_content(state: GameState) -> None:
1449
  "</tr></thead>"
1450
  f"<tbody>{''.join(word_rows)}"
1451
  f"<tr><td colspan=\"3\"><h5 class=\"m-2\">Total: {state.score} <span style='font-size:1.25rem; color:#1d64c8;'>&nbsp;⏱ {timer_str}</span></h5></td></tr>"
 
1452
  "</tbody>"
1453
  "</table>"
1454
  )
@@ -1500,8 +1501,7 @@ def _game_over_content(state: GameState) -> None:
1500
  <div class="mb-2">Tier: <strong>{compute_tier(state.score)}</strong></div>
1501
  <div class="mb-2">Game Mode: <strong>{state.game_mode}</strong></div>
1502
  <div class="mb-2">Wordlist: <strong>{st.session_state.get('selected_wordlist', '')}</strong></div>
1503
- <div class="mb-0">{table_html}</div>
1504
- {difficulty_html}
1505
  </div>
1506
  </div>
1507
  """,
@@ -1649,33 +1649,94 @@ def _game_over_content(state: GameState) -> None:
1649
  st.success("✅ Share link generated!")
1650
  st.code(share_url, language=None)
1651
 
1652
- # Copy to clipboard button
 
 
 
 
 
 
 
1653
  components.html(
1654
  f"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1655
  <script>
1656
- function copyToClipboard() {{
1657
- navigator.clipboard.writeText("{share_url}").then(function() {{
1658
- alert("Share link copied to clipboard!");
1659
- }}, function(err) {{
1660
- console.error('Could not copy text: ', err);
1661
- }});
1662
- }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1663
  </script>
1664
- <button onclick="copyToClipboard()" style="
1665
- width: 100%;
1666
- padding: 0.5rem 1rem;
1667
- background: #1d64c8;
1668
- color: white;
1669
- border: none;
1670
- border-radius: 0.5rem;
1671
- cursor: pointer;
1672
- font-size: 1rem;
1673
- font-weight: bold;
1674
- ">
1675
- 📋 Copy Link
1676
- </button>
1677
  """,
1678
- height=60
1679
  )
1680
 
1681
  st.markdown("---")
 
1182
  text-align: left !important;
1183
  max-width: 320px !important;
1184
  line-height: 1.2;
1185
+ margin-bottom: 35px;
1186
  }
1187
  /* Nudge tooltip popover below the trigger when possible */
1188
  div[data-testid="stTooltipPopover"] {
 
1427
 
1428
  # Render difficulty line only if we have a value
1429
  difficulty_html = (
1430
+ f'<tr><td colspan=\"3\"><h5 class=\"m-2\">Word list difficulty: {difficulty_value:.2f}</h5></td></tr>'
1431
  if difficulty_value is not None else ""
1432
  )
1433
 
 
1449
  "</tr></thead>"
1450
  f"<tbody>{''.join(word_rows)}"
1451
  f"<tr><td colspan=\"3\"><h5 class=\"m-2\">Total: {state.score} <span style='font-size:1.25rem; color:#1d64c8;'>&nbsp;⏱ {timer_str}</span></h5></td></tr>"
1452
+ f"{difficulty_html}"
1453
  "</tbody>"
1454
  "</table>"
1455
  )
 
1501
  <div class="mb-2">Tier: <strong>{compute_tier(state.score)}</strong></div>
1502
  <div class="mb-2">Game Mode: <strong>{state.game_mode}</strong></div>
1503
  <div class="mb-2">Wordlist: <strong>{st.session_state.get('selected_wordlist', '')}</strong></div>
1504
+ <div class="mb-0">{table_html}</div>
 
1505
  </div>
1506
  </div>
1507
  """,
 
1649
  st.success("✅ Share link generated!")
1650
  st.code(share_url, language=None)
1651
 
1652
+ # More robust copy-to-clipboard implementation with fallbacks
1653
+ import json as _json
1654
+ import html as _html
1655
+
1656
+ _share_url_json = _json.dumps(share_url) # safe for JS
1657
+ _share_url_attr = _html.escape(share_url, quote=True) # safe for HTML attribute
1658
+ _share_url_text = _html.escape(share_url)
1659
+
1660
  components.html(
1661
  f"""
1662
+ <div id="bw-copy-container" style="
1663
+ display:flex;
1664
+ gap:8px;
1665
+ width:100%;
1666
+ align-items:center;
1667
+ margin-top:6px;
1668
+ justify-content:center;
1669
+ ">
1670
+
1671
+ <strong><a href="{_share_url_attr}"
1672
+ target="_blank"
1673
+ rel="noopener noreferrer"
1674
+ style="text-decoration: underline; color: #fff; word-break: break-all; filter: drop-shadow(1px 1px 2px #003);">
1675
+ {_share_url_text}
1676
+ </a></strong>
1677
+
1678
+
1679
+ </div>
1680
  <script>
1681
+ (function() {{
1682
+ const SHARE_URL = {_share_url_json};
1683
+ const input = document.getElementById('bw-share-url');
1684
+
1685
+ async function modernCopy(text) {{
1686
+ if (!navigator.clipboard || !window.isSecureContext) {{
1687
+ throw new Error('Clipboard API unavailable in this context');
1688
+ }}
1689
+ await navigator.clipboard.writeText(text);
1690
+ }}
1691
+
1692
+ function legacyCopy(text) {{
1693
+ const ta = document.createElement('textarea');
1694
+ ta.value = text;
1695
+ ta.setAttribute('readonly', '');
1696
+ ta.style.position = 'fixed';
1697
+ ta.style.top = '-1000px';
1698
+ ta.style.left = '-1000px';
1699
+ document.body.appendChild(ta);
1700
+ ta.focus();
1701
+ ta.select();
1702
+ let ok = false;
1703
+ try {{
1704
+ ok = document.execCommand('copy');
1705
+ }} finally {{
1706
+ document.body.removeChild(ta);
1707
+ }}
1708
+ if (!ok) throw new Error('execCommand failed');
1709
+ }}
1710
+
1711
+ btn.addEventListener('click', async () => {{
1712
+ try {{
1713
+ try {{
1714
+ await modernCopy(SHARE_URL);
1715
+ }} catch (e) {{
1716
+ legacyCopy(SHARE_URL);
1717
+ }}
1718
+ setStatus('Copied!');
1719
+ }} catch (e) {{
1720
+ // Final fallback: select input text and ask user to copy
1721
+ try {{
1722
+ input.removeAttribute('readonly');
1723
+ input.focus();
1724
+ input.select();
1725
+ input.setSelectionRange(0, input.value.length);
1726
+ document.execCommand('copy'); // may still work on some browsers
1727
+ input.setAttribute('readonly', '');
1728
+ setStatus('Copied!');
1729
+ }} catch (err) {{
1730
+ input.focus();
1731
+ input.select();
1732
+ setStatus('Press Ctrl+C');
1733
+ }}
1734
+ }}
1735
+ }});
1736
+ }})();
1737
  </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
1738
  """,
1739
+ height=80
1740
  )
1741
 
1742
  st.markdown("---")