Skip to content

Commit 6e00a7d

Browse files
Ruibin LiuRuibin Liu
authored andcommitted
Add CSV export and delete button for history items
1 parent 1e255eb commit 6e00a7d

1 file changed

Lines changed: 228 additions & 10 deletions

File tree

index.html

Lines changed: 228 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,19 @@
161161
font-weight: 600;
162162
}
163163

164-
.modal-header .close-btn {
165-
background: none;
166-
border: none;
167-
font-size: 1.5rem;
168-
cursor: pointer;
169-
padding: 0 0.5rem;
170-
line-height: 1;
171-
color: var(--text);
172-
}
164+
.modal-header .close-btn {
165+
background: none;
166+
border: none;
167+
font-size: 1.5rem;
168+
cursor: pointer;
169+
padding: 0 0.5rem;
170+
line-height: 1;
171+
color: var(--text);
172+
}
173+
174+
.modal-header .action-btn {
175+
font-size: 0.85rem;
176+
}
173177

174178
.modal-header .copy-btn {
175179
background: var(--accent);
@@ -280,13 +284,44 @@
280284
border-color: #22c55e;
281285
}
282286

287+
#historyList > div {
288+
position: relative;
289+
}
290+
283291
#historyList > div:hover {
284292
background: #f8fafc;
285293
border-color: var(--accent);
286294
transform: translateY(-1px);
287295
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
288296
}
289297

298+
.history-item-delete {
299+
position: absolute;
300+
top: 0.5rem;
301+
right: 0.5rem;
302+
background: none;
303+
border: none;
304+
cursor: pointer;
305+
padding: 0.25rem;
306+
border-radius: 4px;
307+
opacity: 0;
308+
transition: opacity 0.15s ease;
309+
color: var(--muted);
310+
}
311+
312+
#historyList > div:hover .history-item-delete {
313+
opacity: 1;
314+
}
315+
316+
.history-item-delete:hover {
317+
background: #fee2e2;
318+
color: #dc2626;
319+
}
320+
321+
#historyList > div > div {
322+
padding-right: 1.5rem;
323+
}
324+
290325
.action-buttons {
291326
display: flex;
292327
gap: 0.75rem;
@@ -355,6 +390,17 @@
355390
border-color: #16a34a;
356391
}
357392

393+
.action-btn.export {
394+
background: linear-gradient(135deg, #fef9c3 0%, #fef08a 100%);
395+
border-color: #eab308;
396+
color: #ca8a04;
397+
}
398+
399+
.action-btn.export:hover {
400+
background: linear-gradient(135deg, #fef08a 0%, #fde047 100%);
401+
border-color: #ca8a04;
402+
}
403+
358404
.drop {
359405
margin-top: 0.75rem;
360406
border: 1px dashed var(--border);
@@ -1224,6 +1270,12 @@ <h1>Molecule Clipboard</h1>
12241270
} else {
12251271
historyList.innerHTML = compoundHistory.map((compound, index) => `
12261272
<div style="border: 1px solid var(--border); border-radius: 8px; padding: 1rem; cursor: pointer; transition: all 0.15s ease;" onclick="loadFromHistory(${index})">
1273+
<button class="history-item-delete" onclick="event.stopPropagation(); deleteFromHistory(${index})" title="Delete from history">
1274+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
1275+
<line x1="18" y1="6" x2="6" y2="18"/>
1276+
<line x1="6" y1="6" x2="18" y2="18"/>
1277+
</svg>
1278+
</button>
12271279
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
12281280
<div>
12291281
<strong style="display: block; margin-bottom: 0.25rem;">${compound.name}</strong>
@@ -1244,6 +1296,156 @@ <h1>Molecule Clipboard</h1>
12441296
document.getElementById('historyModal').style.display = 'none';
12451297
}
12461298

1299+
async function exportHistoryToCSV() {
1300+
if (compoundHistory.length === 0) {
1301+
alert('No history to export.');
1302+
return;
1303+
}
1304+
1305+
const headers = [
1306+
'Name',
1307+
'SMILES',
1308+
'InChI',
1309+
'InChIKey',
1310+
'MW',
1311+
'cLogP',
1312+
'TPSA',
1313+
'HBD',
1314+
'HBA',
1315+
'RotB',
1316+
'Exact MW',
1317+
'Heavy Atoms',
1318+
'Heteroatoms',
1319+
'Total Rings',
1320+
'Aromatic Rings',
1321+
'Aliphatic Rings',
1322+
'Fsp3',
1323+
'Labute ASA',
1324+
'Lipinski',
1325+
'Veber',
1326+
'Egan',
1327+
'Ghose',
1328+
'MCE-18',
1329+
'REOS',
1330+
'High LogP',
1331+
'High TPSA',
1332+
'Too Flexible',
1333+
'Low Fsp3',
1334+
'Too Many Rings',
1335+
'Complex Stereo',
1336+
'Timestamp'
1337+
];
1338+
1339+
let csvContent = headers.join(',') + '\n';
1340+
1341+
for (const compound of compoundHistory) {
1342+
try {
1343+
const m = rdkitModule.get_mol(compound.smiles);
1344+
if (!m || !m.is_valid()) {
1345+
csvContent += compound.name + ',ERROR,,,,,,,,,,,,,,,,,,,,,,,,,,,' + compound.timestamp + '\n';
1346+
continue;
1347+
}
1348+
1349+
const inchi = m.get_inchi();
1350+
const inchikey = rdkitModule.get_inchikey_for_inchi(inchi);
1351+
const d = JSON.parse(m.get_descriptors());
1352+
1353+
const lipinski =
1354+
d.amw <= 500 &&
1355+
d.CrippenClogP <= 5 &&
1356+
d.NumHBD <= 5 &&
1357+
d.NumHBA <= 10;
1358+
1359+
const veber =
1360+
d.tpsa <= 140 &&
1361+
d.NumRotatableBonds <= 10;
1362+
1363+
const egan = d.tpsa <= 132;
1364+
1365+
const ghose =
1366+
d.amw >= 160 && d.amw <= 480 &&
1367+
d.CrippenClogP >= -0.4 && d.CrippenClogP <= 5.6 &&
1368+
d.NumRings >= 1 && d.NumRings <= 5 &&
1369+
d.NumAtoms >= 20 && d.NumAtoms <= 70 &&
1370+
d.NumHeteroatoms >= 2 && d.NumHeteroatoms <= 15;
1371+
1372+
const mce18 =
1373+
d.amw <= 450 &&
1374+
d.CrippenClogP <= 5.3 &&
1375+
d.tpsa <= 131 &&
1376+
d.NumHBD <= 3 &&
1377+
d.NumHBA <= 6 &&
1378+
d.NumRotatableBonds <= 9;
1379+
1380+
const reos =
1381+
d.CrippenClogP > 0 && d.CrippenClogP < 5 &&
1382+
d.amw < 500 &&
1383+
d.NumHBD < 4 &&
1384+
d.NumHBA < 9;
1385+
1386+
const alertHighLogP = d.CrippenClogP > 5;
1387+
const alertHighTPSA = d.tpsa > 140;
1388+
const alertTooFlexible = d.NumRotatableBonds > 10;
1389+
const alertLowFsp3 = d.FractionCSP3 < 0.25;
1390+
const alertTooManyRings = d.NumRings > 5;
1391+
const alertStereo = d.NumAtomStereoCenters > 3;
1392+
1393+
const row = [
1394+
compound.name,
1395+
compound.smiles,
1396+
inchi,
1397+
inchikey,
1398+
d.amw.toFixed(2),
1399+
d.CrippenClogP.toFixed(2),
1400+
d.tpsa.toFixed(2),
1401+
d.NumHBD,
1402+
d.NumHBA,
1403+
d.NumRotatableBonds,
1404+
d.exactmw.toFixed(2),
1405+
d.NumHeavyAtoms,
1406+
d.NumHeteroatoms,
1407+
d.NumRings,
1408+
d.NumAromaticRings,
1409+
d.NumAliphaticRings,
1410+
d.FractionCSP3.toFixed(3),
1411+
d.labuteASA.toFixed(2),
1412+
lipinski ? 'Pass' : 'Fail',
1413+
veber ? 'Pass' : 'Fail',
1414+
egan ? 'Pass' : 'Fail',
1415+
ghose ? 'Pass' : 'Fail',
1416+
mce18 ? 'Pass' : 'Fail',
1417+
reos ? 'Pass' : 'Fail',
1418+
alertHighLogP ? 'Alert' : 'OK',
1419+
alertHighTPSA ? 'Alert' : 'OK',
1420+
alertTooFlexible ? 'Alert' : 'OK',
1421+
alertLowFsp3 ? 'Alert' : 'OK',
1422+
alertTooManyRings ? 'Alert' : 'OK',
1423+
alertStereo ? 'Alert' : 'OK',
1424+
compound.timestamp
1425+
].map(field => {
1426+
const s = String(field);
1427+
if (s.includes(',') || s.includes('"') || s.includes('\n')) {
1428+
return '"' + s.replace(/"/g, '""') + '"';
1429+
}
1430+
return s;
1431+
}).join(',');
1432+
1433+
csvContent += row + '\n';
1434+
m.delete();
1435+
} catch (e) {
1436+
csvContent += compound.name + ',ERROR,,,,,,,,,,,,,,,,,,,,,,,,,,,' + compound.timestamp + '\n';
1437+
}
1438+
}
1439+
1440+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
1441+
const url = URL.createObjectURL(blob);
1442+
const a = document.createElement('a');
1443+
a.href = url;
1444+
a.download = 'molecule_history_' + new Date().toISOString().slice(0,10) + '.csv';
1445+
a.click();
1446+
URL.revokeObjectURL(url);
1447+
}
1448+
12471449
function loadFromHistory(index) {
12481450
const compound = compoundHistory[index];
12491451
if (compound) {
@@ -1254,6 +1456,14 @@ <h1>Molecule Clipboard</h1>
12541456
}
12551457
}
12561458

1459+
function deleteFromHistory(index) {
1460+
if (index >= 0 && index < compoundHistory.length) {
1461+
compoundHistory.splice(index, 1);
1462+
saveHistory();
1463+
showHistory();
1464+
}
1465+
}
1466+
12571467
function clearHistory() {
12581468
if (confirm('Are you sure you want to clear all history?')) {
12591469
compoundHistory = [];
@@ -1302,7 +1512,15 @@ <h2>JSME Editor</h2>
13021512
<div class="modal-content">
13031513
<div class="modal-header">
13041514
<h2>History</h2>
1305-
<div>
1515+
<div style="display: flex; gap: 0.5rem; align-items: center;">
1516+
<button onclick="exportHistoryToCSV()" class="action-btn export">
1517+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1518+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
1519+
<polyline points="7,10 12,15 17,10"/>
1520+
<line x1="12" y1="15" x2="12" y2="3"/>
1521+
</svg>
1522+
Export CSV
1523+
</button>
13061524
<button onclick="clearHistory()" class="action-btn clear">
13071525
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
13081526
<polyline points="3,6 5,6 21,6"/>

0 commit comments

Comments
 (0)