giuseppecuccunm commited on
Commit
df2419b
·
verified ·
1 Parent(s): ec9b156

Fix card-number field sizing and non-digit Go! errors

Browse files
Files changed (4) hide show
  1. src/grades.py +10 -2
  2. src/streamlit_app.py +35 -8
  3. src/styles.py +6 -5
  4. tests/test_grades.py +7 -0
src/grades.py CHANGED
@@ -6,6 +6,7 @@ When the same key appears twice, the row with the later ``modified_at`` wins.
6
 
7
  from __future__ import annotations
8
 
 
9
  from dataclasses import dataclass
10
 
11
  import pandas as pd
@@ -329,10 +330,17 @@ def plan_progress_sync(
329
 
330
  def parse_card_number(raw: object, total_cards: int) -> tuple[int | None, str | None]:
331
  """Parse a 1-based card number, or return ``(None, error)``."""
332
- text = "" if raw is None else str(raw).strip()
 
 
 
 
 
 
 
333
  if not text:
334
  return None, "Enter a card number."
335
- if not text.isdigit():
336
  return None, "Card number must be digits only."
337
  number = int(text)
338
  if number <= 0:
 
6
 
7
  from __future__ import annotations
8
 
9
+ import re
10
  from dataclasses import dataclass
11
 
12
  import pandas as pd
 
330
 
331
  def parse_card_number(raw: object, total_cards: int) -> tuple[int | None, str | None]:
332
  """Parse a 1-based card number, or return ``(None, error)``."""
333
+ if isinstance(raw, bool) or isinstance(raw, float):
334
+ return None, "Card number must be digits only."
335
+ if isinstance(raw, int):
336
+ text = str(raw)
337
+ elif raw is None:
338
+ text = ""
339
+ else:
340
+ text = str(raw).strip()
341
  if not text:
342
  return None, "Enter a card number."
343
+ if not re.fullmatch(r"[0-9]+", text):
344
  return None, "Card number must be digits only."
345
  number = int(text)
346
  if number <= 0:
src/streamlit_app.py CHANGED
@@ -871,8 +871,26 @@ def _wire_card_jump_field() -> None:
871
  const win = window.parent;
872
  const doc = win.document;
873
  const size = (inp) => {
874
- const n = Math.max(2, (inp.value || '').length);
875
- inp.style.width = n + 'ch';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
876
  };
877
  const wire = () => {
878
  doc.querySelectorAll(
@@ -887,16 +905,22 @@ def _wire_card_jump_field() -> None:
887
  inp.addEventListener('keydown', (e) => {
888
  if (e.key !== 'Enter' || e.isComposing) return;
889
  e.preventDefault();
 
890
  const btn = doc.querySelector('.st-key-btn_card_go button');
891
  if (btn) btn.click();
892
  });
893
  }
894
  }
895
- doc.querySelectorAll('.card-jump-error').forEach((el) => {
896
- if (el.dataset.gradingHideArmed) return;
897
- el.dataset.gradingHideArmed = '1';
898
- setTimeout(() => { el.style.display = 'none'; }, 5000);
899
- });
 
 
 
 
 
900
  };
901
  wire();
902
  if (win.__gradingCardJumpObserver) {
@@ -1125,8 +1149,10 @@ def _render_card_jump(card_index: int, total_cards: int) -> None:
1125
  st.session_state.card_jump_to = str(card_index + 1)
1126
  err = st.session_state.get("card_jump_error")
1127
  if err:
 
1128
  st.markdown(
1129
- f"<div class='card-jump-error'>{html.escape(str(err))}</div>",
 
1130
  unsafe_allow_html=True,
1131
  )
1132
  form_kwargs: dict[str, object] = {"border": False}
@@ -1145,6 +1171,7 @@ def _render_card_jump(card_index: int, total_cards: int) -> None:
1145
  "Card number",
1146
  key="card_jump_to",
1147
  label_visibility="collapsed",
 
1148
  )
1149
  with suffix:
1150
  st.markdown(
 
871
  const win = window.parent;
872
  const doc = win.document;
873
  const size = (inp) => {
874
+ inp.setAttribute('maxlength', '6');
875
+ let v = inp.value || '';
876
+ if (v.length > 6) {
877
+ v = v.slice(0, 6);
878
+ inp.value = v;
879
+ }
880
+ const n = Math.min(6, Math.max(3, v.length));
881
+ inp.style.setProperty('width', n + 'ch', 'important');
882
+ };
883
+ const commit = (inp) => {
884
+ inp.dispatchEvent(new Event('input', { bubbles: true }));
885
+ inp.dispatchEvent(new Event('change', { bubbles: true }));
886
+ };
887
+ const hideIfExpired = () => {
888
+ doc.querySelectorAll('.card-jump-error').forEach((el) => {
889
+ const started = parseFloat(el.getAttribute('data-error-at') || '');
890
+ const age = Number.isFinite(started)
891
+ ? Date.now() - started * 1000 : 0;
892
+ el.style.display = age >= 5000 ? 'none' : '';
893
+ });
894
  };
895
  const wire = () => {
896
  doc.querySelectorAll(
 
905
  inp.addEventListener('keydown', (e) => {
906
  if (e.key !== 'Enter' || e.isComposing) return;
907
  e.preventDefault();
908
+ commit(inp);
909
  const btn = doc.querySelector('.st-key-btn_card_go button');
910
  if (btn) btn.click();
911
  });
912
  }
913
  }
914
+ hideIfExpired();
915
+ const err = doc.querySelector('.card-jump-error');
916
+ const at = err ? (err.getAttribute('data-error-at') || '') : '';
917
+ if (err && err.style.display !== 'none' && err.dataset.gradingHideTimer !== at) {
918
+ err.dataset.gradingHideTimer = at;
919
+ const started = parseFloat(at);
920
+ const remaining = Number.isFinite(started)
921
+ ? Math.max(0, 5000 - (Date.now() - started * 1000)) : 5000;
922
+ setTimeout(hideIfExpired, remaining);
923
+ }
924
  };
925
  wire();
926
  if (win.__gradingCardJumpObserver) {
 
1149
  st.session_state.card_jump_to = str(card_index + 1)
1150
  err = st.session_state.get("card_jump_error")
1151
  if err:
1152
+ err_at = st.session_state.get("card_jump_error_at") or time.time()
1153
  st.markdown(
1154
+ f"<div class='card-jump-error' data-error-at='{float(err_at):.6f}'>"
1155
+ f"{html.escape(str(err))}</div>",
1156
  unsafe_allow_html=True,
1157
  )
1158
  form_kwargs: dict[str, object] = {"border": False}
 
1171
  "Card number",
1172
  key="card_jump_to",
1173
  label_visibility="collapsed",
1174
+ max_chars=6,
1175
  )
1176
  with suffix:
1177
  st.markdown(
src/styles.py CHANGED
@@ -325,12 +325,13 @@ hr, [data-testid="stDivider"] {
325
  font-variant-numeric: tabular-nums !important;
326
  color: var(--text) !important;
327
  text-align: center !important;
328
- line-height: 1.25 !important;
329
  min-height: 0 !important;
330
- height: 1.25em !important;
331
- min-width: 2ch !important;
332
- width: 2ch !important;
333
- padding: 0.05em 0.12em !important;
 
334
  margin: 0 !important;
335
  background: var(--surface-2) !important;
336
  border: 1px solid var(--border) !important;
 
325
  font-variant-numeric: tabular-nums !important;
326
  color: var(--text) !important;
327
  text-align: center !important;
328
+ line-height: 1.875em !important;
329
  min-height: 0 !important;
330
+ height: 1.875em !important;
331
+ min-width: 3ch !important;
332
+ max-width: 6ch !important;
333
+ width: 3ch !important;
334
+ padding: 0.075em 0.18em !important;
335
  margin: 0 !important;
336
  background: var(--surface-2) !important;
337
  border: 1px solid var(--border) !important;
tests/test_grades.py CHANGED
@@ -362,6 +362,13 @@ def test_parse_card_number_accepts_digits_and_rejects_errors() -> None:
362
  assert number is None and err is not None
363
  number, err = parse_card_number("12a", 10)
364
  assert number is None and err is not None and "digits" in err
 
 
 
 
 
 
 
365
  number, err = parse_card_number("0", 10)
366
  assert number is None and err is not None and "at least 1" in err
367
  number, err = parse_card_number("11", 10)
 
362
  assert number is None and err is not None
363
  number, err = parse_card_number("12a", 10)
364
  assert number is None and err is not None and "digits" in err
365
+ for raw in (".", "1.2", "1e2", "+3", "abc", "3-1", "12.0"):
366
+ number, err = parse_card_number(raw, 10)
367
+ assert number is None and err is not None and "digits" in err, raw
368
+ number, err = parse_card_number(-4, 10)
369
+ assert number is None and err is not None and "digits" in err
370
+ number, err = parse_card_number(1.5, 10)
371
+ assert number is None and err is not None and "digits" in err
372
  number, err = parse_card_number("0", 10)
373
  assert number is None and err is not None and "at least 1" in err
374
  number, err = parse_card_number("11", 10)