import gradio as gr class RunsTable(gr.HTML): def __init__( self, headers: list[str] | None = None, rows: list[list[str]] | None = None, *, value: list[int] | None = None, interactive: bool = True, **kwargs, ): headers = headers or [] rows = rows or [] value = value or [] html_template = """
${headers.map((h, idx) => ``).join('')} ${rows.length === 0 ? ` ` : rows.map((row, idx) => ` ${row.map((cell, colIdx) => ``).join('')} `).join('')}
${h}
No runs found
${cell}
""" css_template = """ .runs-table-container { background: transparent; overflow: hidden; } .runs-table-scroll { overflow-x: auto; overflow-y: visible; width: 100%; max-width: 100%; } .runs-table { width: 100%; border-collapse: collapse; font-size: var(--text-md); min-width: max-content; border: none; background: transparent; } .runs-table thead { background: transparent; position: sticky; top: 0; z-index: 1; } .runs-table th { padding: 12px 16px; text-align: left; font-weight: 600; color: var(--block-title-text-color); white-space: nowrap; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; border: 1px solid var(--border-color-primary); } .runs-table td { padding: 10px 16px; color: var(--body-text-color); border: 1px solid var(--border-color-primary); } .runs-table tbody tr.selected { background: var(--color-accent-soft); } .runs-table .checkbox-col { width: 40px; text-align: center; padding: 10px 12px; position: sticky; left: 0; background: var(--background-fill-primary); z-index: 2; border: 1px solid var(--border-color-primary); } .runs-table .checkbox-col::before { content: ''; position: absolute; top: 0; bottom: 0; left: -1px; width: 1px; background: var(--border-color-primary); z-index: 100; pointer-events: none; } .runs-table .checkbox-col::after { content: ''; position: absolute; top: 0; bottom: 0; right: -1px; width: 1px; background: var(--border-color-primary); z-index: 100; pointer-events: none; } .runs-table thead .checkbox-col { background: var(--background-fill-primary); z-index: 3; } .runs-table thead .checkbox-col::before, .runs-table thead .checkbox-col::after { z-index: 101; } .runs-table .name-col { position: sticky; left: 40px; background: var(--background-fill-primary); z-index: 2; border: 1px solid var(--border-color-primary); } .runs-table .name-col::before { content: ''; position: absolute; top: 0; bottom: 0; left: -1px; width: 1px; background: var(--border-color-primary); z-index: 100; pointer-events: none; } .runs-table .name-col::after { content: ''; position: absolute; top: 0; bottom: 0; right: -2px; width: 2px; background: var(--border-color-primary); z-index: 100; pointer-events: none; } .runs-table thead .name-col { background: var(--background-fill-primary); z-index: 3; } .runs-table thead .name-col::before, .runs-table thead .name-col::after { z-index: 101; } .runs-table tbody tr .checkbox-col, .runs-table tbody tr .name-col { background: var(--background-fill-primary); } .runs-table tbody tr.selected .checkbox-col, .runs-table tbody tr.selected .name-col { background: var(--color-accent-soft); } .runs-table input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; accent-color: var(--color-accent); } .runs-table input[type="checkbox"]:disabled { cursor: not-allowed; opacity: 0.5; } .runs-table a { color: var(--link-text-color); text-decoration: underline; text-decoration-style: dotted; } .runs-table a:hover { text-decoration-style: solid; } .runs-table .empty-row td { text-align: center; padding: 40px 16px; color: var(--block-label-text-color); font-style: italic; } .runs-table .col-0 { font-weight: 500; } """ js_on_load = """ function getRowCheckboxes() { return element.querySelectorAll('.row-checkbox'); } function getSelectAllCheckbox() { return element.querySelector('.select-all-checkbox'); } function updateSelectAllState() { const checkboxes = getRowCheckboxes(); const selectAll = getSelectAllCheckbox(); if (!selectAll || checkboxes.length === 0) return; const total = checkboxes.length; const checked = Array.from(checkboxes).filter(cb => cb.checked).length; if (checked === total) { selectAll.checked = true; selectAll.indeterminate = false; } else if (checked > 0) { selectAll.checked = false; selectAll.indeterminate = true; } else { selectAll.checked = false; selectAll.indeterminate = false; } } function updateRowStyles() { const checkboxes = getRowCheckboxes(); checkboxes.forEach(cb => { const row = cb.closest('tr'); if (cb.checked) { row.classList.add('selected'); } else { row.classList.remove('selected'); } }); } function updateValue() { const checkboxes = getRowCheckboxes(); props.value = Array.from(checkboxes) .filter(cb => cb.checked) .map(cb => parseInt(cb.dataset.idx, 10)); updateSelectAllState(); updateRowStyles(); trigger('input'); } element.addEventListener('change', (e) => { if (e.target.classList.contains('select-all-checkbox')) { const shouldCheck = e.target.checked; getRowCheckboxes().forEach(cb => { cb.checked = shouldCheck; }); updateValue(); } else if (e.target.classList.contains('row-checkbox')) { updateValue(); } }); updateSelectAllState(); updateRowStyles(); """ super().__init__( value=value, html_template=html_template, css_template=css_template, js_on_load=js_on_load, headers=headers, rows=rows, interactive=interactive, **kwargs, ) def api_info(self): return { "items": {"type": "integer"}, "title": "Runs Table Selected Indices", "type": "array", } if __name__ == "__main__": sample_headers = ["Name", "Group", "Username", "Created"] sample_rows = [ [ "run-001", "experiment-a", "johndoe", "2024-01-15 10:30", ], [ "run-002", "experiment-a", "janedoe", "2024-01-15 11:45", ], [ "run-003", "experiment-b", "johndoe", "2024-01-16 09:00", ], ] with gr.Blocks() as demo: gr.Markdown("## Runs Table Demo") interactive_checkbox = gr.Checkbox(label="Interactive", value=True) table = RunsTable( headers=sample_headers, rows=sample_rows, value=[], interactive=True, ) selected_output = gr.JSON(label="Selected Row Indices") table.input(lambda x: x, inputs=table, outputs=selected_output) def toggle_interactive(is_interactive): return RunsTable( headers=sample_headers, rows=sample_rows, value=[], interactive=is_interactive, ) interactive_checkbox.change( toggle_interactive, inputs=[interactive_checkbox], outputs=[table], ) demo.launch()