diff --git a/.prettierrc b/.prettierrc index 7f21cfac..bfb431bf 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,7 +2,7 @@ "overrides": [ { "files": [".prettierrc", ".babelrc", ".eslintrc", ".stylelintrc"], - "options": { + "options": { "parser": "json" } } diff --git a/e2e/helpers/cleanupOutput.js b/e2e/helpers/cleanupOutput.js index 66421336..217ef8df 100644 --- a/e2e/helpers/cleanupOutput.js +++ b/e2e/helpers/cleanupOutput.js @@ -22,9 +22,9 @@ async function cleanupOutputFiles(outputDir, outputId) { .filter( (entry) => entry.includes(outputId) || - entry.startsWith('RAxML_GUI_ModelTest_nucleotide') + entry.startsWith('RAxML_GUI_ModelTest_nucleotide'), ) - .map((entry) => fs.rm(path.join(outputDir, entry), { force: true })) + .map((entry) => fs.rm(path.join(outputDir, entry), { force: true })), ); } diff --git a/e2e/helpers/waitForOutput.js b/e2e/helpers/waitForOutput.js index 6344e805..ab016ac5 100644 --- a/e2e/helpers/waitForOutput.js +++ b/e2e/helpers/waitForOutput.js @@ -24,7 +24,9 @@ async function waitForOutputFile(filePaths, { timeout = 10 * 60 * 1000 } = {}) { await new Promise((resolve) => setTimeout(resolve, 1000)); } - throw new Error(`Timed out waiting for output file: ${candidates.join(' or ')}`); + throw new Error( + `Timed out waiting for output file: ${candidates.join(' or ')}`, + ); } module.exports = { diff --git a/e2e/modeltest-then-raxml-ng.spec.js b/e2e/modeltest-then-raxml-ng.spec.js index b0ca795f..9dc945d7 100644 --- a/e2e/modeltest-then-raxml-ng.spec.js +++ b/e2e/modeltest-then-raxml-ng.spec.js @@ -61,7 +61,7 @@ test.describe('ModelTest then raxml-ng', () => { expect(commandText).not.toMatch(/--model GTR(\s|$)/); const modeltestFiles = (await fs.readdir(outputDir)).filter((filename) => - filename.startsWith('RAxML_GUI_ModelTest_nucleotide') + filename.startsWith('RAxML_GUI_ModelTest_nucleotide'), ); expect(modeltestFiles.length).toBeGreaterThan(0); @@ -74,33 +74,84 @@ test.describe('ModelTest then raxml-ng', () => { }); await expect(page.locator('#error-dialog-title')).toHaveCount(0); - await expect(page.getByText(`Result for output id '${outputId}'`)).toBeVisible(); - await expect(page.getByText(`${outputId}.raxml.bestTree.tre`)).toBeVisible(); + await expect( + page.getByText(`Result for output id '${outputId}'`), + ).toBeVisible(); + await expect( + page.getByText(`${outputId}.raxml.bestTree.tre`), + ).toBeVisible(); await expect(page.getByText(`${outputId}.raxml.support.tre`)).toBeVisible(); - await expect(page.getByText(`${outputId}.raxml.bootstraps.tre`)).toBeVisible(); + await expect( + page.getByText(`${outputId}.raxml.bootstraps.tre`), + ).toBeVisible(); const bestTreePath = path.join(outputDir, `${outputId}.raxml.bestTree.tre`); const supportTreePath = path.join( outputDir, - `${outputId}.raxml.support.tre` + `${outputId}.raxml.support.tre`, ); const bootstrapsTreePath = path.join( outputDir, - `${outputId}.raxml.bootstraps.tre` + `${outputId}.raxml.bootstraps.tre`, ); const logPath = path.join(outputDir, `${outputId}.raxml.log.txt`); - const bestModelPath = path.join(outputDir, `${outputId}.raxml.bestModel.txt`); + const bestModelPath = path.join( + outputDir, + `${outputId}.raxml.bestModel.txt`, + ); const settingsPath = path.join( outputDir, - `RAxML_GUI_Settings_${outputId}.txt` + `RAxML_GUI_Settings_${outputId}.txt`, ); - await expect.poll(async () => fs.stat(bestTreePath).then(() => true).catch(() => false)).toBe(true); - await expect.poll(async () => fs.stat(supportTreePath).then(() => true).catch(() => false)).toBe(true); - await expect.poll(async () => fs.stat(bootstrapsTreePath).then(() => true).catch(() => false)).toBe(true); - await expect.poll(async () => fs.stat(logPath).then(() => true).catch(() => false)).toBe(true); - await expect.poll(async () => fs.stat(bestModelPath).then(() => true).catch(() => false)).toBe(true); - await expect.poll(async () => fs.stat(settingsPath).then(() => true).catch(() => false)).toBe(true); + await expect + .poll(async () => + fs + .stat(bestTreePath) + .then(() => true) + .catch(() => false), + ) + .toBe(true); + await expect + .poll(async () => + fs + .stat(supportTreePath) + .then(() => true) + .catch(() => false), + ) + .toBe(true); + await expect + .poll(async () => + fs + .stat(bootstrapsTreePath) + .then(() => true) + .catch(() => false), + ) + .toBe(true); + await expect + .poll(async () => + fs + .stat(logPath) + .then(() => true) + .catch(() => false), + ) + .toBe(true); + await expect + .poll(async () => + fs + .stat(bestModelPath) + .then(() => true) + .catch(() => false), + ) + .toBe(true); + await expect + .poll(async () => + fs + .stat(settingsPath) + .then(() => true) + .catch(() => false), + ) + .toBe(true); const bestTree = await fs.readFile(bestTreePath, 'utf8'); expect(bestTree).toContain('TAXON_'); diff --git a/e2e/raxmlHPC.spec.js b/e2e/raxmlHPC.spec.js index cd726861..1020595b 100644 --- a/e2e/raxmlHPC.spec.js +++ b/e2e/raxmlHPC.spec.js @@ -60,13 +60,13 @@ test.describe('raxmlHPC', () => { const outputFilename = `${outputId}.tre`; const bestTreePath = path.join( outputDir, - `RAxML_bestTree.${outputFilename}` + `RAxML_bestTree.${outputFilename}`, ); const infoPathTxt = path.join(outputDir, `RAxML_info.${outputId}.txt`); const infoPathTre = path.join(outputDir, `RAxML_info.${outputFilename}`); const settingsPath = path.join( outputDir, - `RAxML_GUI_Settings_${outputId}.txt` + `RAxML_GUI_Settings_${outputId}.txt`, ); const runButton = page.getByTestId('run-analysis'); @@ -82,7 +82,10 @@ test.describe('raxmlHPC', () => { expect(bestTree).toContain('TAXON_'); expect(bestTree).toMatch(/[()]/); - const infoPath = (await fs.stat(infoPathTxt).then(() => infoPathTxt).catch(() => infoPathTre)); + const infoPath = await fs + .stat(infoPathTxt) + .then(() => infoPathTxt) + .catch(() => infoPathTre); const infoText = await fs.readFile(infoPath, 'utf8'); expect(infoText.length).toBeGreaterThan(0); }); diff --git a/package.json b/package.json index 08733c23..4c2cc559 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "electron-webpack": "^2.8.2", "nodemon": "^3.1.10", "npm-run-all": "^4.1.5", - "prettier": "^3.6.2", + "prettier": "^3.9.6", "shx": "^0.4.0", "typescript": "^5.8.3", "wait-on": "^9.1.0" diff --git a/src/app/AlignmentCard.js b/src/app/AlignmentCard.js index 123ac1ea..0a7d4378 100644 --- a/src/app/AlignmentCard.js +++ b/src/app/AlignmentCard.js @@ -172,12 +172,10 @@ function AlignmentCard({ alignment }) { ...alignment.modelExtra, setValue: alignment.modelExtra.onChange, title: alignment.modelExtra.label, - options: alignment.modelExtra.options.map((model) => ( - { - value: model, - title: model, - } - )), + options: alignment.modelExtra.options.map((model) => ({ + value: model, + title: model, + })), }} /> ) : null} @@ -207,10 +205,7 @@ function AlignmentCard({ alignment }) { avatar={Type} action={
- + @@ -220,7 +215,8 @@ function AlignmentCard({ alignment }) { aria-owns={anchorEl ? 'alignment-menu' : undefined} aria-haspopup="true" onClick={handleMenuClick} - size="large"> + size="large" + > @@ -342,7 +338,8 @@ function FinalAlignmentCard({ sx, alignment }) { aria-owns={anchorEl ? 'alignment-menu' : undefined} aria-haspopup="true" onClick={handleMenuClick} - size="large"> + size="large" + > @@ -376,7 +373,7 @@ function FinalAlignmentCard({ sx, alignment }) { checked={alignment.fillTaxonGapsWithEmptySeqeunces} onChange={(event) => { alignment.setFillTaxonGapsWithEmptySeqeunces( - event.target.checked + event.target.checked, ); }} value="fillTaxonGapsWithEmptySeqeunces" @@ -406,7 +403,8 @@ function FinalAlignmentCard({ sx, alignment }) { component="code" sx={{ color: (theme) => theme.palette.primary.contrastText, - fontFamily: 'Consolas, "Liberation Mono", Menlo, Courier, monospace', + fontFamily: + 'Consolas, "Liberation Mono", Menlo, Courier, monospace', fontSize: '10px', height: '100%', overflowWrap: 'break-word', diff --git a/src/app/App.js b/src/app/App.js index 7333ce4c..f8290b12 100644 --- a/src/app/App.js +++ b/src/app/App.js @@ -44,7 +44,6 @@ const VerticalHeading = styled(Typography)(({ theme }) => ({ })); const App = () => { - const TabItems = store.runs.map((run) => ( { height: '100%', overflowY: 'auto', paddingBottom: '20px', - borderLeft: '1px solid #ccc' + borderLeft: '1px solid #ccc', }} > { {/* In dev mode the app version shown is from electron, in production it is ours */} - - raxmlGUI {store.version} - + raxmlGUI {store.version} {binary.value} {binary.version} diff --git a/src/app/AstralTreeCard.js b/src/app/AstralTreeCard.js index e5948af3..90afba67 100644 --- a/src/app/AstralTreeCard.js +++ b/src/app/AstralTreeCard.js @@ -16,7 +16,7 @@ import CardActions from '@mui/material/CardActions'; import Box from '@mui/material/Box'; function AstralTreeCard({ astralTree }) { - const { } = astralTree; + const {} = astralTree; const [anchorEl, setAnchorEl] = React.useState(null); @@ -44,9 +44,7 @@ function AstralTreeCard({ astralTree }) { }} > - - - + ); @@ -91,7 +89,8 @@ function AstralTreeCard({ astralTree }) { aria-owns={anchorEl ? 'astralTree-menu' : undefined} aria-haspopup="true" onClick={handleMenuClick} - size="large"> + size="large" + > diff --git a/src/app/CitationModal.js b/src/app/CitationModal.js index 26dba362..ae880c7a 100644 --- a/src/app/CitationModal.js +++ b/src/app/CitationModal.js @@ -13,7 +13,6 @@ import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import CodeHighlight from './components/CodeHighlight'; function CitationModal({ citation }) { - return ( How to cite? @@ -43,7 +42,7 @@ function CitationModal({ citation }) { aria-label="text format" size="small" > - {citation.formats.map(format => ( + {citation.formats.map((format) => ( - {citation.content.map(article => ( + {citation.content.map((article) => ( {article.name} theme.palette.output.background, borderRadius: '4px', - padding: '4px' + padding: '4px', }} /> @@ -90,7 +89,7 @@ function CitationModal({ citation }) { } CitationModal.propTypes = { - citation: PropTypes.object.isRequired + citation: PropTypes.object.isRequired, }; export default observer(CitationModal); diff --git a/src/app/Console.js b/src/app/Console.js index 85c7ba2a..74c340aa 100644 --- a/src/app/Console.js +++ b/src/app/Console.js @@ -38,7 +38,8 @@ const Console = ({ run }) => { component="code" sx={{ color: (theme) => theme.palette.console.contrastText, - fontFamily: 'Consolas, "Liberation Mono", Menlo, Courier, monospace', + fontFamily: + 'Consolas, "Liberation Mono", Menlo, Courier, monospace', fontSize: '12px', height: '100%', position: 'absolute', @@ -55,7 +56,8 @@ const Console = ({ run }) => { component="code" sx={{ color: (theme) => theme.palette.console.contrastText, - fontFamily: 'Consolas, "Liberation Mono", Menlo, Courier, monospace', + fontFamily: + 'Consolas, "Liberation Mono", Menlo, Courier, monospace', fontSize: '12px', height: '100%', position: 'absolute', @@ -73,7 +75,7 @@ const Console = ({ run }) => { }; Console.propTypes = { - run: PropTypes.object.isRequired + run: PropTypes.object.isRequired, }; export default observer(Console); diff --git a/src/app/Input.js b/src/app/Input.js index 3be0fef5..edc7e866 100644 --- a/src/app/Input.js +++ b/src/app/Input.js @@ -20,11 +20,7 @@ const Input = ({ run }) => { } // const SelectNumRuns = run. return ( - + { > {({ getRootProps, getInputProps }) => (
- + {run.inputIsAlignment ? run.alignments.map((alignment) => ( { )) : null} {run.inputIsTree && run.hasAstralTree ? ( - + ) : null} diff --git a/src/app/Model.js b/src/app/Model.js index eb45d1fd..ed51cb96 100644 --- a/src/app/Model.js +++ b/src/app/Model.js @@ -8,7 +8,6 @@ import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; const Model = ({ run }) => { - // TODO: Check marginTop: 2 hack, doesn't seem exactly aligned to top if (run.usesModeltestNg) { return ( @@ -90,7 +89,8 @@ const Model = ({ run }) => { title="Random seed" sx={{ width: 60 }} value={run.randomSeed} - onChange={(e) => run.setRandomSeed(e.target.value)} /> + onChange={(e) => run.setRandomSeed(e.target.value)} + /> ) : null} { ); }; - Model.propTypes = { run: PropTypes.object.isRequired, }; diff --git a/src/app/Output.js b/src/app/Output.js index f872ff68..22741255 100644 --- a/src/app/Output.js +++ b/src/app/Output.js @@ -13,7 +13,7 @@ const Output = ({ run }) => { const haveResult = resultFilenames.length > 0; return ( - ( { slotProps={{ input: { readOnly: true, - } - }} /> + }, + }} + /> run.setOutputName(e.target.value)} - error={!run.outputNameOk} /> + onChange={(e) => run.setOutputName(e.target.value)} + error={!run.outputNameOk} + /> - { haveResult ? Result for output id '{run.outputName}' : null } - { resultFilenames.map(filename => - theme.palette.primary.contrastText, - display: 'flex', - alignItems: 'flex-end', - cursor: 'pointer', - }} - onClick={() => run.openFile(join(run.outputDir, filename))} - underline="hover"> - - Result for output id '{run.outputName}' + ) : null} + {resultFilenames.map((filename) => ( + theme.palette.primary.contrastText, + display: 'flex', + alignItems: 'flex-end', + cursor: 'pointer', }} + onClick={() => run.openFile(join(run.outputDir, filename))} + underline="hover" > - {filename} - - - )} + + theme.palette.primary.contrastText, + }} + > + {filename} + + + ))} - { run.haveAlignments || haveResult ? ( + {run.haveAlignments || haveResult ? ( - + - ) : null } - ) + ) : null} + ); }; diff --git a/src/app/PartitionEditor.js b/src/app/PartitionEditor.js index 52b7cbc1..bba871b5 100644 --- a/src/app/PartitionEditor.js +++ b/src/app/PartitionEditor.js @@ -26,68 +26,72 @@ function PartitionEditor({ alignment }) { const { partToAdd } = partition; return ( - ( theme.palette.input.background, display: 'flex', flexDirection: 'column', - alignItems: 'flex-start' + alignItems: 'flex-start', }} > Partition editor - - - {alignment.filename}: {alignment.numSequences} sequences of length {alignment.length} - Partition coverage: {partition.currentEndValue} / {partition.maxEndValue} + + + + {alignment.filename}: {alignment.numSequences} sequences of length{' '} + {alignment.length} + + + Partition coverage: {partition.currentEndValue} /{' '} + {partition.maxEndValue} + - + - + - + - + - + - + - + - {partition.errorMessage || ' '} + + {partition.errorMessage || ' '} + @@ -95,7 +99,8 @@ function PartitionEditor({ alignment }) { sx={{ width: '100%', '& .MuiInputBase-input': { - fontFamily: 'Consolas, "Liberation Mono", Menlo, Courier, monospace', + fontFamily: + 'Consolas, "Liberation Mono", Menlo, Courier, monospace', }, }} id="partition" @@ -110,23 +115,36 @@ function PartitionEditor({ alignment }) { variant="outlined" /> - - { partition.isDefault ? null : ( + + {partition.isDefault ? null : ( - + )} - + - ) + ); } PartitionEditor.propTypes = { alignment: PropTypes.object.isRequired, - className: PropTypes.string + className: PropTypes.string, }; const PartitionEditorObserver = observer(PartitionEditor); @@ -134,7 +152,7 @@ const PartitionEditorObserver = observer(PartitionEditor); function PartitionOnCard({ alignment }) { // const [partitionText, setPartitionText] = React.useState(alignment.partitionText); const [partitionText, setPartitionText] = React.useState( - alignment.partitionFileContent + alignment.partitionFileContent, ); function handleChange(event) { @@ -159,7 +177,7 @@ function PartitionOnCard({ alignment }) { marginTop: -30, backgroundColor: 'rgba(0,0,0,0)', // transparent background display: 'flex', - alignItems: 'flex-start' + alignItems: 'flex-start', }} elevation={0} > @@ -176,7 +194,7 @@ function PartitionOnCard({ alignment }) { padding: 0, marginTop: '10px', marginLeft: 1, - marginRight: 1 + marginRight: 1, }} margin="normal" helperText={alignment.partitionHelperText || ''} @@ -203,12 +221,12 @@ function PartitionOnCard({ alignment }) { PartitionOnCard.propTypes = { alignment: PropTypes.object.isRequired, - className: PropTypes.string + className: PropTypes.string, }; const PartitionOnCardObserver = observer(PartitionOnCard); export { PartitionEditorObserver as default, - PartitionOnCardObserver as PartitionOnCard + PartitionOnCardObserver as PartitionOnCard, }; diff --git a/src/app/PartitionFileCard.js b/src/app/PartitionFileCard.js index 32b83d30..de4690eb 100644 --- a/src/app/PartitionFileCard.js +++ b/src/app/PartitionFileCard.js @@ -67,14 +67,15 @@ function PartitionFileCard({ run }) { component="code" sx={{ color: (theme) => theme.palette.primary.contrastText, - fontFamily: 'Consolas, "Liberation Mono", Menlo, Courier, monospace', + fontFamily: + 'Consolas, "Liberation Mono", Menlo, Courier, monospace', fontSize: '12px', height: '100%', overflowWrap: 'break-word', whiteSpace: 'pre-wrap', }} > - { run.partitionFileContent } + {run.partitionFileContent} diff --git a/src/app/Raxml.js b/src/app/Raxml.js index 36da8897..938c0912 100644 --- a/src/app/Raxml.js +++ b/src/app/Raxml.js @@ -17,72 +17,72 @@ const Raxml = ({ run, store }) => { store.setAppSnack(); }, [run.command, store]); - return ( + return ( + - - - - {run.modelTestIsRunningOnAlignment ? ( - - ) : null} - {run.running ? ( - - ) : null} + + + {run.modelTestIsRunningOnAlignment ? ( - + ) : null} + {run.running ? ( + + ) : null} + + - - - - - - - - {run.command} - - Command + + + + + + + + {run.command} + Command - ); + + ); }; Raxml.propTypes = { diff --git a/src/app/TreeCard.js b/src/app/TreeCard.js index b1d3c7c0..e11b529d 100644 --- a/src/app/TreeCard.js +++ b/src/app/TreeCard.js @@ -3,8 +3,8 @@ import { observer } from 'mobx-react-lite'; import PropTypes from 'prop-types'; import IconButton from '@mui/material/IconButton'; import MoreVertIcon from '@mui/icons-material/MoreVert'; -import CircularProgress from "@mui/material/CircularProgress"; -import Chip from "@mui/material/Chip"; +import CircularProgress from '@mui/material/CircularProgress'; +import Chip from '@mui/material/Chip'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import Card from '@mui/material/Card'; @@ -27,7 +27,7 @@ function TreeCard({ sx, tree }) { return () => { callback(); setAnchorEl(null); - } + }; } return ( @@ -58,35 +58,43 @@ function TreeCard({ sx, tree }) { aria-owns={anchorEl ? 'tree-menu' : undefined} aria-haspopup="true" onClick={handleMenuClick} - size="large"> + size="large" + > - - Show tree - Show folder + + + Show tree + + + Show folder + Remove } - title={ tree.name } - subheader={ '' } + title={tree.name} + subheader={''} /> - { tree.loading ? ( + {tree.loading ? ( - ) : null } + ) : null} - - + ); -}; +} // {tree.name} - TreeCard.propTypes = { tree: PropTypes.object.isRequired, sx: PropTypes.object, diff --git a/src/app/bootstrap.js b/src/app/bootstrap.js index e983d97d..8989ea4c 100644 --- a/src/app/bootstrap.js +++ b/src/app/bootstrap.js @@ -8,9 +8,12 @@ if (process.env.NODE_ENV === 'development') { ipcRenderer.send(ipc.ALIGNMENT_EXAMPLE_FILES_GET_REQUEST); } -ipcRenderer.on(ipc.ALIGNMENT_EXAMPLE_FILES_GET_SUCCESS, (event, exampleFiles) => { - initDev(exampleFiles); -}); +ipcRenderer.on( + ipc.ALIGNMENT_EXAMPLE_FILES_GET_SUCCESS, + (event, exampleFiles) => { + initDev(exampleFiles); + }, +); function initDev(exampleFiles) { // if (exampleFiles.length === 0) { @@ -28,7 +31,9 @@ function initDev(exampleFiles) { // 'mixed_data.txt', // 'multistate.txt', 'nucleotide.txt', - ].map(filename => ({ path: path.join(exampleFilesDir, 'fasta', filename) })); + ].map((filename) => ({ + path: path.join(exampleFilesDir, 'fasta', filename), + })); const usePhylipFiles = [ // 'AA.txt', // 'align_allvariant.txt', @@ -46,7 +51,9 @@ function initDev(exampleFiles) { // 'fail_bad_name.txt', // 'test_invariant_sites.txt', // 'test_lower_case_bases.txt', - ].map(filename => ({ path: path.join(exampleFilesDir, 'phylip', filename) })); + ].map((filename) => ({ + path: path.join(exampleFilesDir, 'phylip', filename), + })); const useFiles = [].concat(useFastaFiles, usePhylipFiles); store.activeRun.addAlignments(useFiles); store.activeRun.setOutputDir(exampleFiles.outdir); diff --git a/src/app/components/CodeHighlight.js b/src/app/components/CodeHighlight.js index 7edd3313..e156ba6b 100644 --- a/src/app/components/CodeHighlight.js +++ b/src/app/components/CodeHighlight.js @@ -11,7 +11,11 @@ function CodeHighlight({ code, language, sx }) { useEffect(() => { if (Prism.languages.hasOwnProperty(language)) { - const highlightHTML = Prism.highlight(code, Prism.languages[language], language); + const highlightHTML = Prism.highlight( + code, + Prism.languages[language], + language, + ); codeNode.current.innerHTML = highlightHTML; } }, [code, language]); @@ -27,7 +31,7 @@ function CodeHighlight({ code, language, sx }) { whiteSpace: 'pre-wrap', }} > - { code } + {code} ); diff --git a/src/app/components/ErrorBoundary.js b/src/app/components/ErrorBoundary.js index 23c125f5..f7f40d28 100644 --- a/src/app/components/ErrorBoundary.js +++ b/src/app/components/ErrorBoundary.js @@ -22,7 +22,7 @@ class ErrorBoundary extends React.Component { handleClose = () => { this.setState({ error: null }); - } + }; render() { const { error } = this.state; @@ -36,7 +36,7 @@ class ErrorBoundary extends React.Component { Oops! Something went wrong. - + ); } diff --git a/src/app/components/ErrorDialog.js b/src/app/components/ErrorDialog.js index f8eca1d1..dc64bf7f 100644 --- a/src/app/components/ErrorDialog.js +++ b/src/app/components/ErrorDialog.js @@ -14,12 +14,15 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import Button from '@mui/material/Button'; import Alert from '@mui/material/Alert'; import { ipcRenderer } from 'electron'; -import { reportIssueToGitHub, getMailtoLinkToReportError } from '../../common/utils'; +import { + reportIssueToGitHub, + getMailtoLinkToReportError, +} from '../../common/utils'; import * as ipc from '../../constants/ipc'; const handleReload = () => { ipcRenderer.send(ipc.RELOAD); -} +}; export default function ErrorDialog({ error, onClose, needReload, title }) { const [reported, setReported] = React.useState(false); @@ -31,17 +34,19 @@ export default function ErrorDialog({ error, onClose, needReload, title }) { const handleReportToGithub = () => { reportIssueToGitHub(error); setReported(true); - } + }; const handleReportToMail = () => { setReported(true); - } + }; const mailtoContent = getMailtoLinkToReportError(error); - const closeMessage = needReload ? ( - reported ? 'Reload' : 'Ignore and reload' - ) : 'Close'; + const closeMessage = needReload + ? reported + ? 'Reload' + : 'Ignore and reload' + : 'Close'; const resetAndClose = () => { setReported(false); @@ -90,10 +95,7 @@ export default function ErrorDialog({ error, onClose, needReload, title }) { Please help us solve the issue by reporting it. ) : ( - + Thanks for reporting the issue! ); @@ -168,4 +170,4 @@ ErrorDialog.propTypes = { onClose: PropTypes.func.isRequired, needReload: PropTypes.bool, title: PropTypes.string, -} +}; diff --git a/src/app/components/ModifiedDialog.js b/src/app/components/ModifiedDialog.js index 18c11143..06815868 100644 --- a/src/app/components/ModifiedDialog.js +++ b/src/app/components/ModifiedDialog.js @@ -22,7 +22,9 @@ export default function ModifiedDialog({ show, onClose, messages }) { {'Attention'} - {'This will be using a copy of your input file, because there were some issues!'} + { + 'This will be using a copy of your input file, because there were some issues!' + } { if (option.notAvailable) { return null; diff --git a/src/app/components/OptionSelect.js b/src/app/components/OptionSelect.js index 24c964b4..ea80621f 100644 --- a/src/app/components/OptionSelect.js +++ b/src/app/components/OptionSelect.js @@ -7,7 +7,6 @@ import Select from '@mui/material/Select'; import MenuItem from '@mui/material/MenuItem'; import TextField from '@mui/material/TextField'; - const OptionSelect = observer(({ option, sx }) => { if (option.notAvailable || option.options.length === 0) { return null; @@ -15,7 +14,7 @@ const OptionSelect = observer(({ option, sx }) => { if (option.options.length === 1) { // No options to change to, render as a text field instead. return ( - ( { slotProps={{ input: { readOnly: true, - } - }} />) + }, + }} + /> ); } @@ -41,7 +41,8 @@ const OptionSelect = observer(({ option, sx }) => { id: option.title, }} error={option.error} - multiple={option.multiple}> + multiple={option.multiple} + > {option.options.map(({ value, title }, index) => ( {title} diff --git a/src/app/components/OptionTextField.js b/src/app/components/OptionTextField.js index 18750799..0e208cda 100644 --- a/src/app/components/OptionTextField.js +++ b/src/app/components/OptionTextField.js @@ -18,7 +18,8 @@ const OptionTextField = observer(({ option, sx }) => { value={option.value} placeholder={option.placeholder} onChange={(e) => option.setValue(e.target.value)} - error={option.haveError} /> + error={option.haveError} + /> ); }); diff --git a/src/app/index.js b/src/app/index.js index db7f4d8c..2a923f93 100644 --- a/src/app/index.js +++ b/src/app/index.js @@ -11,9 +11,11 @@ import { is } from '../common/utils'; import './bootstrap'; -is.development ? null : Sentry.init({ - dsn: 'https://d92efa46c2ba43f38250b202c791a2c2@o117148.ingest.sentry.io/6517975', -}); +is.development + ? null + : Sentry.init({ + dsn: 'https://d92efa46c2ba43f38250b202c791a2c2@o117148.ingest.sentry.io/6517975', + }); const Index = () => { const { light, dark } = theme; @@ -27,7 +29,6 @@ const Index = () => { ); -} +}; export default observer(Index); - diff --git a/src/app/store/Alignment.js b/src/app/store/Alignment.js index dd5529e6..ef0152fb 100644 --- a/src/app/store/Alignment.js +++ b/src/app/store/Alignment.js @@ -36,9 +36,11 @@ class RaxmlNgAlignmentSubstitutionModel extends Option { if (!modelSettings) { return []; } - return modelSettings.options.map(value => ({ value, title: value })); + return modelSettings.options.map((value) => ({ value, title: value })); + } + @computed get notAvailable() { + return !this.alignment.run.haveAlignments; } - @computed get notAvailable() { return !this.alignment.run.haveAlignments; } @computed get cmdValue() { let model = this.value; if (this.alignment.dataType === 'multistate') { @@ -52,7 +54,9 @@ class RaxmlNgModelExtraParam extends Option { constructor(alignment, label, options, { addNone = true } = {}) { super(alignment.run, addNone ? '' : options[0].value, label); this.alignment = alignment; - this.optionsSource = addNone ? [{ value: '', title: 'none' }, ...options] : options; + this.optionsSource = addNone + ? [{ value: '', title: 'none' }, ...options] + : options; this.addNone = addNone; } @computed get options() { @@ -62,10 +66,7 @@ class RaxmlNgModelExtraParam extends Option { return this.optionsSource; } @computed get notAvailable() { - return ( - !this.run.haveAlignments || - !this.run.usesRaxmlNg - ); + return !this.run.haveAlignments || !this.run.usesRaxmlNg; } @computed get cmdValue() { return this.value === '' ? '' : this.value; @@ -77,8 +78,9 @@ class RaxmlNgModelF extends RaxmlNgModelExtraParam { super( alignment, 'Stationary frequencies', - raxmlNgSettings.stationaryFrequenciesOptions.options.map(({ value, label: title }) => - ({ value, title })) + raxmlNgSettings.stationaryFrequenciesOptions.options.map( + ({ value, label: title }) => ({ value, title }), + ), ); } } @@ -88,8 +90,9 @@ class RaxmlNgModelI extends RaxmlNgModelExtraParam { super( alignment, 'Proportion of invariant sites', - raxmlNgSettings.proportionOfInvariantSitesOptions.options.map(({ value, label: title }) => - ({ value, title })) + raxmlNgSettings.proportionOfInvariantSitesOptions.options.map( + ({ value, label: title }) => ({ value, title }), + ), ); } } @@ -99,8 +102,9 @@ class RaxmlNgModelG extends RaxmlNgModelExtraParam { super( alignment, 'Rate heterogeneity', - raxmlNgSettings.amongsiteRateHeterogeneityModelOptions.options.map(({ value, label: title }) => - ({ value, title })) + raxmlNgSettings.amongsiteRateHeterogeneityModelOptions.options.map( + ({ value, label: title }) => ({ value, title }), + ), ); } } @@ -110,9 +114,10 @@ class RaxmlNgModelASC extends RaxmlNgModelExtraParam { super( alignment, 'Ascertainment bias', - [{ value: '', label: 'No correction' }, - ...raxmlNgSettings.ascertainmentBiasCorrectionOptions.options].map(({ value, label: title }) => - ({ value, title })), + [ + { value: '', label: 'No correction' }, + ...raxmlNgSettings.ascertainmentBiasCorrectionOptions.options, + ].map(({ value, label: title }) => ({ value, title })), { addNone: false }, ); } @@ -131,11 +136,17 @@ class MultistateNumber extends Option { this.alignment = alignment; this.placeholder = 'Integer'; } - @computed get notAvailable() { return this.alignment.dataType !== 'multistate' || !this.alignment.run.usesRaxmlNg; } - @computed get error() { return !this.value || !Number.isInteger(Number(this.value)) } + @computed get notAvailable() { + return ( + this.alignment.dataType !== 'multistate' || + !this.alignment.run.usesRaxmlNg + ); + } + @computed get error() { + return !this.value || !Number.isInteger(Number(this.value)); + } } - class Alignment extends InputFile { constructor(run, path) { super(run, path); @@ -300,8 +311,8 @@ class Alignment extends InputFile { if (this.numSequences < 3) { this.run.parent.onError( new UserFixError( - 'ModelTest can only be run on alignments with more than two sequences.' - ) + 'ModelTest can only be run on alignments with more than two sequences.', + ), ); return; } @@ -418,7 +429,7 @@ class Alignment extends InputFile { const newDataType = getFinalDataType( this.run.alignments .map(({ dataType }) => dataType) - .concat(alignment.dataType) + .concat(alignment.dataType), ); if (this.run.dataType !== newDataType) { this.run.substitutionMatrix.value = @@ -458,7 +469,7 @@ class Alignment extends InputFile { ipc.ALIGNMENT_PARSE_CHANGED_PATH, ( event, - { id, newFilePath, format, converted, modified, modificationMessages } + { id, newFilePath, format, converted, modified, modificationMessages }, ) => { if (id === this.id) { runInAction(() => { @@ -474,7 +485,7 @@ class Alignment extends InputFile { } }); } - } + }, ); ipcRenderer.on( ipc.ALIGNMENT_MODEL_SELECTION_SUCCESS, @@ -484,7 +495,7 @@ class Alignment extends InputFile { this.setModelFromString(result); this.run.afterRun(); } - } + }, ); ipcRenderer.on( ipc.ALIGNMENT_MODEL_SELECTION_FAILURE, @@ -495,7 +506,7 @@ class Alignment extends InputFile { this.run.error = error; this.run.afterRun(); } - } + }, ); //TODO: Transform above to the form of below to be able to unlisten on removal this.listenTo(ipc.RUN_STDOUT, this.onRunStdout); @@ -597,11 +608,16 @@ class FinalAlignment { @action setFillTaxonGapsWithEmptySeqeunces = (checked) => { this.fillTaxonGapsWithEmptySeqeunces = checked; - } + }; @computed get taxons() { - const setMethod = this.fillTaxonGapsWithEmptySeqeunces ? union : intersection; - return setMethod.apply(setMethod, this.run.alignments.map(({ taxons }) => taxons)); + const setMethod = this.fillTaxonGapsWithEmptySeqeunces + ? union + : intersection; + return setMethod.apply( + setMethod, + this.run.alignments.map(({ taxons }) => taxons), + ); } @computed get numSequences() { @@ -609,7 +625,10 @@ class FinalAlignment { } @computed get length() { - return this.run.alignments.reduce((sumLength, alignment) => sumLength + alignment.length, 0); + return this.run.alignments.reduce( + (sumLength, alignment) => sumLength + alignment.length, + 0, + ); } @computed get hasInvariantSites() { @@ -639,14 +658,12 @@ class FinalAlignment { // return firstType; // } - @computed get dataType() { const { alignments } = this.run; const dataTypes = alignments.map(({ dataType }) => dataType); return getFinalDataType(dataTypes); } - @computed get modelFlagName() { const numAlignments = this.numAlignments; if (numAlignments === 0) { @@ -664,7 +681,10 @@ class FinalAlignment { return ''; } const suffix = this.numAlignments > 1 ? '_concat' : ''; - return join(`${this.dir}`, `RAxML_${this.run.outputNameSafe}${suffix}.part.txt`); + return join( + `${this.dir}`, + `RAxML_${this.run.outputNameSafe}${suffix}.part.txt`, + ); } @computed get partitionFileContent() { @@ -705,7 +725,9 @@ class FinalAlignment { writeConcatenatedAlignment = async () => { const { taxons, numSequences } = this; try { - console.log(`Write concatenated alignment in FASTA format to ${this.path}..`); + console.log( + `Write concatenated alignment in FASTA format to ${this.path}..`, + ); const writeStream = fs.createWriteStream(this.path); const write = util.promisify(writeStream.write); const end = util.promisify(writeStream.end); @@ -715,12 +737,14 @@ class FinalAlignment { const prefix = i === 0 ? '>' : '\n>'; await write.call(writeStream, `${prefix}${taxons[i]}\n`); } - await write.call(writeStream, this.run.alignments[j].getSequenceCode(taxons[i])); + await write.call( + writeStream, + this.run.alignments[j].getSequenceCode(taxons[i]), + ); } } await end.call(writeStream); - } - catch (err) { + } catch (err) { console.error('Error writing concatenated alignment:', err); throw err; } @@ -731,13 +755,11 @@ class FinalAlignment { try { console.log(`Writing partition to ${this.partitionFilePath}...`); await writeFile(this.partitionFilePath, this.partitionFileContent); - } - catch (err) { + } catch (err) { console.error('Error writing partition:', err); throw err; } - } - + }; } export { Alignment as default, FinalAlignment }; diff --git a/src/app/store/AstralTree.js b/src/app/store/AstralTree.js index 23c65611..9431b41b 100644 --- a/src/app/store/AstralTree.js +++ b/src/app/store/AstralTree.js @@ -2,7 +2,6 @@ import { observable, computed, action, runInAction } from 'mobx'; import InputFile from './InputFile'; - class AstralTree extends InputFile { constructor(run, path) { super(run, path); @@ -14,4 +13,4 @@ class AstralTree extends InputFile { }; } -export default AstralTree; \ No newline at end of file +export default AstralTree; diff --git a/src/app/store/Citation.js b/src/app/store/Citation.js index c0937675..0937adb4 100644 --- a/src/app/store/Citation.js +++ b/src/app/store/Citation.js @@ -210,4 +210,3 @@ ER - `, console.log('Copied citation to clipboard'); }; } - diff --git a/src/app/store/Config.js b/src/app/store/Config.js index 74bbbe31..00a126f9 100644 --- a/src/app/store/Config.js +++ b/src/app/store/Config.js @@ -6,31 +6,30 @@ import Store from 'electron-store'; export const store = new Store(); export default class Config extends StoreBase { - constructor() { super(); this.listen(); } - @observable isDarkMode = store.get('darkMode') + @observable isDarkMode = store.get('darkMode'); @action - setDarkMode = value => { + setDarkMode = (value) => { value = !!value; store.set('darkMode', value); this.isDarkMode = value; - } + }; onLightMode = () => { this.setDarkMode(false); - } + }; onDarkMode = () => { this.setDarkMode(true); - } + }; listen = () => { this.listenTo(ipc.LIGHT_MODE, this.onLightMode); this.listenTo(ipc.DARK_MODE, this.onDarkMode); - } + }; } diff --git a/src/app/store/InputFile.js b/src/app/store/InputFile.js index e02783b5..7a27e010 100644 --- a/src/app/store/InputFile.js +++ b/src/app/store/InputFile.js @@ -58,4 +58,4 @@ class InputFile extends StoreBase { }; } -export default InputFile; \ No newline at end of file +export default InputFile; diff --git a/src/app/store/Option.js b/src/app/store/Option.js index e20dc9fe..2b416731 100644 --- a/src/app/store/Option.js +++ b/src/app/store/Option.js @@ -11,11 +11,14 @@ class Option { * @param {String} hoverInfo * @param {yup|undefined} schema */ - constructor(run, defaultValue, title, description, hoverInfo, { - schema = undefined, - allowOnlyValidChange = false, - helperText = '', - } = {}) { + constructor( + run, + defaultValue, + title, + description, + hoverInfo, + { schema = undefined, allowOnlyValidChange = false, helperText = '' } = {}, + ) { this.run = run; this.defaultValue = defaultValue; this.title = title; @@ -31,9 +34,13 @@ class Option { if (!this.allowOnlyValidChange || isValid) { this.value = isValid ? this.schema.cast(value) : value; } + }; + @action reset() { + this.value = this.defaultValue; + } + @computed get isDefault() { + return this.value === this.defaultValue; } - @action reset() { this.value = this.defaultValue; } - @computed get isDefault() { return this.value === this.defaultValue; } @computed get error() { try { this.schema.validateSync(this.value); @@ -42,8 +49,12 @@ class Option { return err; } } - @computed get haveError() { return this.error !== null; } - @computed get errorMessage() { return this.haveError ? this.error.message : '' } + @computed get haveError() { + return this.error !== null; + } + @computed get errorMessage() { + return this.haveError ? this.error.message : ''; + } } export { Option as default }; diff --git a/src/app/store/Partition.js b/src/app/store/Partition.js index 27c17972..84b37157 100644 --- a/src/app/store/Partition.js +++ b/src/app/store/Partition.js @@ -23,26 +23,38 @@ const getPartitionType = (dataType, aaMatrixName = 'BLOSUM62') => { default: return dataType; } -} +}; class PartBase extends Option { - constructor(part, defaultValue, title, description, hoverInfo, { - schema = undefined, - allowOnlyValidChange = false, - helperText = '', - } = {}) { - super(part.partition.alignment.run, defaultValue, title, description, hoverInfo, { - schema, - allowOnlyValidChange, - helperText, - }); + constructor( + part, + defaultValue, + title, + description, + hoverInfo, + { schema = undefined, allowOnlyValidChange = false, helperText = '' } = {}, + ) { + super( + part.partition.alignment.run, + defaultValue, + title, + description, + hoverInfo, + { + schema, + allowOnlyValidChange, + helperText, + }, + ); this.part = part; } - @computed get alignment() { return this.part.partition.alignment; } + @computed get alignment() { + return this.part.partition.alignment; + } } class PartType extends PartBase { - constructor(part, value='DNA') { + constructor(part, value = 'DNA') { super(part, value, 'Data type', 'Data type of the part', '', { schema: yup.string(), allowOnlyValidChange: true, @@ -64,66 +76,91 @@ class PartType extends PartBase { if (dataType === '' || dataType === 'mixed') { opts = ['DNA', 'BIN', 'MULTI', 'protein']; } - return opts.map(value => ({ value, title: value })); + return opts.map((value) => ({ value, title: value })); + } + @computed get notAvailable() { + return this.alignment.dataType !== 'mixed'; } - @computed get notAvailable() { return this.alignment.dataType !== 'mixed'; } } class PartAAType extends PartBase { - constructor(part, value='BLOSUM62') { + constructor(part, value = 'BLOSUM62') { super(part, value, 'Model', 'Substitution model for the part', '', { schema: yup.string(), allowOnlyValidChange: true, }); } - options = raxmlSettings.aminoAcidSubstitutionMatrixOptions.options.map(value => ({ value, title: value })); - @computed get notAvailable() { return this.alignment.dataType !== 'protein'; } + options = raxmlSettings.aminoAcidSubstitutionMatrixOptions.options.map( + (value) => ({ value, title: value }), + ); + @computed get notAvailable() { + return this.alignment.dataType !== 'protein'; + } } class PartName extends PartBase { - constructor(part, value='part1') { + constructor(part, value = 'part1') { super(part, value, 'Name', 'Name of part', '', { schema: yup.string(), // TODO: Check that it is unique within partition allowOnlyValidChange: true, }); } - @computed get notAvailable() { return false; } + @computed get notAvailable() { + return false; + } } class PartStart extends PartBase { - constructor(part, value=1) { + constructor(part, value = 1) { super(part, value, 'from', 'Start position for this part', '', { schema: yup.number(), allowOnlyValidChange: false, }); this.disabled = true; } - @computed get notAvailable() { return false; } + @computed get notAvailable() { + return false; + } } class PartEnd extends PartBase { - constructor(part, value=1) { - super(part, value, 'to', 'Ending position (inclusive) for this part', 'Ending position (inclusive) for this part', { - schema: yup.number().test({ - name: 'part-end', - message: () => `Must be in interval [${this.min}, ${this.max}]`, - test: (value) => { - return value >= this.min && value <= this.max; - } - }), - allowOnlyValidChange: false, - }); + constructor(part, value = 1) { + super( + part, + value, + 'to', + 'Ending position (inclusive) for this part', + 'Ending position (inclusive) for this part', + { + schema: yup.number().test({ + name: 'part-end', + message: () => `Must be in interval [${this.min}, ${this.max}]`, + test: (value) => { + return value >= this.min && value <= this.max; + }, + }), + allowOnlyValidChange: false, + }, + ); + } + @computed get min() { + return ( + this.part.start.value + (this.part.codon.value === CODON_NONE ? 0 : 2) + ); + } + @computed get max() { + return this.alignment.length; + } + @computed get notAvailable() { + return false; } - @computed get min() { return this.part.start.value + (this.part.codon.value === CODON_NONE ? 0 : 2); } - @computed get max() { return this.alignment.length; } - @computed get notAvailable() { return false; } } class PartCodon extends PartBase { - constructor(part, value='None') { + constructor(part, value = 'None') { super(part, value, 'Codon model', '', '', { schema: yup.string(), allowOnlyValidChange: true, }); } - options = CodonModels.map(value => ({ value, title: value })); + options = CodonModels.map((value) => ({ value, title: value })); @computed get notAvailable() { return this.alignment.partition.partToAdd.type.value !== 'DNA'; } @@ -132,56 +169,61 @@ class PartCodon extends PartBase { class Part { constructor(partition, { type, aaType, name, start, end, codon }) { this.partition = partition; - this.type = new PartType(this, type) - this.aaType = new PartAAType(this, aaType) - this.name = new PartName(this, name) - this.start = new PartStart(this, start) - this.end = new PartEnd(this, end) - this.codon = new PartCodon(this, codon) + this.type = new PartType(this, type); + this.aaType = new PartAAType(this, aaType); + this.name = new PartName(this, name); + this.start = new PartStart(this, start); + this.end = new PartEnd(this, end); + this.codon = new PartCodon(this, codon); } - @computed get typePadded() { return `${this.type.finalValue},`.padEnd(12); } + @computed get typePadded() { + return `${this.type.finalValue},`.padEnd(12); + } /** * codon 0: No codon * codon 1,2 or 3: Codon specific, add offset 0,1 and 2 respectively and append \3 * codon 12: Third codon, add offset 1 on same row and append \3 on the two ranges */ - _transform = (offset = 0, namePrefix = '', codon = 0, ) => { - const nameSuffix = codon === 0 ? '' : codon === 12 ? '_codon1and2' : `_codon${codon}`; + _transform = (offset = 0, namePrefix = '', codon = 0) => { + const nameSuffix = + codon === 0 ? '' : codon === 12 ? '_codon1and2' : `_codon${codon}`; const partWithoutRange = `${this.typePadded}${namePrefix}${this.name.value}${nameSuffix} = `; const start = this.start.value + offset; const end = this.end.value + offset; let range = `${start}-${end}`; if (codon === 12) { - range = `${range}\\3, ${start+1}-${end}\\3`; + range = `${range}\\3, ${start + 1}-${end}\\3`; } else if (codon > 0 && codon <= 3) { - range = `${start+codon-1}-${end}\\3`; + range = `${start + codon - 1}-${end}\\3`; } return `${partWithoutRange}${range}`; - } + }; transform = (offset = 0, namePrefix = '') => { switch (this.codon.value) { case CODON_NONE: return this._transform(offset, namePrefix); case CODON_SPECIFIC: - return [1,2,3].map(codon => this._transform(offset, namePrefix, codon)).join('\n'); + return [1, 2, 3] + .map((codon) => this._transform(offset, namePrefix, codon)) + .join('\n'); case CODON_THIRD: - return [12,3].map(codon => this._transform(offset, namePrefix, codon)).join('\n'); + return [12, 3] + .map((codon) => this._transform(offset, namePrefix, codon)) + .join('\n'); default: throw new Error(`Codon type ${this.codon.value} not recognized.`); } - } + }; @computed get text() { // return `${this.typePadded}${this.name.value} = ${this.start.value}-${this.end.value}`; return this.transform(); } - parse = (row) => { - - } + parse = (row) => {}; @computed get values() { return { @@ -196,7 +238,7 @@ class Part { clone = () => { return new Part(this.partition, this.values); - } + }; } class Partition { constructor(alignment) { @@ -217,25 +259,32 @@ class Partition { end: 1, codon: 'None', }); - reaction(() => alignment.length, (length, reaction) => { - reaction.dispose(); - // Make default alignment complete except for mixed type - if (this.alignment.dataType !== 'mixed') { - this.defaultPartition.end.value = length; - } - }, { name: 'React to alignment length'}); + reaction( + () => alignment.length, + (length, reaction) => { + reaction.dispose(); + // Make default alignment complete except for mixed type + if (this.alignment.dataType !== 'mixed') { + this.defaultPartition.end.value = length; + } + }, + { name: 'React to alignment length' }, + ); reaction( () => ({ dataType: alignment.dataType, aaMatrixName: alignment.aaMatrixName, }), ({ dataType, aaMatrixName }) => { - const type = dataType === 'protein' ? 'protein' : getPartitionType(dataType, aaMatrixName); + const type = + dataType === 'protein' + ? 'protein' + : getPartitionType(dataType, aaMatrixName); this.partToAdd.type.value = this.defaultPartition.type.value = type; this.defaultPartition.aaType.value = aaMatrixName; this.partToAdd.aaType.value = aaMatrixName; }, - { name: 'React to default partition type change' } + { name: 'React to default partition type change' }, ); } @observable parts = []; @@ -246,22 +295,34 @@ class Partition { this.partToAdd.codon.value = CODON_NONE; if (!this.isComplete) { // Start new range after last end position - this.partToAdd.start.value = this.partToAdd.end.value = this.partToAdd.end.value + 1; + this.partToAdd.start.value = this.partToAdd.end.value = + this.partToAdd.end.value + 1; // Increment counter on name const oldName = this.partToAdd.name.value; - let newName = oldName.replace(/(\d+)$/, (_, digits) => `${Number(digits) + 1}`); + let newName = oldName.replace( + /(\d+)$/, + (_, digits) => `${Number(digits) + 1}`, + ); if (newName === oldName) { newName = `${newName}_1`; } this.partToAdd.name.value = newName; } + }; + @computed get isMixed() { + return this.alignment.dataType === 'mixed'; + } + @computed get isDefault() { + return this.parts.length === 0; + } + @computed get maxEndValue() { + return this.alignment.length; } - @computed get isMixed() { return this.alignment.dataType === 'mixed'; } - @computed get isDefault() { return this.parts.length === 0; } - @computed get maxEndValue() { return this.alignment.length; } @computed get currentEndValue() { - return this.isDefault ? this.defaultPartition.end.value : this.parts[this.parts.length - 1].end.value; + return this.isDefault + ? this.defaultPartition.end.value + : this.parts[this.parts.length - 1].end.value; } @computed get isComplete() { return this.currentEndValue === this.maxEndValue; @@ -270,13 +331,17 @@ class Partition { return !this.isDefault && this.isComplete; } @computed get progress() { - return this.isDefault ? 100 : this.currentEndValue * 100.0 / this.alignment.length; + return this.isDefault + ? 100 + : (this.currentEndValue * 100.0) / this.alignment.length; } @computed get haveError() { return this.partToAdd.end.haveError; } @computed get errorMessage() { - return this.haveError ? `End position error: ${this.partToAdd.end.errorMessage}` : ''; + return this.haveError + ? `End position error: ${this.partToAdd.end.errorMessage}` + : ''; } @computed get addPartDisabled() { return this.nonDefaultPartitionComplete || this.haveError; @@ -286,14 +351,14 @@ class Partition { // return this.alignment.partitionFileContent; return this.defaultPartition.text; } - return this.parts.map(part => part.text).join('\n'); + return this.parts.map((part) => part.text).join('\n'); } transform = (offset = 0, prefix = '') => { if (this.isDefault) { return this.defaultPartition.transform(offset, prefix); } - return this.parts.map(part => part.transform(offset, prefix)).join('\n'); - } + return this.parts.map((part) => part.transform(offset, prefix)).join('\n'); + }; @action reset = () => { if (this.parts.length > 0) { @@ -302,7 +367,7 @@ class Partition { this.partToAdd.start.value = 1; this.partToAdd.end.value = 1; } - } + }; } class FinalPartition { @@ -321,21 +386,23 @@ class FinalPartition { const partitionTexts = []; let total = 0; this.run.alignments.forEach((alignment, index) => { - partitionTexts.push(alignment.partition.transform(total, `${index}_`)) + partitionTexts.push(alignment.partition.transform(total, `${index}_`)); total += alignment.length; }); return partitionTexts.join('\n'); } @computed get isDefault() { - return this.run.alignments.length === 0 || - (this.run.alignments.length === 1 && this.run.alignments[0].partition.isDefault); + return ( + this.run.alignments.length === 0 || + (this.run.alignments.length === 1 && + this.run.alignments[0].partition.isDefault) + ); } @computed get isComplete() { - return this.run.alignments.every(alignment => alignment.partition.isComplete); + return this.run.alignments.every( + (alignment) => alignment.partition.isComplete, + ); } } -export { - Partition as default, - FinalPartition, -} +export { Partition as default, FinalPartition }; diff --git a/src/app/store/Run.js b/src/app/store/Run.js index 6a4d6c3b..43c0c871 100644 --- a/src/app/store/Run.js +++ b/src/app/store/Run.js @@ -107,14 +107,15 @@ const winBinaries = // }, ]; -const likelyARM = os.arch().includes('arm64') || os.cpus()[0].model.includes('Apple'); +const likelyARM = + os.arch().includes('arm64') || os.cpus()[0].model.includes('Apple'); const armBinaries = [ { name: 'modeltest-ng-ARM64', multithreaded: true, version: '0.1.7', type: 'modeltest', - } + }, ]; const x64Binaries = [ { @@ -122,7 +123,7 @@ const x64Binaries = [ multithreaded: true, version: '0.1.7', type: 'modeltest', - } + }, ]; const allBinaries = is.windows @@ -170,7 +171,7 @@ const allBinaries = is.windows ]; const binaries = allBinaries.filter(({ multithreaded }) => - MAX_NUM_CPUS === 1 ? !multithreaded : true + MAX_NUM_CPUS === 1 ? !multithreaded : true, ); const initialBinaryName = binaries.filter(({ initial }) => initial)[0].name; @@ -367,7 +368,7 @@ class BranchLength extends Option { false, 'BS brL', 'Compute branch lengths', - 'Optimize model parameters and branch lengths for the given input tree' + 'Optimize model parameters and branch lengths for the given input tree', ); } @computed get notAvailable() { @@ -382,7 +383,7 @@ class SHlike extends Option { false, 'SH-like', 'Compute log-likelihood test', - 'Shimodaira-Hasegawa-like procedure' + 'Shimodaira-Hasegawa-like procedure', ); } @computed get notAvailable() { @@ -621,11 +622,11 @@ class AAMatrixName extends Option { run, 'BLOSUM62', 'Matrix name', - 'Amino Acid Substitution Matrix name' + 'Amino Acid Substitution Matrix name', ); } options = raxmlSettings.aminoAcidSubstitutionMatrixOptions.options.map( - (value) => ({ value, title: value }) + (value) => ({ value, title: value }), ); @computed get notAvailable() { return this.run.dataType !== 'protein'; @@ -639,7 +640,7 @@ class EstimatedFrequencies extends Option { false, 'ML Freq.', 'Estimated base frequencies', - 'Use estimated base frequencies instead of empirical.' + 'Use estimated base frequencies instead of empirical.', ); } @computed get notAvailable() { @@ -653,7 +654,7 @@ class BaseFrequencies extends Option { run, 'default', 'Base frequencies', - 'Empirical, ML estimated or model based base frequencies' + 'Empirical, ML estimated or model based base frequencies', ); } options = [ @@ -671,7 +672,7 @@ class MultistateModel extends Option { super(run, 'GTR', 'Multistate model'); } options = raxmlSettings.kMultistateSubstitutionModelOptions.options.map( - (value) => ({ value, title: value }) + (value) => ({ value, title: value }), ); @computed get notAvailable() { return this.run.dataType !== 'multistate' || this.run.usesRaxmlNg; @@ -778,7 +779,7 @@ class Run extends StoreBase { this.atomAfterRun = createAtom('AfterRun'); this.atomFinished = createAtom('finished'); this.modeltestName = binaries.filter((b) => - b.name.includes('modeltest') + b.name.includes('modeltest'), )[0]?.name; } @@ -818,7 +819,7 @@ class Run extends StoreBase { get analysisOption() { return this.raxmlNgSwitch( raxmlNgAnalysisOptions.find((opt) => opt.value === this.analysis.value), - analysisOptions.find((opt) => opt.value === this.analysis.value) + analysisOptions.find((opt) => opt.value === this.analysis.value), ); } @@ -900,10 +901,10 @@ class Run extends StoreBase { outputDir, outputName: outputNameToCheck, }, - ipc.OUTPUT_CHECKED + ipc.OUTPUT_CHECKED, ); return result; - } + }, ); @computed get outputNameOk() { @@ -1083,7 +1084,7 @@ class Run extends StoreBase { @computed get modelTestIsRunningOnAlignment() { const running = this.alignments.some( - (alignment) => alignment.modeltestLoading + (alignment) => alignment.modeltestLoading, ); return running; } @@ -1179,7 +1180,7 @@ class Run extends StoreBase { first.push('-i', quote(this.astralTree?.path)); first.push( '-o', - quote(join(this.outputDir, `ASTRAL_${this.outputNameSafe}.tre`)) + quote(join(this.outputDir, `ASTRAL_${this.outputNameSafe}.tre`)), ); return [first]; }; @@ -1197,7 +1198,7 @@ class Run extends StoreBase { // output file, modeltest errors if this file already exists first.push( '-o', - quote(join(this.outputDir, `RAxML_GUI_ModelTest_${this.outputNameSafe}`)) + quote(join(this.outputDir, `RAxML_GUI_ModelTest_${this.outputNameSafe}`)), ); // modeltest throws errors if the output file already exists first.push('--force'); @@ -1227,19 +1228,19 @@ class Run extends StoreBase { } first.push( '--prefix', - quote(join(this.outputDir, this.outputNameSafe)) + quote(join(this.outputDir, this.outputNameSafe)), ); first.push('--msa', quote(this.finalAlignment.path)); if (this.backboneConstraint.isSet) { first.push( '--tree-constraint', - quote(this.backboneConstraint.filePath) + quote(this.backboneConstraint.filePath), ); } if (this.multifurcatingConstraint.isSet) { first.push( '--tree-constraint', - quote(this.multifurcatingConstraint.filePath) + quote(this.multifurcatingConstraint.filePath), ); } break; @@ -1255,19 +1256,19 @@ class Run extends StoreBase { } first.push( '--prefix', - quote(join(this.outputDir, this.outputNameSafe)) + quote(join(this.outputDir, this.outputNameSafe)), ); first.push('--msa', quote(this.finalAlignment.path)); if (this.backboneConstraint.isSet) { first.push( '--tree-constraint', - quote(this.backboneConstraint.filePath) + quote(this.backboneConstraint.filePath), ); } if (this.multifurcatingConstraint.isSet) { first.push( '--tree-constraint', - quote(this.multifurcatingConstraint.filePath) + quote(this.multifurcatingConstraint.filePath), ); } break; @@ -1283,7 +1284,7 @@ class Run extends StoreBase { } first.push( '--prefix', - quote(join(this.outputDir, this.outputNameSafe)) + quote(join(this.outputDir, this.outputNameSafe)), ); if (!this.numThreads.notAvailable) { first.push('--threads', this.numThreads.value); @@ -1296,13 +1297,13 @@ class Run extends StoreBase { if (this.backboneConstraint.isSet) { first.push( '--tree-constraint', - quote(this.backboneConstraint.filePath) + quote(this.backboneConstraint.filePath), ); } if (this.multifurcatingConstraint.isSet) { first.push( '--tree-constraint', - quote(this.multifurcatingConstraint.filePath) + quote(this.multifurcatingConstraint.filePath), ); } break; @@ -1320,7 +1321,7 @@ class Run extends StoreBase { } first.push( '--prefix', - quote(join(this.outputDir, this.outputNameSafe)) + quote(join(this.outputDir, this.outputNameSafe)), ); first.push('--seed', this.seedParsimony); if (!this.numThreads.notAvailable) { @@ -1335,13 +1336,13 @@ class Run extends StoreBase { if (this.backboneConstraint.isSet) { first.push( '--tree-constraint', - quote(this.backboneConstraint.filePath) + quote(this.backboneConstraint.filePath), ); } if (this.multifurcatingConstraint.isSet) { first.push( '--tree-constraint', - quote(this.multifurcatingConstraint.filePath) + quote(this.multifurcatingConstraint.filePath), ); } break; @@ -1359,7 +1360,7 @@ class Run extends StoreBase { } first.push( '--prefix', - quote(join(this.outputDir, this.outputNameSafe)) + quote(join(this.outputDir, this.outputNameSafe)), ); first.push('--seed', this.seedParsimony); if (!this.numThreads.notAvailable) { @@ -1374,13 +1375,13 @@ class Run extends StoreBase { if (this.backboneConstraint.isSet) { first.push( '--tree-constraint', - quote(this.backboneConstraint.filePath) + quote(this.backboneConstraint.filePath), ); } if (this.multifurcatingConstraint.isSet) { first.push( '--tree-constraint', - quote(this.multifurcatingConstraint.filePath) + quote(this.multifurcatingConstraint.filePath), ); } break; @@ -1398,7 +1399,7 @@ class Run extends StoreBase { } first.push( '--prefix', - quote(join(this.outputDir, this.outputNameSafe)) + quote(join(this.outputDir, this.outputNameSafe)), ); first.push('--tree', quote(this.tree.filePath)); break; @@ -1449,7 +1450,7 @@ class Run extends StoreBase { if (this.branchLength.value) { const treeFile1 = join( this.outputDir, - `RAxML_fastTree.${this.outputFilenameSafe}` + `RAxML_fastTree.${this.outputFilenameSafe}`, ); const next = []; if (!this.numThreads.notAvailable) { @@ -1477,7 +1478,7 @@ class Run extends StoreBase { const treeFile2 = this.branchLength.value ? join( this.outputDir, - `RAxML_result.brL.${this.outputFilenameSafe}` + `RAxML_result.brL.${this.outputFilenameSafe}`, ) : join(this.outputDir, `RAxML_fastTree.${this.outputFilenameSafe}`); const next = []; @@ -1543,7 +1544,7 @@ class Run extends StoreBase { if (this.sHlike.value) { const treeFile = join( this.outputDir, - `RAxML_bestTree.${this.outputFilenameSafe}` + `RAxML_bestTree.${this.outputFilenameSafe}`, ); const next = []; if (!this.numThreads.notAvailable) { @@ -1626,11 +1627,11 @@ class Run extends StoreBase { const outputFilenameSafe2 = `${this.outputNameSafe}B.tre`; const treeFile = join( this.outputDir, - `RAxML_bestTree.${outputFilenameSafe2}` + `RAxML_bestTree.${outputFilenameSafe2}`, ); // MLtreeR const treesFile = join( this.outputDir, - `RAxML_bootstrap.${outputFilenameSafe1}` + `RAxML_bootstrap.${outputFilenameSafe1}`, ); // trees // first wrote RAxML_bootstrap.binary_8R.tre // second wrote RAxML_bootstrap.binary_8B.tre @@ -1737,7 +1738,7 @@ class Run extends StoreBase { const bsTreeFile = join( this.outputDir, - `RAxML_bootstrap.${this.outputFilenameSafe}` + `RAxML_bootstrap.${this.outputFilenameSafe}`, ); const consensusOutput = `consensus.${this.outputFilenameSafe}`; @@ -1921,7 +1922,7 @@ Results saved to: ${this.outputDir} `; let argumentext = 'RAxML was called with these arguments:\n'; this.args.map( - (arg, index) => (argumentext += `${index + 1}.) ${arg.join(' ')}\n`) + (arg, index) => (argumentext += `${index + 1}.) ${arg.join(' ')}\n`), ); text += argumentext; // TODO: should we be more precise about what the single params mean? @@ -1934,7 +1935,7 @@ Results saved to: ${this.outputDir} @computed get settingsFilePath() { return join( `${this.outputDir}`, - `RAxML_GUI_Settings_${this.outputNameSafe}.txt` + `RAxML_GUI_Settings_${this.outputNameSafe}.txt`, ); } @@ -1984,8 +1985,8 @@ Results saved to: ${this.outputDir} if (this.finalAlignment.numSequences <= 3) { this.parent.onError( new UserFixError( - 'To start a run with RAxML the final alignment needs to have at least four sequences.' - ) + 'To start a run with RAxML the final alignment needs to have at least four sequences.', + ), ); return; } @@ -2280,7 +2281,7 @@ Results saved to: ${this.outputDir} onRunFinished = (event, { id, resultDir, resultFilenames, exitCode }) => { if (id === this.id) { console.log( - `Process ${id} finished with exitCode '${exitCode}' and result filenames ${resultFilenames} in dir ${resultDir}.` + `Process ${id} finished with exitCode '${exitCode}' and result filenames ${resultFilenames} in dir ${resultDir}.`, ); this.resultDir = resultDir; this.atomFinished.reportChanged(); diff --git a/src/app/store/RunList.js b/src/app/store/RunList.js index 9ca6c845..41bebd0b 100644 --- a/src/app/store/RunList.js +++ b/src/app/store/RunList.js @@ -83,7 +83,7 @@ class RunList extends AppStore { this.listenTo(ipc.TOGGLE_BACKBONE_CONSTRAINT, this.onBackboneConstraint); this.listenTo( ipc.TOGGLE_MULTIFURCATING_CONSTRAINT, - this.onMultifurcatingConstraint + this.onMultifurcatingConstraint, ); }; diff --git a/src/app/theme/index.js b/src/app/theme/index.js index ddff174f..250df88c 100644 --- a/src/app/theme/index.js +++ b/src/app/theme/index.js @@ -197,15 +197,15 @@ const lightTheme = { // Used by the functions below to shift a color's luminance by approximately // two indexes within its tonal palette. // E.g., shift from Red 500 to Red 300 or Red 700. - tonalOffset: 0.2 + tonalOffset: 0.2, }, // Migration to typography v2 typography: { - useNextVariants: true - } + useNextVariants: true, + }, }; export default { light: createTheme(lightTheme), - dark: createTheme(darkTheme) + dark: createTheme(darkTheme), }; diff --git a/src/common/errors.js b/src/common/errors.js index f746d085..5451748a 100644 --- a/src/common/errors.js +++ b/src/common/errors.js @@ -1,8 +1,7 @@ export default class UserFixError extends Error { constructor(message) { super(message); - this.name = "UserFixError"; + this.name = 'UserFixError'; this.isUserFix = true; } } - diff --git a/src/common/fastaParser.js b/src/common/fastaParser.js index 38d3ca10..8959193c 100644 --- a/src/common/fastaParser.js +++ b/src/common/fastaParser.js @@ -1,4 +1,4 @@ -import UserFixError from "./errors"; +import UserFixError from './errors'; export const isFasta = (lines) => { for (let i = 0; i < lines.length; ++i) { @@ -11,10 +11,9 @@ export const isFasta = (lines) => { } } return false; -} +}; export const parse = (lines) => { - if (!isFasta(lines)) { throw new Error(`Could not parse the file as a FASTA file`); } @@ -30,14 +29,14 @@ export const parse = (lines) => { throw new Error(`Empty taxon at line ${lineIndex + 1}`); } return taxon; - } + }; const parseCode = (line) => { const code = line.replace(/\s+/g, ''); if (!code) { throw new Error(`Empty sequence at line ${lineIndex + 1}`); } return code; - } + }; const sequences = []; let taxon = ''; @@ -47,7 +46,7 @@ export const parse = (lines) => { const code = codeLines.join(''); sequences.push({ taxon, code }); codeLines = []; - } + }; for (; lineIndex < lines.length; ++lineIndex) { const line = lines[lineIndex]; @@ -60,10 +59,11 @@ export const parse = (lines) => { codeLines = []; } taxon = parseTaxon(line); - } - else { + } else { if (!taxon) { - throw new Error(`'No taxon line found before line ${lineIndex+1} ('${line}')`); + throw new Error( + `'No taxon line found before line ${lineIndex + 1} ('${line}')`, + ); } codeLines.push(parseCode(line)); } @@ -75,7 +75,9 @@ export const parse = (lines) => { // Check that all sequnces have the same length; for (let seq of sequences) { if (seq.code.length !== length) { - throw new UserFixError(`Sequence '${seq.taxon}' has different length (${seq.code.length}) than previous taxons (${length})`); + throw new UserFixError( + `Sequence '${seq.taxon}' has different length (${seq.code.length}) than previous taxons (${length})`, + ); } } @@ -88,8 +90,7 @@ export const parse = (lines) => { }; return alignment; - -} +}; export default { isFasta, diff --git a/src/common/io.js b/src/common/io.js index d0717d09..9e89d6f6 100644 --- a/src/common/io.js +++ b/src/common/io.js @@ -8,9 +8,7 @@ import typecheckAlignment from './typecheckAlignment'; import UserFixError from './errors'; export const parseAlignment = async (filePath) => { - return new Promise((resolve, reject) => { - const rl = readline.createInterface({ input: fs.createReadStream(filePath), terminal: false, @@ -22,7 +20,6 @@ export const parseAlignment = async (filePath) => { rl.on('line', (line) => { lines.push(line); - }).on('close', () => { try { if (phylipParser.isPhylip(lines)) { @@ -30,19 +27,21 @@ export const parseAlignment = async (filePath) => { } else if (fastaParser.isFasta(lines)) { alignment = fastaParser.parse(lines); } else { - throw new UserFixError("Unrecognized input format. RaxmlGUI2 supports the following input types at the moment: 'clustal', 'fasta', 'nbrf', 'nexus', 'mega', 'phylip'."); + throw new UserFixError( + "Unrecognized input format. RaxmlGUI2 supports the following input types at the moment: 'clustal', 'fasta', 'nbrf', 'nexus', 'mega', 'phylip'.", + ); } - } - catch (err) { + } catch (err) { error = err; error.message = `Error parsing file ${filePath}: ${error.message}.`; } if (error) { reject(error); } else { - if (alignment.sequences.length === 0) { - return reject(new Error(`Couldn't parse any sequences from file ${filePath}`)) + return reject( + new Error(`Couldn't parse any sequences from file ${filePath}`), + ); } try { @@ -52,13 +51,15 @@ export const parseAlignment = async (filePath) => { return reject(err); } - const alignmentRestricted = Object.assign({}, alignment, { sequences: alignment.sequences.slice(0,2) }); + const alignmentRestricted = Object.assign({}, alignment, { + sequences: alignment.sequences.slice(0, 2), + }); console.log('Alignment with first two sequences:', alignmentRestricted); resolve(alignment); } }); }); -} +}; export const writeAlignment = async (filePath, alignment) => { console.log(`Write alignment in FASTA format to ${filePath}`); @@ -73,9 +74,9 @@ export const writeAlignment = async (filePath, alignment) => { await write.call(writeStream, sequence.code); } await end.call(writeStream); -} +}; export default { parseAlignment, writeAlignment, -} +}; diff --git a/src/common/phylipParser.js b/src/common/phylipParser.js index 6e8d22a5..d32a8834 100644 --- a/src/common/phylipParser.js +++ b/src/common/phylipParser.js @@ -1,20 +1,18 @@ -import UserFixError from "./errors"; +import UserFixError from './errors'; const rePhylipHeader = /^\s*(\d+)\s+(\d+)(?:\s+([is]))?\s*$/; // 3 78 i (optional i/s for interleaved/sequential) const reStrictPhylipLine = /^(.{10})(.+)$/; const reRelaxedPhylipLine = /^(\w+)\s+(.+)$/; - export const isPhylip = (lines) => { if (lines.length === 0) { throw new Error('No lines'); } return rePhylipHeader.test(lines[0]); -} +}; //TODO: Make PhylipParserError class export const parse = (lines) => { - if (!isPhylip(lines)) { throw new Error('First line is not a phylip formatted header'); } @@ -41,9 +39,21 @@ export const parse = (lines) => { } const numSeqLines = lastSeqLineIndex - firstSeqLineIndex + 1; const oneLineSequences = numSeqLines === numSequences; - console.log(`Start parsing phylip with specified ${numSequences} sequences of length ${length}...`); - console.log('firstSeqLineIndex:', firstSeqLineIndex, 'lastSeqLineIndex:', lastSeqLineIndex); - console.log('numSeqLines:', numSeqLines, 'oneLineSequences:', oneLineSequences); + console.log( + `Start parsing phylip with specified ${numSequences} sequences of length ${length}...`, + ); + console.log( + 'firstSeqLineIndex:', + firstSeqLineIndex, + 'lastSeqLineIndex:', + lastSeqLineIndex, + ); + console.log( + 'numSeqLines:', + numSeqLines, + 'oneLineSequences:', + oneLineSequences, + ); let numLinesPerTaxa = 1; const interleavedStartLineIndices = []; @@ -56,9 +66,11 @@ export const parse = (lines) => { const assertCorrectNumberOfSeqLines = (startIndex, endIndex) => { const numSeq = endIndex + 1 - startIndex; if (numSeq !== numSequences) { - throw new UserFixError(`Interleaved section (lines ${startIndex+1}-${endIndex+1}) doesn't have specified ${numSequences} lines of sequences.`); + throw new UserFixError( + `Interleaved section (lines ${startIndex + 1}-${endIndex + 1}) doesn't have specified ${numSequences} lines of sequences.`, + ); } - } + }; for (let i = firstSeqLineIndex; i <= lastSeqLineIndex; ++i) { if (lines[i].trim().length === 0) { if (lastEmpty) { @@ -78,14 +90,19 @@ export const parse = (lines) => { isInterleaved = true; numLinesPerTaxa = interleavedStartLineIndices.length + 1; // Also check correct length of last section - const lastInterleavedStartLineIndex = interleavedStartLineIndices[interleavedStartLineIndices.length - 1]; - assertCorrectNumberOfSeqLines(lastInterleavedStartLineIndex, lastSeqLineIndex); + const lastInterleavedStartLineIndex = + interleavedStartLineIndices[interleavedStartLineIndices.length - 1]; + assertCorrectNumberOfSeqLines( + lastInterleavedStartLineIndex, + lastSeqLineIndex, + ); console.log('interleavedStartLineIndices:', interleavedStartLineIndices); - } - else { + } else { // Should be sequential if (isInterleaved) { - throw new UserFixError(`File is specified as interleaved in header but no empty lines separating interleaved sections found`); + throw new UserFixError( + `File is specified as interleaved in header but no empty lines separating interleaved sections found`, + ); } // Is sequential, check consistent number of lines for each taxa // (the total number of sequence lines should be a multiple of the number of sequences specified) @@ -94,16 +111,21 @@ export const parse = (lines) => { ++numLinesPerTaxa; } if (numSeqLines !== numLinesPerTaxa * numSequences) { - throw new UserFixError(`Inferred sequential phylip format because of no empty lines between sequences, but the number of sequence lines (${numSeqLines}) is not a multiple of the specified number of sequences (${numSequences}).`); + throw new UserFixError( + `Inferred sequential phylip format because of no empty lines between sequences, but the number of sequence lines (${numSeqLines}) is not a multiple of the specified number of sequences (${numSequences}).`, + ); } - console.log(`Is sequential format with ${numLinesPerTaxa} consecutive lines for each sequence`); + console.log( + `Is sequential format with ${numLinesPerTaxa} consecutive lines for each sequence`, + ); } } const numConsequtiveLinesPerTaxa = isInterleaved ? 1 : numLinesPerTaxa; // Discern if strict or relaxed format, first some helper methods const getLineIndex = (seqIndex, subLineIndex = 0) => { - const startLineIndex = firstSeqLineIndex + seqIndex * numConsequtiveLinesPerTaxa; + const startLineIndex = + firstSeqLineIndex + seqIndex * numConsequtiveLinesPerTaxa; if (subLineIndex === 0) { return startLineIndex; } @@ -111,32 +133,32 @@ export const parse = (lines) => { return startLineIndex + subLineIndex; } return interleavedStartLineIndices[subLineIndex - 1]; - } + }; const getSeqLine = (seqIndex, subLineIndex = 0) => { return lines[getLineIndex(seqIndex, subLineIndex)]; - } + }; const getSeqLines = (seqIndex) => { const seqLines = []; for (let i = 0; i < numLinesPerTaxa; ++i) { seqLines.push(getSeqLine(seqIndex, i)); } return seqLines; - } + }; const parseSeqAssumingStrictName = (seqIndex) => { const line = getSeqLines(seqIndex).join(''); const taxon = line.substring(0, 10).trim(); let code = line.substring(10); // Replace possible single spaces within sequence, separating for example ten characters each - code = code.replace(/(?<=\S)\s+/g, ""); + code = code.replace(/(?<=\S)\s+/g, ''); return { taxon, code }; - } + }; const parseSeqAssumingRelaxedName = (seqIndex) => { const line = getSeqLines(seqIndex).join(''); const [taxon, ...codeChunks] = line.split(/\s+/); const code = codeChunks.join(''); return { taxon, code }; - } + }; const checkIsRelaxed = () => { console.log(`Checking if strict or relaxed from first sequence...`); // Check the length of the code part assuming strict and relaxe and see which matches @@ -150,17 +172,23 @@ export const parse = (lines) => { console.log('Is relaxed phylip format! First sequence:', seqIfRelaxed); return true; } - throw new UserFixError(`Can't find specified ${length} number of characters for first sequence. Assuming relaxed format gives length ${seqIfRelaxed.code.length} (${JSON.stringify(seqIfRelaxed)}) and assuming strict format gives length ${seqIfStrict.code.length} (${JSON.stringify(seqIfStrict)})`); - } + throw new UserFixError( + `Can't find specified ${length} number of characters for first sequence. Assuming relaxed format gives length ${seqIfRelaxed.code.length} (${JSON.stringify(seqIfRelaxed)}) and assuming strict format gives length ${seqIfStrict.code.length} (${JSON.stringify(seqIfStrict)})`, + ); + }; const isRelaxed = checkIsRelaxed(); // Parse sequences const sequences = []; - const parseSequence = isRelaxed ? parseSeqAssumingRelaxedName : parseSeqAssumingStrictName; + const parseSequence = isRelaxed + ? parseSeqAssumingRelaxedName + : parseSeqAssumingStrictName; for (let seqIndex = 0; seqIndex < numSequences; ++seqIndex) { const seq = parseSequence(seqIndex); if (seq.code.length !== length) { - throw new UserFixError(`Length ${seq.code.length} of sequence ${seqIndex + 1} (parsed taxon name '${seq.taxon}' and code '${seq.code}') doesn't match specified length of ${length}.`); + throw new UserFixError( + `Length ${seq.code.length} of sequence ${seqIndex + 1} (parsed taxon name '${seq.taxon}' and code '${seq.code}') doesn't match specified length of ${length}.`, + ); } sequences.push(seq); } @@ -177,7 +205,7 @@ export const parse = (lines) => { }; return alignment; -} +}; export default { isPhylip, diff --git a/src/common/typecheckAlignment.js b/src/common/typecheckAlignment.js index cd7646c5..ae794eb1 100644 --- a/src/common/typecheckAlignment.js +++ b/src/common/typecheckAlignment.js @@ -53,7 +53,7 @@ function hasInvariantSites(length, sequences) { for (const sequence of sequences) { const site = sequence.code[i]; if (!variantsAtPosition.includes(site)) { - variantsAtPosition.push(site) + variantsAtPosition.push(site); } } if (variantsAtPosition.length <= 1) { @@ -115,7 +115,7 @@ export default function typecheckAlignment(alignment) { console.log('At least one sequence have only unknown characters'); if (dataTypes.size === 0) { throw new Error( - `Invalid alignment: cannot determine data type because all ${numSequencesTypechecked} sequences are of type unknown` + `Invalid alignment: cannot determine data type because all ${numSequencesTypechecked} sequences are of type unknown`, ); } } @@ -123,7 +123,7 @@ export default function typecheckAlignment(alignment) { if (dataTypes.size > 1) { // Only valid case with different types is binary and multistate as [01] is a subset of [012]. const isMultistate = !sequenceDataTypes.find( - (type) => type !== 'binary' && type !== 'multistate' + (type) => type !== 'binary' && type !== 'multistate', ); if (isMultistate) { dataType = 'multistate'; @@ -131,13 +131,13 @@ export default function typecheckAlignment(alignment) { dataType = 'invalid'; console.log( 'Illegal mix of data types among sequences:', - sequenceDataTypes + sequenceDataTypes, ); throw new UserFixError( `Your alignment is a mix of different data types, namely = ${Array.from( - dataTypes.keys() + dataTypes.keys(), )}. - Please use only the same type for one alignment or combine several files.` + Please use only the same type for one alignment or combine several files.`, ); } } @@ -153,11 +153,14 @@ export default function typecheckAlignment(alignment) { invalidSiteIndex + 1 } in sequence ${ index + 1 - } (${sample}) for inferred data type '${dataType}'` + } (${sample}) for inferred data type '${dataType}'`, ); } }); - alignment.hasInvariantSites = hasInvariantSites(alignment.length, alignment.sequences); + alignment.hasInvariantSites = hasInvariantSites( + alignment.length, + alignment.sequences, + ); alignment.dataType = dataType; alignment.typecheckingComplete = true; return alignment; diff --git a/src/common/utils.js b/src/common/utils.js index b57db5ef..0c6dcc31 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -13,7 +13,7 @@ function newGithubIssueUrl(options = {}) { repoUrl = `https://github.com/${options.user}/${options.repo}`; } else { throw new Error( - 'You need to specify either the `repoUrl` option or both the `user` and `repo` options' + 'You need to specify either the `repoUrl` option or both the `user` and `repo` options', ); } @@ -98,28 +98,28 @@ const getActiveState = async () => { const win = activeWindow(); const report = await ipc.ipcMain.callRenderer(win, 'get-state-report'); return report; -} +}; const getActiveStateSync = () => { if (is.renderer) { return window.store.generateReport(); } return `Current state not available on synchronous call from main`; -} +}; const serializeAndCleanError = (error) => { const err = serializeError(error); err.stack = cleanStack(error.stack); return err; -} +}; export function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } -export const quote = dir => is.windows ? `"${dir}"` : dir; +export const quote = (dir) => (is.windows ? `"${dir}"` : dir); -const stringify = json => JSON.stringify(json, null, ' '); +const stringify = (json) => JSON.stringify(json, null, ' '); const stringifyToGithubMarkdown = (json) => `\`\`\`json ${stringify(json)} @@ -136,7 +136,8 @@ ${stringifyToGithubMarkdown(activeState)} Process: ${is.renderer ? 'renderer' : 'main'} ${debugInfo()}`; -const createReportBodyForMail = (error, activeState) => encodeURI(`Autogenerated report: +const createReportBodyForMail = (error, activeState) => + encodeURI(`Autogenerated report: ${stringify(serializeAndCleanError(error))} Active state: @@ -160,10 +161,10 @@ export const reportIssueToGitHub = async (error) => { title: error.name, body: createReportBodyForGithub(error, activeState), }); -} +}; export const getMailtoLinkToReportError = (error) => { const activeState = getActiveStateSync(); const mailtoLinkContent = `mailto:raxmlgui.help@googlemail.com?subject=${encodeURI(error.name)}&body=${createReportBodyForMail(error, activeState)}`; return mailtoLinkContent; -} +}; diff --git a/src/constants/ipc.js b/src/constants/ipc.js index c7fb30cd..c9453638 100644 --- a/src/constants/ipc.js +++ b/src/constants/ipc.js @@ -26,15 +26,22 @@ export const ALIGNMENT_PARSE_REQUEST = 'ALIGNMENT_PARSE_REQUEST'; export const ALIGNMENT_PARSE_SUCCESS = 'ALIGNMENT_PARSE_SUCCESS'; export const ALIGNMENT_PARSE_FAILURE = 'ALIGNMENT_PARSE_FAILURE'; export const ALIGNMENT_PARSE_CHANGED_PATH = 'ALIGNMENT_PARSE_CHANGED_PATH'; -export const ALIGNMENT_EXAMPLE_FILES_GET_REQUEST = 'ALIGNMENT_EXAMPLE_FILES_GET_REQUEST'; -export const ALIGNMENT_EXAMPLE_FILES_GET_SUCCESS = 'ALIGNMENT_EXAMPLE_FILES_GET_SUCCESS'; -export const ALIGNMENT_MODEL_SELECTION_REQUEST = 'ALIGNMENT_MODEL_SELECTION_REQUEST'; -export const ALIGNMENT_MODEL_SELECTION_CANCEL = 'ALIGNMENT_MODEL_SELECTION_CANCEL'; -export const ALIGNMENT_MODEL_SELECTION_SUCCESS = 'ALIGNMENT_MODEL_SELECTION_SUCCESS'; -export const ALIGNMENT_MODEL_SELECTION_FAILURE = 'ALIGNMENT_MODEL_SELECTION_FAILURE'; +export const ALIGNMENT_EXAMPLE_FILES_GET_REQUEST = + 'ALIGNMENT_EXAMPLE_FILES_GET_REQUEST'; +export const ALIGNMENT_EXAMPLE_FILES_GET_SUCCESS = + 'ALIGNMENT_EXAMPLE_FILES_GET_SUCCESS'; +export const ALIGNMENT_MODEL_SELECTION_REQUEST = + 'ALIGNMENT_MODEL_SELECTION_REQUEST'; +export const ALIGNMENT_MODEL_SELECTION_CANCEL = + 'ALIGNMENT_MODEL_SELECTION_CANCEL'; +export const ALIGNMENT_MODEL_SELECTION_SUCCESS = + 'ALIGNMENT_MODEL_SELECTION_SUCCESS'; +export const ALIGNMENT_MODEL_SELECTION_FAILURE = + 'ALIGNMENT_MODEL_SELECTION_FAILURE'; export const UNHANDLED_ERROR = 'UNHANDLED_ERROR'; export const TOGGLE_BACKBONE_CONSTRAINT = 'TOGGLE_BACKBONE_CONSTRAINT'; -export const TOGGLE_MULTIFURCATING_CONSTRAINT = 'TOGGLE_MULTIFURCATING_CONSTRAINT'; +export const TOGGLE_MULTIFURCATING_CONSTRAINT = + 'TOGGLE_MULTIFURCATING_CONSTRAINT'; export const ADD_RUN = 'ADD_RUN'; export const REMOVE_RUN = 'REMOVE_RUN'; export const LIGHT_MODE = 'LIGHT_MODE'; diff --git a/src/main/api.js b/src/main/api.js index cf931cb9..8286b7b4 100644 --- a/src/main/api.js +++ b/src/main/api.js @@ -20,11 +20,12 @@ import { is, platform, quote } from '../common/utils'; import UserFixError from '../common/errors'; import { activeWindow } from './utils/utils'; - -is.development ? null : Sentry.init({ - dsn: 'https://d92efa46c2ba43f38250b202c791a2c2@o117148.ingest.sentry.io/6517975', - maxValueLength: 2000, -}); +is.development + ? null + : Sentry.init({ + dsn: 'https://d92efa46c2ba43f38250b202c791a2c2@o117148.ingest.sentry.io/6517975', + maxValueLength: 2000, + }); const fs = _fs.promises; @@ -73,7 +74,7 @@ function send(event, channel, data) { } return event.sender.send( channel, - Object.assign({}, data, { error: serializeError(data.error) }) + Object.assign({}, data, { error: serializeError(data.error) }), ); } @@ -179,10 +180,10 @@ ipcMain.on(ipc.OUTPUT_CHECK, async (event, data) => { filename.startsWith(`RAxML_GUI_ModelTest_${outputNameUnused}.`) || filename.startsWith(`RAxML_GUI_Settings_${outputNameUnused}.`); const resultFilenamesAdditional = filenames.filter( - filterResultFilenamesAdditional + filterResultFilenamesAdditional, ); const resultFilenames = resultFilenamesMain.concat( - resultFilenamesAdditional + resultFilenamesAdditional, ); let counter = 1; const matchCounterName = /(\w+)_\d+$/.exec(outputName); @@ -252,7 +253,7 @@ ipcMain.on( usesRaxmlNg, usesModeltestNg, inputPath, - } + }, ) => { cancelProcess(id); @@ -260,7 +261,7 @@ ipcMain.on( console.log( `Run ${id}:\n output filename id: ${outputFilename}\n output dir: ${outputDir}\n binary: ${binaryName}\n binary path: ${binaryDir}\n args:`, - args + args, ); // Check for deleted input file @@ -269,7 +270,7 @@ ipcMain.on( // The check succeeded } catch (err) { const error = new UserFixError( - `The input file does not exist '${inputPath}': ${err.message}` + `The input file does not exist '${inputPath}': ${err.message}`, ); Sentry.captureException(err); send(event, ipc.RUN_ERROR, { id, error }); @@ -284,7 +285,7 @@ ipcMain.on( } catch (err) { console.error('Error writing to output file:', err); const error = new Error( - `Error trying to write to output file '${resultFilePath}': ${err.message}` + `Error trying to write to output file '${resultFilePath}': ${err.message}`, ); Sentry.captureException(err); send(event, ipc.RUN_ERROR, { id, error }); @@ -294,7 +295,7 @@ ipcMain.on( fs.unlink(resultFilePath); } catch (err) { console.error( - `Error trying to unlink temporary result file: ${err.message}` + `Error trying to unlink temporary result file: ${err.message}`, ); } } @@ -307,7 +308,7 @@ ipcMain.on( { // env: { PATH: binaryDir }, shell: is.windows, - } + }, ); console.log(stdout); if (stderr) { @@ -316,7 +317,7 @@ ipcMain.on( } catch (err) { console.error('Error executing binary:', err); const error = new Error( - `Error trying to execute raxml binary '${binaryPath}': ${err.message}` + `Error trying to execute raxml binary '${binaryPath}': ${err.message}`, ); Sentry.captureException(err); send(event, ipc.RUN_ERROR, { id, error }); @@ -335,7 +336,7 @@ ipcMain.on( `"${binaryPath}" ${arg.join(' ')} --flag-check`, { shell: is.windows, - } + }, ); console.log(stdout, stderr); } catch (err) { @@ -370,7 +371,7 @@ ipcMain.on( // Rename the RAxML_info.\*.tre into RAxML_info.\*.txt console.log( - `Renaming info file 'RAxML_info\.${outputName}\.tre' -> 'RAxML_info\.${outputName}\.txt'...` + `Renaming info file 'RAxML_info\.${outputName}\.tre' -> 'RAxML_info\.${outputName}\.txt'...`, ); const anyMatch = new RegExp(`RAxML_info\.${outputName}\.tre`); const filenames = await fs.readdir(outputDir); @@ -379,7 +380,7 @@ ipcMain.on( const infoPath = path.join(outputDir, infoFiles[i]); const newPath = path.join( outputDir, - infoFiles[i].replace('.tre', '.txt') + infoFiles[i].replace('.tre', '.txt'), ); await fs.rename(infoPath, newPath); } @@ -387,11 +388,13 @@ ipcMain.on( // Rename the raxml-ng output files to add .tre or .txt extension if (usesRaxmlNg) { console.log( - `Renaming raxml-ng output files to add .tre or .txt extension...` + `Renaming raxml-ng output files to add .tre or .txt extension...`, ); const anyMatch = new RegExp(`${outputName}.raxml`); const filenames = await fs.readdir(outputDir); - const outputFiles = filenames.filter((filename) => anyMatch.test(filename)); + const outputFiles = filenames.filter((filename) => + anyMatch.test(filename), + ); const rbaMatch = new RegExp(`${outputName}.raxml.rba`); const bestModelMatch = new RegExp(`${outputName}.raxml.bestModel`); const logMatch = new RegExp(`${outputName}.raxml.log`); @@ -401,10 +404,15 @@ ipcMain.on( if (rbaMatch.test(f)) { return f; } - if (bestModelMatch.test(f) || logMatch.test(f) || aPMatch.test(f) || aSMatch.test(f)) { - return f += '.txt'; + if ( + bestModelMatch.test(f) || + logMatch.test(f) || + aPMatch.test(f) || + aSMatch.test(f) + ) { + return (f += '.txt'); } - return f += '.tre'; + return (f += '.tre'); }); for (let i = 0; i < outputFiles.length; i++) { const infoPath = path.join(outputDir, outputFiles[i]); @@ -413,10 +421,9 @@ ipcMain.on( } } - const nextFilenames = await fs.readdir(outputDir); const resultFilenames = nextFilenames.filter((filename) => - filename.includes(outputName) + filename.includes(outputName), ); send(event, ipc.RUN_FINISHED, { @@ -425,7 +432,7 @@ ipcMain.on( resultFilenames, exitCode, }); - } + }, ); ipcMain.on(ipc.RUN_CANCEL, (event, arg) => { @@ -451,7 +458,7 @@ function spawnProcess(binaryDir, binaryName, args) { args, { shell: is.windows, - } + }, ); return proc; } @@ -462,7 +469,7 @@ async function runProcess( binaryDir, binaryName, args, - { onStdOut = () => {}, onStdErr = () => {} } = {} + { onStdOut = () => {}, onStdErr = () => {} } = {}, ) { return new Promise((resolve, reject) => { cancelProcess(id); @@ -493,7 +500,7 @@ async function runProcess( } console.log( `Process finished with event '${message}' and error/code/signal:`, - signal || code + signal || code, ); exited = true; delete state.processes[id]; @@ -517,8 +524,8 @@ async function runProcess( new Error( `Exited with code ${ signal || code - }. Check console output for more information.` - ) + }. Check console output for more information.`, + ), ); }; @@ -616,9 +623,8 @@ ipcMain.on(ipc.ALIGNMENT_PARSE_REQUEST, async (event, { id, filePath }) => { console.log(message); // Add a digit to the end of the second sequence identicalCounter++; - alignment.sequences[ - index - ].taxon = `${sequence.taxon}_${identicalCounter}`; + alignment.sequences[index].taxon = + `${sequence.taxon}_${identicalCounter}`; modified = true; modificationMessages.push(message); } @@ -628,7 +634,7 @@ ipcMain.on(ipc.ALIGNMENT_PARSE_REQUEST, async (event, { id, filePath }) => { // Test white-space characters and excluded characters above const testInvalid = new RegExp( `[\\s${excludedCharacters.map((c) => `\\${c}`).join('')}]`, - 'g' + 'g', ); if (testInvalid.test(sequence.taxon)) { const message = `Illegal characters in sequence name = taxon '${sequence.taxon}' found.`; @@ -636,7 +642,7 @@ ipcMain.on(ipc.ALIGNMENT_PARSE_REQUEST, async (event, { id, filePath }) => { // Replace the invalid characters in taxon names with underscores alignment.sequences[index].taxon = sequence.taxon.replace( testInvalid, - '_' + '_', ); modified = true; modificationMessages.push(message); @@ -744,7 +750,7 @@ ipcMain.on(ipc.ASTRAL_REQUEST, async (event, payload) => { let exitCode = 0; try { - console.log('arg', arg) + console.log('arg', arg); console.log(`ASTRAL?`); exitCode = await runProcess(id, event, '', javaBin, arg); if (exitCode !== 0) { @@ -753,7 +759,7 @@ ipcMain.on(ipc.ASTRAL_REQUEST, async (event, payload) => { return; } throw new Error( - `Error trying to run ASTRAL, exited with code '${exitCode}'.` + `Error trying to run ASTRAL, exited with code '${exitCode}'.`, ); } } catch (err) { @@ -764,7 +770,7 @@ ipcMain.on(ipc.ASTRAL_REQUEST, async (event, payload) => { send(event, ipc.ASTRAL_SUCCESS, { id, - exitCode + exitCode, }); }); @@ -812,7 +818,7 @@ ipcMain.on(ipc.ALIGNMENT_MODEL_SELECTION_REQUEST, async (event, payload) => { return; } throw new Error( - `Error trying to run modeltest-ng, exited with code '${exitCode}'.` + `Error trying to run modeltest-ng, exited with code '${exitCode}'.`, ); } } catch (err) { @@ -828,17 +834,19 @@ ipcMain.on(ipc.ALIGNMENT_MODEL_SELECTION_REQUEST, async (event, payload) => { try { // Each '> [program]' is written three times, for BIC, AIC and AICc respectively. Use AICc. const cmdRaxml = commands.filter((cmd) => - cmd.startsWith(' > raxmlHPC-SSE3') + cmd.startsWith(' > raxmlHPC-SSE3'), )[2]; const cmdRaxmlNG = commands.filter((cmd) => - cmd.startsWith(' > raxml-ng') + cmd.startsWith(' > raxml-ng'), )[2]; const modelRaxml = /-m (\S+)/.exec(cmdRaxml)[1]; const extraFlag = /--(\S+)/.exec(cmdRaxml)?.[0]; const modelRaxmlNG = /--model (\S+)/.exec(cmdRaxmlNG)[1]; - console.log(`-> raxml: ${modelRaxml}, extraFlag: ${extraFlag}, raxml-ng: ${modelRaxmlNG}`); + console.log( + `-> raxml: ${modelRaxml}, extraFlag: ${extraFlag}, raxml-ng: ${modelRaxmlNG}`, + ); send(event, ipc.ALIGNMENT_MODEL_SELECTION_SUCCESS, { id, @@ -852,7 +860,7 @@ ipcMain.on(ipc.ALIGNMENT_MODEL_SELECTION_REQUEST, async (event, payload) => { console.error(`Couldn't parse best models from modeltest-ng output:`, err); console.log('output:', commands); const error = new Error( - `Couldn't parse best models from modeltest-ng output. Check alignment log.` + `Couldn't parse best models from modeltest-ng output. Check alignment log.`, ); error.name = 'Modeltest error'; send(event, ipc.ALIGNMENT_MODEL_SELECTION_FAILURE, { id, error }); @@ -877,7 +885,7 @@ ipcMain.on(ipc.TREE_SELECT, (event, params) => { if (filePaths.length === 0) { return; } - } + }, ) .then((result) => { console.debug(ipc.TREE_SELECT, result); diff --git a/src/main/index.js b/src/main/index.js index f6235f05..8d660a29 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -34,7 +34,8 @@ const dialog = electron.dialog; // This is the dev mode definition from Daniel's initial version of the repository, // without explanation where the --noDevServer comes from // TODO: add an explanation -const isDevMode = is.development && process.argv.indexOf('--noDevServer') === -1; +const isDevMode = + is.development && process.argv.indexOf('--noDevServer') === -1; const isE2eMode = process.env.RAXMLGUI_E2E === '1'; // Keep a global reference of the window object, if you don't, the window will @@ -86,7 +87,7 @@ autoUpdater.on('error', (error) => { log.info('Error in auto-updater. ' + error); dialog.showErrorBox( 'Error: ', - error == null ? 'unknown' : (error.stack || error).toString() + error == null ? 'unknown' : (error.stack || error).toString(), ); }); autoUpdater.on('download-progress', (progressObj) => { diff --git a/src/main/menu/index.js b/src/main/menu/index.js index 95e84636..2a57863f 100644 --- a/src/main/menu/index.js +++ b/src/main/menu/index.js @@ -18,9 +18,9 @@ const menuTemplate = [ accelerator: 'CmdOrCtrl+S', click() { saveScreenshot(); - } + }, }, - ] + ], }, ]; @@ -50,8 +50,8 @@ export default class MenuBuilder { { role: 'hideothers' }, { role: 'unhide' }, { type: 'separator' }, - { role: 'quit' } - ] + { role: 'quit' }, + ], }); } @@ -70,8 +70,8 @@ export default class MenuBuilder { label: 'Inspect element', click: () => { this.mainWindow.inspectElement(x, y); - } - } + }, + }, ]).popup(this.mainWindow); }); } diff --git a/src/main/menu/subMenuAnalysis.js b/src/main/menu/subMenuAnalysis.js index ebb66ab6..e20be13f 100644 --- a/src/main/menu/subMenuAnalysis.js +++ b/src/main/menu/subMenuAnalysis.js @@ -14,9 +14,9 @@ const subMenuAnalysis = { checked: false, click() { BrowserWindow.getFocusedWindow().webContents.send( - ipc.TOGGLE_BACKBONE_CONSTRAINT + ipc.TOGGLE_BACKBONE_CONSTRAINT, ); - } + }, }, { label: 'Use multifurcating constraint', @@ -24,13 +24,13 @@ const subMenuAnalysis = { checked: false, click() { BrowserWindow.getFocusedWindow().webContents.send( - ipc.TOGGLE_MULTIFURCATING_CONSTRAINT + ipc.TOGGLE_MULTIFURCATING_CONSTRAINT, ); - } - } - ] - } - ] + }, + }, + ], + }, + ], }; export default subMenuAnalysis; diff --git a/src/main/menu/subMenuDeveloper.js b/src/main/menu/subMenuDeveloper.js index 4bd6ec4e..f895068f 100644 --- a/src/main/menu/subMenuDeveloper.js +++ b/src/main/menu/subMenuDeveloper.js @@ -6,7 +6,7 @@ const subMenuDeveloper = { label: 'Force Error', click() { throw new Error('Testing Error'); - } + }, }, { type: 'separator' }, { role: 'toggledevtools' }, @@ -17,8 +17,8 @@ const subMenuDeveloper = { { role: 'zoomin' }, { role: 'zoomout' }, { type: 'separator' }, - { role: 'togglefullscreen' } - ] + { role: 'togglefullscreen' }, + ], }; export default subMenuDeveloper; diff --git a/src/main/menu/subMenuFile.js b/src/main/menu/subMenuFile.js index 04218ef5..c3690c70 100644 --- a/src/main/menu/subMenuFile.js +++ b/src/main/menu/subMenuFile.js @@ -11,14 +11,14 @@ const subMenuFile = { accelerator: 'CmdOrCtrl+T', click() { BrowserWindow.getFocusedWindow().webContents.send(ipc.ADD_RUN); - } + }, }, { label: 'Close Tab', accelerator: 'CmdOrCtrl+W', click() { BrowserWindow.getFocusedWindow().webContents.send(ipc.REMOVE_RUN); - } + }, }, { label: 'Theme', @@ -29,7 +29,7 @@ const subMenuFile = { checked: !store.get('darkMode'), click() { BrowserWindow.getFocusedWindow().webContents.send(ipc.LIGHT_MODE); - } + }, }, { label: 'Dark mode', @@ -37,10 +37,10 @@ const subMenuFile = { checked: store.get('darkMode'), click() { BrowserWindow.getFocusedWindow().webContents.send(ipc.DARK_MODE); - } - } - ] - } + }, + }, + ], + }, ], }; diff --git a/src/main/utils/saveScreenshot.js b/src/main/utils/saveScreenshot.js index c670a69d..dec18137 100644 --- a/src/main/utils/saveScreenshot.js +++ b/src/main/utils/saveScreenshot.js @@ -6,33 +6,32 @@ const writeFile = util.promisify(fs.writeFile); const saveScreenshot = async () => { console.log('Save screenshot...'); const win = BrowserWindow.getFocusedWindow(); - const img = await win.webContents.capturePage() + const img = await win.webContents.capturePage(); try { const file = await dialog.showSaveDialog({ - title: "Select the File Path to save", - buttonLabel: "Save", + title: 'Select the File Path to save', + buttonLabel: 'Save', // Restricting the user to only Image Files. filters: [ - { - name: "Image Files", - extensions: ["png", "jpeg", "jpg"], - }, + { + name: 'Image Files', + extensions: ['png', 'jpeg', 'jpg'], + }, ], properties: [], }); if (!file.canceled) { - console.log(`Save screenshot to '${file.filePath}'...`); - // Creating and Writing to the image.png file - // Can save the File as a jpeg file as well, - // by simply using img.toJPEG(100); - await writeFile(file.filePath.toString(), img.toPNG(), "base64"); - console.log('Saved!'); + console.log(`Save screenshot to '${file.filePath}'...`); + // Creating and Writing to the image.png file + // Can save the File as a jpeg file as well, + // by simply using img.toJPEG(100); + await writeFile(file.filePath.toString(), img.toPNG(), 'base64'); + console.log('Saved!'); } - } - catch (err) { + } catch (err) { console.log(err); } -} +}; export default saveScreenshot; diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js index 12542ba2..f02b2dae 100644 --- a/src/registerServiceWorker.js +++ b/src/registerServiceWorker.js @@ -10,12 +10,12 @@ const isLocalhost = Boolean( window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/, + ), ); export default function register() { @@ -46,7 +46,7 @@ export default function register() { function registerValidSW(swUrl) { navigator.serviceWorker .register(swUrl) - .then(registration => { + .then((registration) => { registration.onupdatefound = () => { const installingWorker = registration.installing; installingWorker.onstatechange = () => { @@ -67,7 +67,7 @@ function registerValidSW(swUrl) { }; }; }) - .catch(error => { + .catch((error) => { console.error('Error during service worker registration:', error); }); } @@ -75,14 +75,14 @@ function registerValidSW(swUrl) { function checkValidServiceWorker(swUrl) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl) - .then(response => { + .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. if ( response.status === 404 || response.headers.get('content-type').indexOf('javascript') === -1 ) { // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { + navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { window.location.reload(); }); @@ -94,14 +94,14 @@ function checkValidServiceWorker(swUrl) { }) .catch(() => { console.log( - 'No internet connection found. App is running in offline mode.' + 'No internet connection found. App is running in offline mode.', ); }); } export function unregister() { if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { + navigator.serviceWorker.ready.then((registration) => { registration.unregister(); }); } diff --git a/src/settings/raxml.js b/src/settings/raxml.js index 05dac8be..7426966d 100644 --- a/src/settings/raxml.js +++ b/src/settings/raxml.js @@ -24,7 +24,7 @@ export const secondaryStructureOptions = [ 'S7F', 'S16', // default 'S16A', - 'S16B' + 'S16B', ]; // -f @@ -68,11 +68,16 @@ export const algorithmOptions = [ 'w', 'W', 'x', - 'y' + 'y', ]; // -I -export const bootstoppingOptions = ['autoFC', 'autoMR', 'autoMRE', 'autoMRE_IGN']; +export const bootstoppingOptions = [ + 'autoFC', + 'autoMR', + 'autoMRE', + 'autoMRE_IGN', +]; // -J export const consensusTreeOptions = ['MR', 'MRE', 'STRICT', 'T_']; @@ -81,7 +86,7 @@ export const consensusTreeOptions = ['MR', 'MRE', 'STRICT', 'T_']; export const kMultistateSubstitutionModelOptions = { argument: 'K', default: 'GTR', - options: ['ORDERED', 'MK', 'GTR'] + options: ['ORDERED', 'MK', 'GTR'], }; // -m @@ -106,11 +111,7 @@ export const mixedSubstitutionModelOptions = { export const mixedSubstitutionMatrixOptions = { default: 'GTR', - options: [ - 'GTR', - 'BIN', - 'MULTI', - ], + options: ['GTR', 'BIN', 'MULTI'], }; // -m @@ -131,9 +132,7 @@ export const binarySubstitutionModelOptions = { export const binarySubstitutionMatrixOptions = { argument: 'm', default: 'BIN', - options: [ - 'BIN', - ], + options: ['BIN'], }; // -m @@ -191,16 +190,14 @@ export const multistateSubstitutionModelOptions = { 'ASC_MULTICAT', 'MULTIGAMMA', 'MULTIGAMMAI', - 'ASC_MULTIGAMMA' - ] + 'ASC_MULTIGAMMA', + ], }; // -m export const multistateSubstitutionMatrixOptions = { default: 'MULTI', - options: [ - 'MULTI', - ], + options: ['MULTI'], }; // -m @@ -213,8 +210,8 @@ export const aminoAcidSubstitutionModelOptions = { 'ASC_PROTCAT', 'PROTGAMMA', 'PROTGAMMAI', - 'ASC_PROTGAMMA' - ] + 'ASC_PROTGAMMA', + ], }; // -m @@ -248,18 +245,18 @@ export const aminoAcidSubstitutionMatrixOptions = { 'LG4X', 'PROT_FILE', 'GTR_UNLINKED', - 'GTR' - ] + 'GTR', + ], }; export const modelOptions = { - 'protein': aminoAcidSubstitutionModelOptions, - 'binary': binarySubstitutionModelOptions, - 'mixed': mixedSubstitutionModelOptions, - 'multistate': multistateSubstitutionModelOptions, - 'dna': nucleotideSubstitutionModelOptions, - 'rna': nucleotideSubstitutionModelOptions, - 'nucleotide': nucleotideSubstitutionModelOptions, + protein: aminoAcidSubstitutionModelOptions, + binary: binarySubstitutionModelOptions, + mixed: mixedSubstitutionModelOptions, + multistate: multistateSubstitutionModelOptions, + dna: nucleotideSubstitutionModelOptions, + rna: nucleotideSubstitutionModelOptions, + nucleotide: nucleotideSubstitutionModelOptions, }; export const matrixOptions = { @@ -272,12 +269,11 @@ export const matrixOptions = { nucleotide: nucleotideSubstitutionMatrixOptions, }; - // -N export const numberRunsOptions = { argument: 'N', default: 1, - options: [1, 10, 20, 50, 100, 500] + options: [1, 10, 20, 50, 100, 500], }; // -N @@ -293,19 +289,24 @@ export const numberRepsOptions = { 'autoMR', 'autoMRE', 'autoMRE_IGN', - 'autoFC' - ] + 'autoFC', + ], }; // --asc-corr export const asscertainmentBiasCorrectionOptions = [ 'lewis', 'felsenstein', - 'stamatakis' + 'stamatakis', ]; // --auto-prot -export const automaticProteinModelSelectionOptions = ['ml', 'bic', 'aic', 'aicc']; +export const automaticProteinModelSelectionOptions = [ + 'ml', + 'bic', + 'aic', + 'aicc', +]; /* Boolean settings @@ -339,7 +340,7 @@ export const intermediateTreesToFileOption = OFF; // default OFF export const printBranchLengthsBootstrapOption = { argument: 'k', - default: OFF + default: OFF, }; // -M @@ -403,7 +404,7 @@ export const kimuraOption = OFF; export const randomSeedBootstrapOption = { min: 1, // TODO calc max (FF - FFFFFF) - max: 256 + max: 256, }; // -c @@ -411,7 +412,7 @@ export const distinctRateCatgeoriesOption = { min: 0, // TODO calc max (FF - FFFFFF) max: 256, - defaultValue: 25 + defaultValue: 25, }; // -p @@ -419,7 +420,7 @@ export const randomSeedParsimonyOption = { min: 1, // TODO calc max (FF - FFFFFF) max: 256, - defaultValue: Date.now() + defaultValue: Date.now(), }; // -T @@ -427,7 +428,7 @@ export const numberThreadsOption = { argument: 'T', min: 1, // TODO does a max and default value make sense? - defaultValue: 1 + defaultValue: 1, // TODO max value has to be checked dynamically with number of CPUs present }; @@ -436,14 +437,14 @@ export const randomSeedRapidBootstrapOption = { min: 1, // TODO calc max (FF - FFFFFF) max: 256, - defaultValue: Date.now() + defaultValue: Date.now(), }; // --epa-keep-placements export const epaKeepPlacementsOption = { min: 1, // TODO what is max - defaultValue: 7 + defaultValue: 7, }; /* @@ -454,7 +455,7 @@ export const epaKeepPlacementsOption = { export const bootstopCutoffOption = { min: 0, max: 1, - defaultValue: 0.03 + defaultValue: 0.03, }; // -e @@ -462,21 +463,21 @@ export const modelOptimizationPrecisionOption = { // TODO check min max min: 0.0, max: 0.1, - defaultValue: 0.1 + defaultValue: 0.1, }; // TODO not sure if this is a double setting, not specified in help // -G export const evolutionaryPlacementAlgorithmOption = { min: 0.0, - max: 1.0 + max: 1.0, }; // --epa-prob-threshold export const epaPropThresholdOption = { min: 0.0, max: 1.0, - defaultValue: 0.01 + defaultValue: 0.01, }; /* diff --git a/src/settings/raxmlng.js b/src/settings/raxmlng.js index 360fa76e..eba12e7f 100644 --- a/src/settings/raxmlng.js +++ b/src/settings/raxmlng.js @@ -1,9 +1,7 @@ // --model export const binarySubstitutionModelOptions = { default: 'BIN', - options: [ - 'BIN' - ] + options: ['BIN'], }; // --model @@ -31,17 +29,14 @@ export const nucleotideSubstitutionModelOptions = { 'TVMef', 'TVM', 'SYM', - 'GTR' - ] + 'GTR', + ], }; // --model export const multistateSubstitutionModelOptions = { default: 'MULTIx_GTR', - options: [ - 'MULTIx_MK', - 'MULTIx_GTR' - ] + options: ['MULTIx_MK', 'MULTIx_GTR'], }; // --model @@ -70,8 +65,8 @@ export const aminoAcidSubstitutionModelOptions = { 'WAG', 'LG4M', 'LG4X', - 'PROTGTR' - ] + 'PROTGTR', + ], }; // --model @@ -79,20 +74,20 @@ export const stationaryFrequenciesOptions = { options: [ { value: '+F', - label: '+F (empirical)' + label: '+F (empirical)', }, { value: '+FO', - label: '+FO (ML estimate)' + label: '+FO (ML estimate)', }, { value: '+FE', - label: '+FE (equal)' + label: '+FE (equal)', }, // Also posibble are user defined values // +FU{f1/f2/../fn} (user-defined: f1 f2 ... fn) // +FU{freqs.txt} (user-defined from file) - ] + ], }; // --model @@ -100,15 +95,15 @@ export const proportionOfInvariantSitesOptions = { options: [ { value: '+I', - label: '+I (ML estimate)' + label: '+I (ML estimate)', }, { value: '+IC', - label: '+IC (empirical)' + label: '+IC (empirical)', }, // Also posibble are user defined values // +IU{p} (user-defined: p) - ] + ], }; // --model @@ -116,20 +111,18 @@ export const amongsiteRateHeterogeneityModelOptions = { options: [ { value: '+G', - label: - '+GAMMA (mean)' + label: '+GAMMA (mean)', }, { value: '+GA', - label: - '+GAMMA (median)' - } + label: '+GAMMA (median)', + }, // Also posibble are user defined values // +Gn (discrete GAMMA with n categories', 'ML estimate of alpha) // +Gn{a} (discrete GAMMA with n categories and user-defined alpha a) // +Rn (FreeRate with n categories', 'ML estimate of rates and weights) // +Rn{r1/r2/../rn}{w1/w2/../wn} (FreeRate with n categories', 'user-defined rates r1 r2 ... rn and weights w1 w2 ... wn) - ] + ], }; // --model @@ -137,20 +130,19 @@ export const ascertainmentBiasCorrectionOptions = { options: [ { value: '+ASC_LEWIS', - label: "Lewis' method" + label: "Lewis' method", }, // Also posibble are user defined values // +ASC_FELS{w} (Felsenstein's method with total number of invariable sites w) // +ASC_STAM{w1/w2/../wn} (Stamatakis' method with per-state invariable site numbers w1 w2 ... wn) - ] + ], }; - export const modelOptions = { - 'protein': aminoAcidSubstitutionModelOptions, - 'binary': binarySubstitutionModelOptions, - 'multistate': multistateSubstitutionModelOptions, - 'dna': nucleotideSubstitutionModelOptions, - 'rna': nucleotideSubstitutionModelOptions, - 'nucleotide': nucleotideSubstitutionModelOptions, + protein: aminoAcidSubstitutionModelOptions, + binary: binarySubstitutionModelOptions, + multistate: multistateSubstitutionModelOptions, + dna: nucleotideSubstitutionModelOptions, + rna: nucleotideSubstitutionModelOptions, + nucleotide: nucleotideSubstitutionModelOptions, }; diff --git a/yarn.lock b/yarn.lock index 78ebbe8d..09a19738 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12253,10 +12253,10 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== -prettier@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393" - integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== +prettier@^3.9.6: + version "3.9.6" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.6.tgz#b3ea5146515d40fc53f18aa63f74dfab1e10dbf6" + integrity sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g== pretty-bytes@^4.0.2: version "4.0.2"