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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Molden files are checked before loading. The g-xTB binary's molden writer (tblite) puts the valence electron count in the atomic-number column of `[Atoms]`, and JSmol trusts that column over the element label, so such files showed up as boron/beryllium/carbon with garbled bonds. A dialog now reports the problem and offers "Fix and load", "Fix, load and save…" (writes a `<name>-fixed.molden` copy through the save dialog) or "Load as is". The check also reports, without fixing, an MO coefficient count that does not match the basis size (the usual sign of a missing `[5D]` flag). Files given on the command line go through the same check.

### Changed

- ElemCo.jl and xtb calculations started from a terminal now run in the directory jlmol was started from, so exported files (`orbitals.molden` from an Export step, `xtbopt.xyz`, `xtbopt.log`, ...) and the generated `calculation.jl` / `coord.xyz` stay there. Previously every run used a temporary directory under the OS temp dir that was deleted when the run finished, so exports silently vanished. Launches without a terminal (desktop launcher, Finder, Start menu) keep the temporary directory. The output panel now prints which directory is used and whether it is deleted. The directory comes from `--workdir=PATH` (the `npm start` scripts pass the directory the command was typed in, also from WSL running the Windows binary) or, when started from a terminal, the current directory.
- Image export and XYZ export in the desktop app now open a save dialog instead of dropping the file silently into the download folder. The dialog defaults to the launch directory when started from a terminal and to the download folder otherwise; the status line shows where the file was saved, or that the export was cancelled.
- "Choose File" in the desktop app is replaced by an "Open File…" button with a native open dialog that starts in the launch directory when there is one (the browser file picker cannot be pointed at a directory, and its "No file chosen" label would stay stale). The loaded file's name is shown next to the button. Without a launch directory the OS chooses the folder as before. The browser build keeps the file input.

## [1.5.5] - 2026-09-07

### Fixed
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ npm install
npm start
```

`npm start` passes the directory you typed it in to the app as `--workdir` (npm itself changes to the package root), so calculations run there and the export dialog defaults to it. Under WSL this also works when `electron` resolves to the Windows binary: the script passes `$WSL_DISTRO_NAME` and the path is mapped to `\\wsl.localhost\<distro>\...` (or `C:\...` for `/mnt/c/...`). For any other start, pass `--workdir=/abs/path` explicitly, or start the app from a terminal, in which case the current directory is used.

The terminal output is kept quiet by default. A full log is always written to `jsmol.log` in the app's user-data directory. To echo the detailed log to the terminal, run with verbose logging:

```bash
Expand Down Expand Up @@ -291,6 +293,7 @@ Run [xtb](https://github.com/grimme-lab/xtb) g-xTB calculations on the current s
- Set charge, number of unpaired electrons (`--uhf`), and extra xtb flags
- Configurable xtb command (supports WSL) in Settings → xtb
- Browser mode shows a message to install jlmol locally; a clear message is shown if xtb is not accessible
- Working directory: when jlmol is started from a terminal, xtb runs in the directory it was started from, so `coord.xyz`, `xtbopt.xyz`, `xtbopt.log` and the other xtb output files stay there. Started from a desktop launcher, xtb runs in a temporary directory that is deleted after the run. The output panel prints which one is used.
- Requires the g-xTB binary from [grimme-lab/g-xtb](https://github.com/grimme-lab/g-xtb). At the moment that is all you need — download it, extract it, and make sure the `xtb` binary in its `bin` folder is on your `PATH` (e.g. a symbolic link in `~/bin`) or set its full path in Settings → xtb. No separate xtb installation or parameter-file download is required.

## ElemCo.jl Integration
Expand Down Expand Up @@ -340,6 +343,8 @@ jlmol now includes an integrated calculation runner that can execute ElemCo.jl c
3. **Run Calculation**: Click "Run Calculation" to execute the calculation
4. **View Results**: Monitor progress and view calculation output in real-time

When jlmol is started from a terminal, the calculation runs in the directory it was started from: `calculation.jl` and every file the calculation writes (e.g. `orbitals.molden` from an Export step) stay there. Started from a desktop launcher, it runs in a temporary directory that is deleted after the run. The output panel prints which one is used. Image and XYZ exports open a save dialog; its default folder is the launch directory from a terminal and the download folder otherwise, and the status line shows where the file was saved. "Open File…" opens in the launch directory as well.

#### WSL (Windows Subsystem for Linux) Support

For Windows users running Julia under WSL, jlmol provides seamless integration:
Expand Down
35 changes: 33 additions & 2 deletions css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,26 @@
margin-bottom: 8px; /* Add space below the file input */
display: block; /* Ensure it's on its own line */
}
#structureFile[hidden] {
display: none; /* the id rule above would beat the hidden attribute */
}
/* Desktop app: replaces the file input (see js/search.js) */
#structureFileNative {
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
}
#structureFileNative[hidden] {
display: none;
}
#structureFileName {
font-size: 12px;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sample-buttons {
margin-top: 4px; /* Add space above the sample buttons */
}
Expand Down Expand Up @@ -1342,7 +1362,7 @@
}

/* ---- Update-available dialog (js/updater.js) ---- */
#updateOverlay {
#updateOverlay, #moldenCheckOverlay {
position: fixed;
top: 0;
left: 0;
Expand All @@ -1355,7 +1375,7 @@
z-index: 100000;
}

#updateDialog {
#updateDialog, #moldenCheckDialog {
background: white;
border: 1px solid #ccc;
border-radius: 8px;
Expand Down Expand Up @@ -1423,6 +1443,17 @@
text-decoration: underline;
}

.molden-check-issues {
margin: 8px 0 0;
padding-left: 20px;
font-size: 13px;
line-height: 1.4;
color: #333;
}
.molden-check-issues li {
margin-bottom: 6px;
}

.update-dialog-footer {
padding: 12px 18px;
border-top: 1px solid #eee;
Expand Down
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<script type="text/javascript" src="js/image-export.js"></script>
<script type="text/javascript" src="js/molecule-data.js"></script>
<script type="text/javascript" src="js/viewer-controls.js"></script>
<script type="text/javascript" src="js/molden-check.js"></script>
<script type="text/javascript" src="js/search.js"></script>
<script type="text/javascript" src="js/orbitals.js"></script>
<script type="text/javascript" src="js/xyz-editor.js"></script>
Expand All @@ -62,6 +63,10 @@
<div id="version-display" style="font-size: 12px; color: #666; margin-bottom: 16px; padding: 4px 8px; background: #f5f5f5; border-radius: 4px; text-align: center;">jlmol v<span id="version-number">loading...</span></div>
<h3>Load Structure</h3>
<input type="file" id="structureFile" accept=".xyz,.pdb,.mol,.cif,.molden" onChange="handleFileSelect(event)" title="Select a molecular structure file">
<div id="structureFileNative" hidden>
<button id="structureFileButton" title="Select a molecular structure file">Open File…</button>
<span id="structureFileName"></span>
</div>
<div class="sample-buttons">
<button onclick="loadSample('caffeine.mol')" title="Load a caffeine molecule structure">Caffeine</button>
<button onclick="loadSample('buckyball.mol')" title="Load a buckminsterfullerene (C60) structure">Buckyball</button>
Expand Down
35 changes: 28 additions & 7 deletions js/elemco.js
Original file line number Diff line number Diff line change
Expand Up @@ -1234,12 +1234,16 @@ async function runJuliaInElectron(juliaCode) {
const prefs = getPreferences();
const juliaCommand = prefs.juliaCommand || 'julia';

// Started from a terminal: run in that directory, so @export_molden and
// friends write next to the user and nothing is deleted afterwards.
// Otherwise a temp work dir, removed after the run.
let workDir = null;
let ownsWorkDir = false;
function cleanupTempFile() {
if (workDir) {
if (workDir && ownsWorkDir) {
native.removeWorkDir(workDir); // main also reaps on quit
workDir = null;
}
workDir = null;
}

try {
Expand Down Expand Up @@ -1273,8 +1277,14 @@ async function runJuliaInElectron(juliaCode) {
const commandParts = parseCommand(juliaCommand.trim());
const isWSL = commandParts[0].toLowerCase() === 'wsl';

// Julia code goes into a scoped work directory
workDir = await native.mkWorkDir('jlmol_julia_');
// Julia code goes into the work directory
const launch = await native.launchDir();
if (launch) {
workDir = launch.token;
} else {
workDir = await native.mkWorkDir('jlmol_julia_');
ownsWorkDir = true;
Comment on lines +1281 to +1286
}
const workDirPath = await native.workDirPath(workDir);
await native.writeFile(workDir, 'calculation.jl', juliaCode);
const sep = native.platform === 'win32' ? '\\' : '/';
Expand All @@ -1283,15 +1293,23 @@ async function runJuliaInElectron(juliaCode) {
// For WSL, convert Windows path to WSL path
let filePathForCommand = tempFile;
if (isWSL) {
if (/^[A-Za-z]:/.test(tempFile)) {
const unc = /^\\\\(?:wsl\.localhost|wsl\$)\\[^\\]+(\\.*)$/.exec(tempFile);
if (unc) {
// \\wsl.localhost\<distro>\home\... is the distro's own tree
// (the launch dir when started from WSL): drop the share.
filePathForCommand = unc[1].replace(/\\/g, '/');
} else if (/^[A-Za-z]:/.test(tempFile)) {
const driveLetter = tempFile.charAt(0).toLowerCase();
filePathForCommand = tempFile.replace(/^[A-Za-z]:/, `/mnt/${driveLetter}`).replace(/\\/g, '/');
} else {
filePathForCommand = tempFile.replace(/\\/g, '/');
}
}

outputTextarea.value = `=== JLMol Julia Calculation ===\nTimestamp: ${new Date().toISOString()}\nJulia command: ${juliaCommand}\nTemporary file: ${tempFile}\n${isWSL ? `WSL path: ${filePathForCommand}\n` : ''}Full command: ${juliaCommand} "${filePathForCommand}"\n\n--- Starting calculation ---\n`;
const workDirNote = ownsWorkDir
? `Working directory (temporary, deleted after the run): ${workDirPath}`
: `Working directory (started from here; exported files stay here): ${workDirPath}`;
outputTextarea.value = `=== JLMol Julia Calculation ===\nTimestamp: ${new Date().toISOString()}\nJulia command: ${juliaCommand}\n${workDirNote}\nInput file: ${tempFile}\n${isWSL ? `WSL path: ${filePathForCommand}\n` : ''}Full command: ${juliaCommand} "${filePathForCommand}"\n\n--- Starting calculation ---\n`;
setStatusText('Starting Julia process...');

// Prepare command and arguments for version check
Expand Down Expand Up @@ -1375,8 +1393,11 @@ async function runJuliaInElectron(juliaCode) {
close: (code) => {
updateOutput();
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
const cleanupNote = ownsWorkDir
? '--- Cleanup: temporary work directory deleted ---'
: `--- Files written by the calculation are in ${workDirPath} ---`;
cleanupTempFile();
outputTextarea.value += `\n--- Cleanup: Temporary file deleted ---\n`;
outputTextarea.value += `\n${cleanupNote}\n`;
if (code === 0) {
outputTextarea.value += `\n=== CALCULATION COMPLETED SUCCESSFULLY ===\nElapsed time: ${elapsed} seconds\nExit code: ${code}`;
setStatusText(`Julia calculation completed successfully (${elapsed}s)`);
Expand Down
10 changes: 10 additions & 0 deletions js/image-export.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,13 @@ function downloadImage(imageData, filename, format, transparent) {
const transparencyNote = (format === 'png' && transparent) ? ' with transparent background' : '';
setStatusText(`${format.toUpperCase()} image exported successfully${transparencyNote}`);
}

// Desktop app: exports go through a save dialog and main reports the
// outcome; show the real path (or the cancel) instead of the guess above.
if (window.jlmolNative && window.jlmolNative.onDownloadDone) {
window.jlmolNative.onDownloadDone((state, savedPath) => {
if (state === 'completed') setStatusText(`Saved ${savedPath}`);
else if (state === 'cancelled') setStatusText('Export cancelled');
else setStatusText(`Export failed (${state})`);
});
}
Loading
Loading