Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

## Interface and handling

- refine GUI seed and CSV control widths, filter examples by task, cap result
table height and use dark active tabs for additional output views (PR #303 review)

- compact and left-align GUI parameter rows, split CSV controls into their own
tab, describe every additional output format and plot energy/probability files
with sequence names, source tabs and PNG export (PR #287 review)
Expand Down Expand Up @@ -292,6 +295,16 @@ energies, restricted partition sums, and trackers.
################################################################################
################################################################################

261026 Alexander Mitrofanov
* gui/app.js, gui/{index.html,styles.css}, scripts/wasm/test-browser.py
* place Explicit seeds beside a half-width Seed constraint selector and halve
the CSV sort selector width; verify desktop/mobile control geometry
* show only examples for the selected task, retain complete CSV downloads while
scrolling result tables above 500 px, and match output tab selection styling
to the parameter tabs (PR #303 maintainer review)
* gui/docs/{json-workflow,step3-frontend-ui,step4-results-and-deployment}.md
* document task-specific examples, compact controls and the bounded result table

261026 Alexander Mitrofanov
* gui/{app,options,sessions,output-visualization}.js,
gui/{index.html,styles.css}
Expand Down
15 changes: 13 additions & 2 deletions gui/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,22 @@ function fieldRow(body, fields, suffix = '') {
for (const definition of GROUPS) {
const body = group(definition.key, definition.label);
if (definition.key === 'seed-options') {
const primary = element('div', '', 'seed-primary-row');
const wrapper = element('div', '', 'field seed-mode');
const label = element('label', 'Seed constraint'); label.htmlFor = 'seed-mode';
const select = element('select'); select.id = 'seed-mode';
for (const [value, name] of [['none', 'Disable seed constraint'], ['explicit', 'Explicit seed'], ['identify', 'Identify seeds']]) {
const option = element('option', name); option.value = value; select.append(option);
}
select.value = 'identify'; wrapper.append(label, select); body.append(wrapper);
select.value = 'identify'; wrapper.append(label, select);
primary.append(wrapper, renderField(FIELDS.find((field) => field.key === 'seedTQ')));
body.append(primary);
}
if (definition.rows) {
for (const row of definition.rows) fieldRow(body, row);
for (const row of definition.rows) {
if (definition.key === 'seed-options' && row[0].key === 'seedTQ') continue;
fieldRow(body, row);
}
} else fieldRow(body, definition.fields);
if (definition.strand) {
const p = definition.strand;
Expand Down Expand Up @@ -173,6 +179,9 @@ function updateLength(name) {
}
function updateAvailability() {
const evaluation = getTask() === 'evaluation';
for (const button of byId('example-options').querySelectorAll('[data-example-task]')) {
button.hidden = button.dataset.exampleTask !== getTask();
}
byId('evaluation-input').hidden = !evaluation;
byId('task-help').textContent = evaluation ? 'Evaluate supplied structures for one query and one target. Prediction constraints are ignored.' : 'Predict interactions for all query–target sequence pairs.';
byId('compute').textContent = evaluation ? 'Evaluate interactions' : 'Compute interactions';
Expand Down Expand Up @@ -314,6 +323,7 @@ async function loadExamples() {
container.replaceChildren();
for (const example of examples) {
const button = element('button'); button.type = 'button';
button.dataset.exampleTask = example.request.task;
button.append(element('strong', example.name), element('span', example.description));
button.addEventListener('click', () => {
if (byId('inputs').disabled) return;
Expand All @@ -322,6 +332,7 @@ async function loadExamples() {
});
container.append(button);
}
updateAvailability();
} catch (error) { container.replaceChildren(element('p', `${error.message} Reload to try again.`, 'input-help')); }
}
loadExamples();
Expand Down
3 changes: 2 additions & 1 deletion gui/docs/json-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ uses native defaults for omitted parameters. Inactive controls keep their edits
in the form for switching modes, but their values are omitted from executed and
stored settings. Previously computed sessions remain available. Empty FASTA
fields show the short demonstration sequences as placeholders only. The Examples
disclosure menu follows the Input Parameters label.
disclosure menu follows the Input Parameters label and lists only examples for
the selected Prediction or Evaluation task.

Maintainers edit `gui/examples/index.json` (an array of local JSON filenames).
Each listed file contains `name`, `description`, and `configuration`, where the
Expand Down
3 changes: 3 additions & 0 deletions gui/docs/step3-frontend-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ are at most 9 rem wide within bounded, left-aligned row grids; select/text input
use compact type-specific tracks and text/data fields retain useful width. Rows
keep their scientific grouping without stretching across the complete panel,
use 16 px horizontal gaps and bottom-align labels above their controls.
The Seed tab places the constraint selector (12 rem) beside Explicit seeds on
desktop; the CSV sort selector uses a 19 rem track. Both controls stack at mobile
widths.
The institutional link reads “Bioinformatics - University of Freiburg” and opens
the group website. Footer and table notes use the available width, wrapping only
when the viewport requires it.
Expand Down
4 changes: 3 additions & 1 deletion gui/docs/step4-results-and-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ This is relative suboptimality, not a free-energy change or probability.
Every heading is a keyboard-operable sort button with `aria-sort`. Search covers
all returned values. A separate checklist shows/hides table columns after the
run; it does not change selected CSV output. All identifiers, output previews
and diagnostics render with `textContent`. Selected extra files appear below the
and diagnostics render with `textContent`. The table has a 500 px maximum height
with vertical scrolling for longer results and a sticky heading; CSV downloads
still contain every selected row. Selected extra files appear below the
table with their original filename, covered sequence/sequence-pair names, a
header-level download, and bounded text previews, including SVG source.
Empty output is successful and still permits CSV/accessibility downloads.
Expand Down
2 changes: 1 addition & 1 deletion gui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
<div data-role="results-view" hidden>
<div class="table-tools"><label>Filter interactions <input data-role="search" type="search" placeholder="Search any column" autocomplete="off"></label><span data-role="visible-count" role="status" aria-live="polite"></span><div class="download-actions"><button data-role="download" class="secondary" type="button">Download CSV</button></div></div>
<details class="column-picker"><summary>Show or hide table columns</summary><div data-role="visible-columns" class="checkbox-grid"></div></details>
<div class="table-scroll" tabindex="0" role="region" aria-label="Interaction results, scroll horizontally for all columns"><table><caption class="sr-only">Interactions. Select a heading to sort. mfe Δ is relative to the best reported interaction for the same sequence pair.</caption><thead><tr></tr></thead><tbody></tbody></table></div>
<div class="table-scroll" tabindex="0" role="region" aria-label="Interaction results, scroll to see more rows or columns"><table><caption class="sr-only">Interactions. Select a heading to sort. mfe Δ is relative to the best reported interaction for the same sequence pair.</caption><thead><tr></tr></thead><tbody></tbody></table></div>
<p data-role="no-matches" hidden>No interactions match this filter. Clear the search to see all results.</p>
<p class="table-note">mfe Δ is the energy difference from the best reported interaction for the same query–target pair; in evaluation, the reference is the best supplied structure. It is not a free-energy change. Coordinates are inclusive and use your start indices. Table visibility does not change the selected CSV download columns.</p>
<section data-role="visualization" aria-label="Interaction visualization">
Expand Down
35 changes: 28 additions & 7 deletions gui/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,15 @@ fieldset {
.field-grid > .field:not(.check) > :nth-child(2) { align-self:start; }
.accessibility-columns { display:grid; grid-template-columns:repeat(2,minmax(16rem,32rem)); gap:16px; justify-content:start; margin-top:12px; }
.accessibility-columns > div { min-width:0; }
.seed-mode { max-width:24rem; margin-bottom:12px; }
.seed-primary-row {
display:grid;
grid-template-columns:minmax(10rem,12rem) minmax(12rem,24rem);
gap:12px 16px;
align-items:start;
margin-bottom:12px;
}
.seed-primary-row .field { min-width:0; }
#csv-options .field-grid.columns-1 { grid-template-columns:minmax(10rem,19rem); }
.field-grid:has(#noSeed) { display:none; }
#query,#target { white-space:pre; overflow:auto; }
#sequence-help,#general-options p { max-width:none; }
Expand Down Expand Up @@ -463,6 +471,7 @@ fieldset {

.table-scroll {
width:100%;
max-height:500px;
overflow:auto;
border:1px solid var(--border);
}
Expand All @@ -478,6 +487,9 @@ th {
background:var(--table-header);
text-align:left;
padding:0 5px;
position:sticky;
top:0;
z-index:1;
}

th button {
Expand Down Expand Up @@ -614,21 +626,28 @@ tbody tr.selected { background:var(--table-selection); }
.output-tabs {
display:flex;
gap:4px;
padding:8px;
border-bottom:1px solid var(--border);
border-radius:5px 5px 0 0;
background:var(--brand-blue-soft);
}

.output-tab {
border:0;
border-bottom:3px solid transparent;
border-radius:0;
padding:.5rem .8rem;
border:1px solid transparent;
border-radius:5px;
padding:.55rem .75rem;
color:var(--brand-blue-dark);
background:transparent;
}

.output-tab[aria-selected=true] {
border-bottom-color:var(--brand-blue);
background:var(--brand-blue-soft);
border-color:var(--brand-blue);
background:var(--brand-blue);
color:white;
}

.output-tab:hover {
border-color:var(--brand-blue);
}

.output-tab-panel {
Expand Down Expand Up @@ -791,6 +810,8 @@ dialog::backdrop {
grid-template-columns:repeat(auto-fill,minmax(min(100%,220px),1fr));
}
.field-grid[class*=columns-],.accessibility-columns { grid-template-columns:1fr; }
.seed-primary-row { grid-template-columns:1fr; }
#csv-options .field-grid.columns-1 { grid-template-columns:1fr; }
.results {
padding:14px;
}
Expand Down
25 changes: 25 additions & 0 deletions scripts/wasm/test-browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ def predict():
expect(page.locator('#task-help')).to_contain_text('Evaluate supplied structures')
page.keyboard.press('ArrowLeft')
expect(page.get_by_role('radio', name='Prediction')).to_be_checked()
page.locator('#examples-menu summary').click()
expect(page.get_by_role('button', name='Short duplex', exact=False)).to_be_visible()
expect(page.get_by_role('button', name='Evaluate a duplex', exact=False)).not_to_be_visible()
page.get_by_role('radio', name='Evaluation').check()
expect(page.get_by_role('button', name='Evaluate a duplex', exact=False)).to_be_visible()
expect(page.get_by_role('button', name='Short duplex', exact=False)).not_to_be_visible()
page.get_by_role('radio', name='Prediction').check()
page.locator('#examples-menu summary').click()

# Tabs support keyboard navigation and wrap without page overflow.
page.locator('#tab-sequence-input').focus()
Expand Down Expand Up @@ -151,6 +159,11 @@ def predict():
expect(page.locator('#helixMinBP')).to_be_disabled()
open_group('seed-options')
expect(page.locator('#seedTQ')).to_be_disabled()
seed_box = page.locator('#seed-mode').bounding_box()
explicit_box = page.locator('#seedTQ').bounding_box()
assert abs(seed_box['y'] - explicit_box['y']) < 5
assert seed_box['x'] + seed_box['width'] < explicit_box['x']
assert seed_box['width'] < explicit_box['width']
page.locator('#seed-mode').select_option('none')
for key in ['seedBP', 'seedTQ', 'seedQRange', 'seedNoGU']:
expect(page.locator('#' + key)).to_be_disabled()
Expand All @@ -166,6 +179,7 @@ def predict():
assert all(label.get_attribute('title') for label in output_labels.all())
expect(page.locator('#output-options input[value="pMinE"]').locator('xpath=..')).to_have_attribute('title', re.compile('energy heatmap'))
open_group('csv-options')
assert page.locator('#outCsvSort').bounding_box()['width'] < 310
groups = page.locator('.csv-group')
assert groups.count() == 5
for group in groups.all():
Expand Down Expand Up @@ -268,6 +282,15 @@ def predict():
page.locator('#outCsvSort').select_option('E')
predict()
expect(rows).to_have_count(10)
assert active.locator('.table-scroll').evaluate('''node => {
const body = node.querySelector('tbody');
const copies = [...body.children].map(row => row.cloneNode(true));
body.append(...copies);
const scrolls = node.clientHeight <= 500 && node.scrollHeight > node.clientHeight
&& getComputedStyle(node).overflowY === 'auto';
copies.forEach(copy => copy.remove());
return scrolls;
}''')
assert rows.first.locator('td').all_text_contents()[1:7] == ['target', '1', '10', 'query', '1', '10']
assert rows.first.locator('td').nth(9).inner_text() == '-21.40'
assert rows.first.locator('td').last.inner_text() == '0.00'
Expand Down Expand Up @@ -358,6 +381,7 @@ def predict():
expect(output_files.nth(1).locator('summary')).to_contain_text('target : q2')
output_files.first.locator('summary').click()
expect(output_files.first.get_by_role('tab', name='Visualization')).to_have_attribute('aria-selected', 'true')
assert output_files.first.get_by_role('tab', name='Visualization').evaluate('node => getComputedStyle(node).backgroundColor') == page.locator('#tab-output-options').evaluate('node => getComputedStyle(node).backgroundColor')
expect(output_files.first.locator('.js-plotly-plot')).to_be_visible(timeout=30000)
expect(output_files.first.locator('.gtitle')).to_contain_text('q1')
if args.screenshot:
Expand Down Expand Up @@ -528,6 +552,7 @@ def predict():
assert download_archive(first) == first_archive

# A different task, CSV order and output report own a second result area.
page.get_by_role('radio', name='Evaluation').check()
load_example('evaluation')
with page.expect_download() as stored:
page.locator('#store-parameters').click()
Expand Down
Loading