diff --git a/web-server/img/favicon.ico b/web-server/img/favicon.ico new file mode 100644 index 000000000..b37befd6b Binary files /dev/null and b/web-server/img/favicon.ico differ diff --git a/web-server/js/slycat-table-ingestion-react.js b/web-server/js/slycat-table-ingestion-react.js index a2ba931eb..2fdc617d1 100644 --- a/web-server/js/slycat-table-ingestion-react.js +++ b/web-server/js/slycat-table-ingestion-react.js @@ -1,19 +1,18 @@ import React, { useState } from "react"; export default function SlycatTableIngestion(props) { - // Declare a new UI state variables... // To track which variables are selected // selected is an array, each element corresponds to a variable, with boolean values indicating whether it's selected. - const [selected, setSelected] = useState(props.variables.map((variable, indexVars) => variable.selected)); + const [selected, setSelected] = useState( + props.variables.map((variable, indexVars) => variable.selected), + ); // To track the last selected variable const [lastSelected, setLastSelected] = useState(0); function anyVariablesSelected() { - for(var i = 0; i < selected.length; i++) - { - if( selected[i] ) - { + for (var i = 0; i < selected.length; i++) { + if (selected[i]) { return true; } } @@ -26,60 +25,50 @@ export default function SlycatTableIngestion(props) { function selectAll(e) { let property = e.target.dataset.property; - for(let [index, variableSelected] of selected.entries()) - { - if(variableSelected) - { + for (let [index, variableSelected] of selected.entries()) { + if (variableSelected) { // Find the radio button that needs to be selected based on its name and value attributes - let radio = document.querySelector(`.${props.uniqueID} input[value='${property}'][name='${index}']`); + let radio = document.querySelector( + `.${props.uniqueID} input[value='${property}'][name='${index}']`, + ); // Fire the onChange handler only if radio button is not disabled - if(!radio.disabled) - { - props.onChange({currentTarget: radio}); + if (!radio.disabled) { + props.onChange({ currentTarget: radio }); } } } } function select(event, varIndex) { - if(props.variables[varIndex].disabled) - { + if (props.variables[varIndex].disabled) { return; - } - else if(event.shiftKey) - { + } else if (event.shiftKey) { // Find start and end between lastSelected and currently shift clicked let begin = Math.min(lastSelected, varIndex); let end = Math.max(lastSelected, varIndex); // Set everything in between to selected let newSelected = selected.slice(0); - for(var i = begin; i <= end; i++) - { - if(!props.variables[i].disabled) - { + for (var i = begin; i <= end; i++) { + if (!props.variables[i].disabled) { newSelected[i] = true; } } setSelected(newSelected); // Set current clicked to lastSelected setLastSelected(varIndex); - } - else if(event.ctrlKey || event.metaKey) - { + } else if (event.ctrlKey || event.metaKey) { setLastSelected(varIndex); let newSelected = selected.slice(0); // Invert the selected state, so Ctrl + click will either selecte unselected, or unselect selected. newSelected[varIndex] = !newSelected[varIndex]; setSelected(newSelected); - } - else - { + } else { // Set last selected to current variable setLastSelected(varIndex); // Set all selected to false - let newSelected = selected.map(x => false); + let newSelected = selected.map((x) => false); // Set selected to true on current variable newSelected[varIndex] = true; setSelected(newSelected); @@ -87,109 +76,96 @@ export default function SlycatTableIngestion(props) { } const propertiesItems = props.properties.map((property, indexProps) => { - if(property.type == 'bool') - { + if (property.type == "bool") { return ( - + {property.name} - ); + } else if (property.type == "select") { + return property.values.map((value, indexVals, array) => ( + + {value} + + + )); } - else if(property.type == 'select') - { - return ( - property.values.map((value, indexVals, array) => - ( - - {value} - - - ) - ) - ); - } - }); // Figure out if radio button should be disabled function disabledRadioButton(property, value, variable) { // Check if we have a disabledValue defined for this radio button - let disabledValue = property.disabledValues && property.disabledValues[value] && property.disabledValues[value].indexOf(variable.index) > -1; + let disabledValue = + property.disabledValues && + property.disabledValues[value] && + property.disabledValues[value].indexOf(variable.index) > -1; // Also return disabled if the entire variable is disabled - return variable.disabled || disabledValue ? 'disabled' : false; + return variable.disabled || disabledValue ? "disabled" : false; } const variablesItems = props.variables.map((variable, indexVars, arrayVars) => { return ( - - select(event, indexVars)}> - {variable.name} - - { - props.properties.map((property, indexProps, arrayProps) => { - if(property.type == 'bool') - { + + select(event, indexVars)}> + {variable.name} + + {props.properties.map((property, indexProps, arrayProps) => { + if (property.type == "bool") { return ( - - ); + } else if (property.type == "select") { + return property.values.map((value, indexVals, arrayVals) => ( + + + + )); } - else if(property.type == 'select') - { - return ( - property.values.map((value, indexVals, arrayVals) => - ( - - - - ) - ) - ); - } - }) - } - + })} + ); }); @@ -202,10 +178,8 @@ export default function SlycatTableIngestion(props) { {propertiesItems} - - {variablesItems} - + {variablesItems} ); -} \ No newline at end of file +} diff --git a/web-server/plugins/slycat-parameter-image/js/Components/ControlsButtonVarOptions.js b/web-server/plugins/slycat-parameter-image/js/Components/ControlsButtonVarOptions.js index f7f38c693..b3d8183e6 100644 --- a/web-server/plugins/slycat-parameter-image/js/Components/ControlsButtonVarOptions.js +++ b/web-server/plugins/slycat-parameter-image/js/Components/ControlsButtonVarOptions.js @@ -19,7 +19,6 @@ import VariableAliasLabels from "components/VariableAliasLabels"; import ScatterplotOptions from "components/ScatterplotOptions"; import VariableRanges from "components/VariableRanges"; import "js/slycat-table-ingestion"; -import ko from "knockout"; import "../../css/controls-button-var-options.css"; import $ from "jquery"; import client from "js/slycat-web-client"; @@ -133,7 +132,7 @@ class ControlsButtonVarOptions extends React.PureComponent { // Maybe it was deleted. // Let's save to the model's artifact instead. console.log( - "Oops, we have a pointer to project data but can't save to it. Let's save to the model's artifact instead." + "Oops, we have a pointer to project data but can't save to it. Let's save to the model's artifact instead.", ); self.writeAliasesToModelArtifact(); }, @@ -157,11 +156,11 @@ class ControlsButtonVarOptions extends React.PureComponent { }, error: function () { console.log( - "Oops, can't even write aliases to model artifact. Closing dialog and popping up error dialog." + "Oops, can't even write aliases to model artifact. Closing dialog and popping up error dialog.", ); $("#" + self.modalId).modal("hide"); dialog.ajax_error( - "There was an error saving the variable alias labels to the model's artifact." + "There was an error saving the variable alias labels to the model's artifact.", )(); }, }); @@ -242,9 +241,12 @@ class ControlsButtonVarOptions extends React.PureComponent { undefined; // Check if variable is greater than zero const isGreaterThanZero = this.props.table_statistics?.[index]?.min > 0; - // If not numeric or not greater than zero, add to disabledLogVariables + // Check if custom axis min variable range is 0 or less + const isCustomAxisMinZeroOrLess = this.props.variableRanges?.[index]?.min <= 0; + // If not numeric or not greater than zero or custom min variablel range is 0 or less, + // add to disabledLogVariables // so we don't allow them for log scales. - if (!isNumericVariable || !isGreaterThanZero) { + if (!isNumericVariable || !isGreaterThanZero || isCustomAxisMinZeroOrLess) { disabledLogVariables.push(index); } } @@ -660,7 +662,7 @@ const mapStateToProps = (state, ownProps) => { min: value.min, max: value.max, }; - } + }, ); return { diff --git a/web-server/plugins/slycat-parameter-image/js/Components/ControlsSelection.js b/web-server/plugins/slycat-parameter-image/js/Components/ControlsSelection.js index c61c526f4..a46d19c77 100644 --- a/web-server/plugins/slycat-parameter-image/js/Components/ControlsSelection.js +++ b/web-server/plugins/slycat-parameter-image/js/Components/ControlsSelection.js @@ -18,13 +18,17 @@ class ControlsSelection extends React.PureComponent { { className: "btn-primary", label: "Apply" }, ], callback: function (button, valueIn) { - if (button.label === "Apply") { - let userValue = valueIn().trim(); - let numeric = self.props.metadata["column-types"][variableIndex] !== "string"; - let valueValid = userValue.length > 0; - if (valueValid && numeric && isNaN(Number(userValue))) { - valueValid = false; - } + if (button?.label === "Apply") { + const userValue = valueIn().trim(); + const numeric = self.props.metadata["column-types"][variableIndex] !== "string"; + const log_scale_type = self.props.axes_variables_scale?.[variableIndex] == "Log"; + + // Perform validity checks + const fails_length_check = userValue.length <= 0; + const fails_numeric_check = numeric && isNaN(Number(userValue)); + const fails_log_check = log_scale_type && userValue <= 0; + const valueValid = !fails_length_check && !fails_numeric_check && !fails_log_check; + if (valueValid) { self.props.element.trigger("set-value", { selection: self.props.selection, @@ -32,9 +36,13 @@ class ControlsSelection extends React.PureComponent { value: numeric ? userValue : '"' + userValue + '"', }); } else { - let alertText = "Please enter a value."; - if (numeric) { + let alertText; + if (fails_length_check) { + alertText = "Please enter a value."; + } else if (fails_numeric_check) { alertText = "Please enter a numeric value."; + } else if (fails_log_check) { + alertText = "Please enter a value greater than 0. This variable is on a log scale."; } self.set_value(variable, variableIndex, userValue, alertText); } @@ -161,7 +169,7 @@ class ControlsSelection extends React.PureComponent { // console.log(`current_selection_pinned is ${current_selection_pinned}`); // Find the unpinned items in the current selection that are visible (i.e, not off axes). const unpinned_selection_not_off_axes = unpinned_selection.filter( - (unpinned_selected_item) => !this._is_off_axes(unpinned_selected_item) + (unpinned_selected_item) => !this._is_off_axes(unpinned_selected_item), ); // console.log(`unpinned_selection_not_off_axes is ${unpinned_selection_not_off_axes}`); // Check if entire unpinned selection is off axes, because we will be @@ -356,6 +364,7 @@ const mapStateToProps = (state, ownProps) => { media_index: state.media_index, xValues: state.derived.xValues, yValues: state.derived.yValues, + axes_variables_scale: state.axesVariables, }; }; diff --git a/web-server/plugins/slycat-parameter-image/js/Components/Histogram.tsx b/web-server/plugins/slycat-parameter-image/js/Components/Histogram.tsx index 6841ee3f5..74ffb85b9 100644 --- a/web-server/plugins/slycat-parameter-image/js/Components/Histogram.tsx +++ b/web-server/plugins/slycat-parameter-image/js/Components/Histogram.tsx @@ -35,7 +35,7 @@ type HistogramProps = { }, ) => void; handleBackgroundClick: (event: React.MouseEvent) => void; - tickFormatter: (value: number) => string; + tickFormatter: ((value: number, index?: number) => string) | null; }; const Histogram: React.FC = (props) => { @@ -207,13 +207,13 @@ const Histogram: React.FC = (props) => { }) .append("title") .text((bin) => { - const x0 = tickFormatter ? tickFormatter(bin.x0) : bin.x0; - const x1 = tickFormatter ? tickFormatter(bin.x1) : bin.x1; + const x0 = tickFormatter && bin.x0 !== undefined ? tickFormatter(bin.x0) : bin.x0; + const x1 = tickFormatter && bin.x1 !== undefined ? tickFormatter(bin.x1) : bin.x1; return `Count: ${bin.length}\n\nRange: ${x0} (inclusive) to \n${x1} (exclusive, except for last bar)`; }); let x_axis = d3.axisBottom(x_scale).tickSizeOuter(0); - + // Create tick values by combining the x0 and x1 values of each bin. // That way we get a tick at start and end of each bin. let tickValues = [...bins.map((bin) => bin.x0), ...bins.map((bin) => bin.x1)]; diff --git a/web-server/plugins/slycat-parameter-image/js/Components/PSHistogram.tsx b/web-server/plugins/slycat-parameter-image/js/Components/PSHistogram.tsx index 6065902b8..1e4226911 100644 --- a/web-server/plugins/slycat-parameter-image/js/Components/PSHistogram.tsx +++ b/web-server/plugins/slycat-parameter-image/js/Components/PSHistogram.tsx @@ -125,7 +125,7 @@ const PSHistogram: React.FC = (props) => { const handleBinClick = ( event: React.MouseEvent, bin: { - range: number[]; + range: (number | undefined)[]; count: number; index: number; bins_length: number; diff --git a/web-server/plugins/slycat-parameter-image/js/ui.js b/web-server/plugins/slycat-parameter-image/js/ui.js index 790891c6f..b01da6a3f 100644 --- a/web-server/plugins/slycat-parameter-image/js/ui.js +++ b/web-server/plugins/slycat-parameter-image/js/ui.js @@ -18,7 +18,6 @@ import bookmark_manager from "js/slycat-bookmark-manager"; import * as dialog from "js/slycat-dialog"; import NoteManager from "./note-manager"; import FilterManager from "./filter-manager"; -// import d3 from "d3"; import URI from "urijs"; import * as chunker from "js/chunker"; import "./parameter-image-scatterplot"; diff --git a/webpack.common.js b/webpack.common.js index 2b19add79..b7d7ccb23 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -18,6 +18,8 @@ const branch = execSync('git rev-parse --abbrev-ref HEAD') // importing vtk rules for for vtk.js package to work var vtkRules = require('vtk.js/Utilities/config/dependency.js').webpack.core.rules; +const favicon = 'web-server/img/favicon.ico'; + module.exports = { experiments: { // Support the Top Level Await Stage 3 proposal, it makes the module an async module when await is used on the top-level. @@ -85,36 +87,43 @@ module.exports = { new HtmlWebpackPlugin({ template: 'web-server/plugins/slycat-run-command/ui.html', filename: 'ui_run_command.html', + favicon: favicon, chunks: ['ui_run_command'], }), new HtmlWebpackPlugin({ template: 'web-server/plugins/slycat-smb/ui.html', filename: 'ui_smb.html', + favicon: favicon, chunks: ['ui_smb'], }), new HtmlWebpackPlugin({ template: 'web-server/templates/slycat-projects.html', filename: 'slycat_projects.html', + favicon: favicon, chunks: ['slycat_projects'], }), new HtmlWebpackPlugin({ template: 'web-server/templates/slycat-project.html', filename: 'slycat_project.html', + favicon: favicon, chunks: ['slycat_project'], }), new HtmlWebpackPlugin({ template: 'web-server/templates/slycat-page.html', filename: 'slycat_page.html', + favicon: favicon, chunks: ['slycat_page'], }), new HtmlWebpackPlugin({ template: 'web-server/templates/slycat-model-page.html', filename: 'slycat_model.html', + favicon: favicon, chunks: ['slycat_model'], }), new HtmlWebpackPlugin({ template: 'web-server/slycat-login/index.html', filename: 'slycat_login.html', + favicon: favicon, chunks: ['slycat_login'], }), // Copying our documentation manual into the dist folder, from docs/manual/html to dist/docs