Skip to content

Commit 6bc4e4a

Browse files
committed
feat(ui): show data source label in header and improve export filenames
- Display loaded filename, STRING query genes, or tour name below the app header with ellipsis truncation and full-text hover tooltip - Use timestamped export filenames (YYYYMMDD-HHmmss_GLL_{source}.ext) for JSON, PNG, and data editor Excel exports; strip stacked prefixes on re-export to prevent name accumulation - Fix data editor Excel export writing internal propHashId column headers instead of bracket notation, which broke filter matching on re-import (showed 0/N nodes) - Fix conditional getters for halo and label background color to only emit values when the feature is enabled, preventing unintended activation on Excel round-trip
1 parent 2ca19c9 commit 6bc4e4a

6 files changed

Lines changed: 80 additions & 12 deletions

File tree

src/gll.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ async function loadDemoData() {
392392
const data = await stringDemoDataLoader.loadNetwork();
393393

394394
if (data) {
395+
cache.ui.setDataSourceLabel(`STRING: ${genes.join(', ')}`);
395396

396397
await cache.gcm.destroyGraphAndRollBackUI();
397398
cache.gcm.resetEventLocks();
@@ -432,6 +433,7 @@ async function loadDemoData() {
432433

433434
async function startTour() {
434435
const data = generateTourData();
436+
cache.ui.setDataSourceLabel('Tour: Sample Gene Network');
435437

436438
await cache.gcm.destroyGraphAndRollBackUI();
437439
cache.gcm.resetEventLocks();

src/graph_lens_lite.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,13 @@ <h3 id="loadingHeader">Loading ..</h3>
7676
<div class="sidebar-resize-handle"></div>
7777
<div id="sidebarContentContainer">
7878
<div class="header-row">
79-
<h3 id="appHeader" class="disabled-header" onclick="cache.ui.reloadApp()">
80-
<img inline src="delta-4-icon.png" alt="Delta4 Icon" style="height: 1em; vertical-align: middle; margin-right: 0.3em;">
81-
Graph Lens Lite <span id="versionInfo" style="font-size: 0.6em; color: #666; font-weight: normal;"></span>
82-
</h3>
79+
<div class="header-title-group">
80+
<h3 id="appHeader" class="disabled-header" onclick="cache.ui.reloadApp()">
81+
<img inline src="delta-4-icon.png" alt="Delta4 Icon" style="height: 1em; vertical-align: middle; margin-right: 0.3em;">
82+
Graph Lens Lite <span id="versionInfo" style="font-size: 0.6em; color: #666; font-weight: normal;"></span>
83+
</h3>
84+
<span id="dataSourceLabel" class="data-source-label"></span>
85+
</div>
8386
<div class="showOnLoad button-group">
8487
<button id="metricsToggleBtn" class="showOnLoad medium-btn" title="Toggle network metrics panel (M)" onclick="cache.metrics.toggleUI()">📊</button>
8588
<button id="dataToggleBtn" class="showOnLoad medium-btn" title="Toggle data editor (D)" onclick="cache.ui.toggleDataEditor()">🔢</button>

src/managers/io.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ const EXCEL_NODE_PROPERTIES = [
135135
n.style.labelBackgroundFill = v;
136136
},
137137
get: (n) => {
138-
return n.style.labelBackgroundFill;
138+
return n.style.labelBackground ? n.style.labelBackgroundFill : undefined;
139139
},
140140
},
141141
{
@@ -313,7 +313,7 @@ const EXCEL_EDGE_PROPERTIES = [
313313
e.style.labelBackgroundFill = v;
314314
},
315315
get: (e) => {
316-
return e.style.labelBackgroundFill;
316+
return e.style.labelBackground ? e.style.labelBackgroundFill : undefined;
317317
},
318318
},
319319
{
@@ -384,7 +384,7 @@ const EXCEL_EDGE_PROPERTIES = [
384384
e.style.haloStroke = v;
385385
},
386386
get: (e) => {
387-
return e.style.haloStroke;
387+
return e.style.halo ? e.style.haloStroke : undefined;
388388
},
389389
},
390390
{
@@ -1446,6 +1446,31 @@ class IOManager {
14461446
}
14471447
}
14481448

1449+
buildExportFilename(ext, suffix = '') {
1450+
const now = new Date();
1451+
const ts = now.getFullYear().toString()
1452+
+ String(now.getMonth() + 1).padStart(2, '0')
1453+
+ String(now.getDate()).padStart(2, '0')
1454+
+ '-'
1455+
+ String(now.getHours()).padStart(2, '0')
1456+
+ String(now.getMinutes()).padStart(2, '0')
1457+
+ String(now.getSeconds()).padStart(2, '0');
1458+
1459+
const label = document.getElementById('dataSourceLabel')?.textContent || '';
1460+
let basename = label
1461+
.replace(/\.[a-z0-9]+$/i, '') // strip file extension
1462+
.replace(/^(\d{8}-\d{6}_GLL_)+/, '') // strip stacked GLL prefixes
1463+
.replace(/[<>:"/\\|?*,]+/g, '_') // replace unsafe chars
1464+
.replace(/\s+/g, '_') // spaces to underscores
1465+
.replace(/_+/g, '_') // collapse multiple underscores
1466+
.replace(/^_|_$/g, ''); // trim leading/trailing underscores
1467+
1468+
if (!basename) basename = 'export';
1469+
if (suffix) basename += `_${suffix}`;
1470+
1471+
return `${ts}_GLL_${basename}.${ext}`;
1472+
}
1473+
14491474
async exportGraphAsJSON() {
14501475
if (this.cache.data === null) {
14511476
this.cache.ui.error("No graph data to save.");
@@ -1531,7 +1556,7 @@ class IOManager {
15311556
const url = URL.createObjectURL(blob);
15321557
const a = document.createElement("a");
15331558
a.href = url;
1534-
a.download = "graph-export.json";
1559+
a.download = this.buildExportFilename("json");
15351560
a.click();
15361561
URL.revokeObjectURL(url);
15371562
await this.cache.ui.hideLoading();
@@ -1654,6 +1679,8 @@ class IOManager {
16541679
let file = event.target.files[0];
16551680
if (!file) return;
16561681

1682+
this.cache.ui.setDataSourceLabel(file.name);
1683+
16571684
await this.cache.ui.showLoading(
16581685
"Loading",
16591686
`Loading ${file.name} (${file.type} with ${StaticUtilities.humanFileSize(file.size)})`,
@@ -1729,7 +1756,7 @@ class IOManager {
17291756

17301757
const link = document.createElement("a");
17311758
link.href = imageData;
1732-
link.download = "graph-export.png";
1759+
link.download = this.buildExportFilename("png");
17331760
document.body.appendChild(link);
17341761
link.click();
17351762
document.body.removeChild(link);

src/managers/ui.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ class UIManager {
1010
this.bottomBarHeight = null;
1111
}
1212

13+
setDataSourceLabel(text) {
14+
const label = document.getElementById('dataSourceLabel');
15+
if (label) {
16+
label.textContent = text;
17+
label.title = text;
18+
}
19+
}
20+
1321
async showLoading(header, text = "") {
1422
const overlay = document.getElementById('loadingOverlay');
1523
overlay.style.display = 'flex';

src/style.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,10 +1249,30 @@ hr {
12491249
margin-bottom: 0.6em;
12501250
}
12511251

1252+
.header-title-group {
1253+
display: flex;
1254+
flex-direction: column;
1255+
min-width: 0;
1256+
}
1257+
12521258
.header-row h3 {
12531259
margin: 0;
12541260
}
12551261

1262+
.data-source-label {
1263+
font-size: 0.7em;
1264+
color: #888;
1265+
white-space: nowrap;
1266+
overflow: hidden;
1267+
text-overflow: ellipsis;
1268+
max-width: 200px;
1269+
line-height: 1.2;
1270+
}
1271+
1272+
.data-source-label:empty {
1273+
display: none;
1274+
}
1275+
12561276
.button-group {
12571277
display: inline-flex;
12581278
background: #403C5320;

src/utilities/data_editor.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,9 +1185,16 @@ class DataTable {
11851185

11861186
const positions = this.cache.data.layouts[this.cache.data.selectedLayout]?.positions;
11871187

1188+
const propIdToExcelHeader = (propId) => {
1189+
const [, subGroup, key] = StaticUtilities.decodePropHashId(propId);
1190+
return subGroup === this.cache.CFG.EXCEL_UNCATEGORIZED_SUBHEADER
1191+
? key
1192+
: `${key} [${subGroup}]`;
1193+
};
1194+
11881195
if (nodesToExport.length > 0) {
11891196
const nodesSheet = workbook.addWorksheet('nodes');
1190-
const nodesHeader = [...EXCEL_NODE_PROPERTIES.map(p => p.column), ...this.cache.nodeExclusiveProps];
1197+
const nodesHeader = [...EXCEL_NODE_PROPERTIES.map(p => p.column), ...[...this.cache.nodeExclusiveProps].map(propIdToExcelHeader)];
11911198
nodesSheet.addRow(nodesHeader);
11921199

11931200
for (const node of nodesToExport) {
@@ -1217,7 +1224,7 @@ class DataTable {
12171224

12181225
if (edgesToExport.length > 0) {
12191226
const edgesSheet = workbook.addWorksheet('edges');
1220-
const edgesHeader = [...EXCEL_EDGE_PROPERTIES.map(p => p.column), ...this.cache.edgeExclusiveProps];
1227+
const edgesHeader = [...EXCEL_EDGE_PROPERTIES.map(p => p.column), ...[...this.cache.edgeExclusiveProps].map(propIdToExcelHeader)];
12211228
edgesSheet.addRow(edgesHeader);
12221229

12231230
for (const edge of edgesToExport) {
@@ -1249,7 +1256,8 @@ class DataTable {
12491256
const url = window.URL.createObjectURL(blob);
12501257
const link = document.createElement('a');
12511258
link.href = url;
1252-
link.download = `graph_data_export_${this.currentTab}_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.xlsx`;
1259+
const suffix = this.currentTab === 'entireGraph' ? '' : this.currentTab;
1260+
link.download = this.cache.io.buildExportFilename("xlsx", suffix);
12531261

12541262
document.body.appendChild(link);
12551263
link.click();

0 commit comments

Comments
 (0)