Skip to content

Commit

Permalink
WIP: #205 better support for multiple EMME python versions
Browse files Browse the repository at this point in the history
  • Loading branch information
e-halinen committed Oct 10, 2024
1 parent 0b93119 commit 2564e63
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 9 deletions.
21 changes: 20 additions & 1 deletion src/renderer/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ const {execSync} = require('child_process');
const path = require('path');

// vex-js imported globally in index.html, since we cannot access webpack config in electron-forge

const App = ({helmetUIVersion, versions, searchEMMEPython}) => {

// Global settings
const [isSettingsOpen, setSettingsOpen] = useState(false); // whether Settings -dialog is open
const [isProjectRunning, setProjectRunning] = useState(false); // whether currently selected Project is running
const [emmePythonPath, setEmmePythonPath] = useState(undefined); // file path to EMME python executable
const [emmePythonEnvs, setEmmePythonEnvs] = useState([]); // List of all discovered python executables
const [helmetScriptsPath, setHelmetScriptsPath] = useState(undefined); // folder path to HELMET model system scripts
const [projectPath, setProjectPath] = useState(undefined); // folder path to scenario configs, default homedir
const [basedataPath, setBasedataPath] = useState(undefined); // folder path to base input data (subdirectories: 2016_zonedata, 2016_basematrices)
Expand All @@ -30,6 +30,20 @@ const App = ({helmetUIVersion, versions, searchEMMEPython}) => {
globalSettingsStore.current.set('emme_python_path', newPath);
};

const _setEMMEPythonEnvs = (newList) => {
setEmmePythonEnvs(newList);
globalSettingsStore.current.set('emme_python_envs', newList);
}

const _removeFromEMMEPythonEnvs = (path) => {
const pythonEnvs = emmePythonEnvs.slice();
const foundPath = pythonEnvs.indexOf(path);
if (foundPath > -1) {
pythonEnvs.splice(foundPath,1);
_setEMMEPythonEnvs(pythonEnvs);
}
}

const _setHelmetScriptsPath = (newPath) => {
// Cannot use state variable since it'd be undefined at times
const pythonPath = globalSettingsStore.current.get('emme_python_path');
Expand Down Expand Up @@ -121,11 +135,13 @@ const App = ({helmetUIVersion, versions, searchEMMEPython}) => {
const existingProjectPath = globalSettingsStore.current.get('project_path');
const existingBasedataPath = globalSettingsStore.current.get('basedata_path');
const existingResultsPath = globalSettingsStore.current.get('resultdata_path');
const existingPythonEnvs = globalSettingsStore.current.get('emme_python_envs');
setEmmePythonPath(existingEmmePythonPath);
setHelmetScriptsPath(existingHelmetScriptsPath);
setProjectPath(existingProjectPath);
setBasedataPath(existingBasedataPath);
setResultsPath(existingResultsPath);
setEmmePythonEnvs(existingPythonEnvs);

// If project path is the initial (un-set), set it to homedir. Remember: state updates async so refer to existing.
if (!existingProjectPath) {
Expand Down Expand Up @@ -159,6 +175,7 @@ const App = ({helmetUIVersion, versions, searchEMMEPython}) => {
<div className="App__settings" style={{display: isSettingsOpen ? "block" : "none"}}>
<Settings
emmePythonPath={emmePythonPath}
emmePythonEnvs={emmePythonEnvs}
helmetScriptsPath={helmetScriptsPath}
projectPath={projectPath}
basedataPath={basedataPath}
Expand All @@ -167,6 +184,8 @@ const App = ({helmetUIVersion, versions, searchEMMEPython}) => {
isDownloadingHelmetScripts={isDownloadingHelmetScripts}
closeSettings={() => setSettingsOpen(false)}
setEMMEPythonPath={_setEMMEPythonPath}
setEMMEPythonEnvs={_setEMMEPythonEnvs}
removeFromEMMEPythonEnvs={_removeFromEMMEPythonEnvs}
setHelmetScriptsPath={_setHelmetScriptsPath}
setProjectPath={_setProjectPath}
setBasedataPath={_setBasedataPath}
Expand Down
28 changes: 28 additions & 0 deletions src/renderer/components/Settings/Settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,31 @@
height: 40px;
font-size: 16px;
}

.Settings__environment_option {
display: flex;
width: 100%;
padding: 10px 12px;
margin-top: 4px;
background: #ffffff;
box-sizing: border-box;
border-radius: 5px;
font-size: 16px;
font-weight: 325;
color: #333333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: inherit;
min-height: 2rem;
align-items: center;
justify-items: center;
flex-wrap: wrap;
}

.Settings__env_option_btn {
width: 4rem;
height: 2.5rem;
border-radius: 5px;
margin-left: 1.5rem;
}
33 changes: 25 additions & 8 deletions src/renderer/components/Settings/Settings.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import React from 'react';
import path from "path";
const {dialog} = require('@electron/remote');
import {searchEMMEPython} from './search_emme_pythonpath';
import versions from '../versions';
import { listEMMEPythonPaths } from './search_emme_pythonpath';

const EnvironmentOption = ({
envPath, isSelected, setPath, removePath,
}) => {
return (
<div className="Settings__environment_option">
<p>{envPath}</p>
<button className="Settings__env_option_btn" disabled={isSelected} onClick={() => setPath(envPath)}>Valitse</button>
<button className="Settings__env_option_btn" onClick={() => removePath(envPath)}>Poista</button>
</div>
)
}

const Settings = ({
emmePythonPath, setEMMEPythonPath,
emmePythonPath, emmePythonEnvs, setEMMEPythonPath, setEMMEPythonEnvs, removeFromEMMEPythonEnvs,
helmetScriptsPath, setHelmetScriptsPath, dlHelmetScriptsVersion, isDownloadingHelmetScripts,
projectPath, setProjectPath,
basedataPath, setBasedataPath,
Expand All @@ -24,7 +36,12 @@ const Settings = ({

<div className="Settings__dialog-heading">Projektin asetukset</div>
<div className="Settings__dialog-input-group">
<span className="Settings__pseudo-label">Emme Python v3.7</span>
<span className="Settings__pseudo-label">Valittu Python-ympäristö: {emmePythonPath}</span>
<span className="Settings__pseudo-label">Käytettävät Python-ympäristöt:</span>
{ emmePythonEnvs.map(env => { return <EnvironmentOption envPath={env} isSelected={emmePythonPath === env}
setPath={setEMMEPythonPath}
removePath={removeFromEMMEPythonEnvs} />}) }
<br/>
<label className="Settings__pseudo-file-select" htmlFor="hidden-input-emme-python-path" title={emmePythonPath}>
{emmePythonPath ? path.basename(emmePythonPath) : "Valitse.."}
</label>
Expand All @@ -46,18 +63,18 @@ const Settings = ({
})
}}
/>

<button className="Settings__input-btn"
onClick={(e) => {
const [found, pythonPath] = searchEMMEPython();
const [found, pythonPaths] = listEMMEPythonPaths();
if (found) {
if (confirm(`Python ${versions.emme_python} löytyi sijainnista:\n\n${pythonPath}\n\nHaluatko käyttää tätä sijaintia?`)) {
setEMMEPythonPath(pythonPath);
}
alert(`Python-ympäristöjä löytyi. Valitse listasta oikea EMME Python-ympäristö ja ota sen jälkeen käyttöön ${pythonPaths}`)
setEMMEPythonEnvs(pythonPaths);
} else {
alert(`Emme ${versions.emme_system} ja Python ${versions.emme_python} eivät löytyneet oletetusta sijainnista.\n\nSyötä Pythonin polku manuaalisesti.`);
}}}
>
Hae Python automaattisesti
Etsi Python-ympäristöjä
</button>
</div>
<div className="Settings__dialog-input-group">
Expand Down
38 changes: 38 additions & 0 deletions src/renderer/search_emme_pythonpath.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,43 @@ const searchEMMEPython = () => {
}
};

const listEMMEPythonPaths = () => {
// Set Windows' python exe path postfix (e.g. Python27\python.exe)
const p = getVersion(versions.emme_python);
const pythonPathPostfix = `Python${p.major}${p.minor}\\python.exe`;

// Search from environment variable "EMMEPATH"
const envEmmePath = process.env.EMMEPATH || '';
const envEmmePythonPath = path.join(envEmmePath, pythonPathPostfix);
if (envEmmePath && fs.existsSync(envEmmePythonPath)) {
return [true, envEmmePythonPath];
}

// Not found based on EMMEPATH, try guessing some common locations on Windows
const e = getVersion(versions.emme_system);
const pythonVersion = getVersion(versions.emme_python);
const commonEmmePath = `INRO\\Emme\\Emme ${e.major}\\Emme-${e.semver}`;
const drives = ['C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:', '/'];
const paths = [
`\\Program Files\\${commonEmmePath}\\${pythonPathPostfix}`,
`\\Program Files (x86)\\${commonEmmePath}\\${pythonPathPostfix}`,
`\\${commonEmmePath}\\${pythonPathPostfix}`,
`usr/bin/python${pythonVersion.major}`, // mainly for developers on Mac & Linux
`Users/erkki/pyyttoni/testi/testikansio/testiloremipsum2.34.5`,
];
const allPathCombinations = drives.reduce(
(accumulator, d) => {
// Combine each (d)rive to all (p)aths, and merge results via reduce
return accumulator.concat(paths.map((p) => `${d}${p}`));
}, []);
const pythonInstallations = allPathCombinations.filter(fs.existsSync);
if (pythonInstallations.length > 0) {
return [true, pythonInstallations];
} else {
return [false, null];
}
}

/**
* Dissect given semantic version number string
*/
Expand All @@ -56,4 +93,5 @@ function getVersion(semver) {

module.exports = {
searchEMMEPython: searchEMMEPython,
listEMMEPythonPaths: listEMMEPythonPaths,
};

0 comments on commit 2564e63

Please sign in to comment.