+
@@ -145,12 +150,7 @@ export class ChemiscopeView extends ChemiscopeBaseView {
this._updatePythonSettings();
})
.catch((e: Error) => {
- // eslint-disable-next-line no-console
- console.error(e);
-
- const display = getByID(`${this.guid}-error-display`, element);
- display.style.display = 'block';
- display.getElementsByTagName('p')[0].innerText = e.toString();
+ logger.error(e);
});
if (!this.model.get('has_metadata')) {
@@ -177,10 +177,10 @@ export class StructureView extends ChemiscopeBaseView {
// and then inserting this.el inside the HTML document.
const element = this.el;
- addWarningHandler((message) => {
+ logger.addHandler('warn', (message) => {
const display = getByID(`${this.guid}-warning-display`, element);
display.style.display = 'block';
- display.getElementsByTagName('p')[0].innerText = message;
+ display.getElementsByTagName('p')[0].innerText = message as string;
});
element.innerHTML = `
@@ -258,10 +258,10 @@ export class MapView extends ChemiscopeBaseView {
// and then inserting this.el inside the HTML document.
const element = this.el;
- addWarningHandler((message) => {
+ logger.addHandler('warn', (message) => {
const display = getByID(`${this.guid}-warning-display`, element);
display.style.display = 'block';
- display.getElementsByTagName('p')[0].innerText = message;
+ display.getElementsByTagName('p')[0].innerText = message as string;
});
element.innerHTML = `
diff --git a/src/index.ts b/src/index.ts
index 86b182900..17bb47705 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -20,14 +20,7 @@ import {
UserStructure,
} from './dataset';
import { JsObject, getTarget, validateDataset } from './dataset';
-import {
- GUID,
- PositioningCallback,
- WarningHandler,
- addWarningHandler,
- getNextColor,
- sendWarning,
-} from './utils';
+import { GUID, PositioningCallback, WarningHandler, getNextColor, logger } from './utils';
import {
ArrowParameters,
CustomShapeParameters,
@@ -730,7 +723,7 @@ class MapVisualizer {
for (const key in dataset.properties) {
const property = dataset.properties[key];
if (property.target === 'atom') {
- sendWarning('unsupported per-atom property in a map-only viewer');
+ logger.warn('unsupported per-atom property in a map-only viewer');
} else {
assert(property.target === 'structure');
n_structure = property.values.length;
@@ -870,9 +863,9 @@ function getMapSettings(settings: Partial | undefined): Settings {
}
export {
- // free functions
- addWarningHandler,
+ // free function
version,
+ logger,
// dataset definitions
Dataset,
Metadata,
diff --git a/src/info/info.ts b/src/info/info.ts
index ef31b74e9..a53d16169 100644
--- a/src/info/info.ts
+++ b/src/info/info.ts
@@ -7,7 +7,7 @@ import assert from 'assert';
import { Parameter, Property } from '../dataset';
import { DisplayTarget, EnvironmentIndexer, Indexes } from '../indexer';
-import { binarySearch, getElement, sendWarning } from '../utils';
+import { binarySearch, getElement, logger } from '../utils';
import * as plotlyStyles from '../map/plotly/plotly-styles';
@@ -256,7 +256,7 @@ export class EnvironmentInfo {
if (this._atom !== undefined) {
const activeAtoms = this._indexer.activeAtoms(structure);
if (activeAtoms.length === 0) {
- sendWarning(
+ logger.warn(
`Cannot change to structure ${
structure + 1
}, which does not contain any active atoms`
@@ -389,7 +389,7 @@ export class EnvironmentInfo {
if (indexes === undefined) {
const structure = this._structure.slider.value();
const atom = this._atom.slider.value();
- sendWarning(
+ logger.warn(
`Environment for atom ${atom} in structure ${structure} is not part of this dataset`
);
return;
diff --git a/src/map/data.ts b/src/map/data.ts
index 8a66945d7..d76a76906 100644
--- a/src/map/data.ts
+++ b/src/map/data.ts
@@ -4,7 +4,7 @@
*/
import { Property } from '../dataset';
-import { sendWarning } from '../utils';
+import { logger } from '../utils';
/** @hidden
* Properties turned into numeric values to be displayed on the map.
@@ -146,7 +146,7 @@ export class MapData {
try {
property = propertyToNumeric(name, properties[name]);
} catch (e) {
- sendWarning(`warning: ${(e as Error).message}`);
+ logger.warn(`warning: ${(e as Error).message}`);
continue;
}
if (property !== undefined) {
diff --git a/src/map/map.ts b/src/map/map.ts
index 0e5081c04..327f099b7 100644
--- a/src/map/map.ts
+++ b/src/map/map.ts
@@ -14,7 +14,7 @@ import { Property, Settings } from '../dataset';
import { DisplayTarget, EnvironmentIndexer, Indexes } from '../indexer';
import { OptionModificationOrigin } from '../options';
-import { GUID, PositioningCallback, arrayMaxMin, sendWarning } from '../utils';
+import { GUID, PositioningCallback, arrayMaxMin, logger } from '../utils';
import { enumerate, getElement, getFirstKey } from '../utils';
import { MapData, NumericProperties, NumericProperty } from './data';
@@ -738,7 +738,7 @@ export class PropertiesMap {
arrayMaxMin(this._coordinates(axis, 0)[0] as number[])['min'] < 0 &&
axis.min.value <= 0
) {
- sendWarning(
+ logger.warn(
'This property contains negative values. Note that taking the log will discard them.'
);
}
@@ -790,7 +790,7 @@ export class PropertiesMap {
const min = axis.min.value;
const max = axis.max.value;
if (min > max) {
- sendWarning(
+ logger.warn(
`The inserted min and max values in ${name} are such that min > max! The last inserted value was reset.`
);
if (minOrMax === 'min') {
@@ -991,7 +991,7 @@ export class PropertiesMap {
const min = this._options.color.min.value;
const max = this._options.color.max.value;
if (min > max) {
- sendWarning(
+ logger.warn(
`The inserted min and max values in color are such that min > max! The last inserted value was reset.`
);
if (minOrMax === 'min') {
@@ -1028,7 +1028,7 @@ export class PropertiesMap {
const someValuesNaN = values.some((value) => isNaN(value));
if (allValuesNaN) {
- sendWarning(
+ logger.warn(
`The selected property contains only values ${invalidValues}. ` +
'To display this property, select an appropriate color scale. ' +
`The ${changed} will be set to its last value.`
@@ -1042,7 +1042,7 @@ export class PropertiesMap {
return false;
} else if (someValuesNaN) {
- sendWarning(
+ logger.warn(
`The selected property contains some values ${invalidValues}. ` +
'These values will be colored in grey.'
);
@@ -1301,7 +1301,7 @@ export class PropertiesMap {
if (min <= max) {
return [min, max];
}
- sendWarning(
+ logger.warn(
`The inserted min and max values in ${axisName} are such that min > max! The default values will be used.`
);
}
diff --git a/src/map/options.ts b/src/map/options.ts
index 37956e3fb..e5fb194e9 100644
--- a/src/map/options.ts
+++ b/src/map/options.ts
@@ -11,7 +11,7 @@ import { Settings } from '../dataset';
import { HTMLOption, OptionsGroup } from '../options';
import { optionValidator } from '../options';
import { PositioningCallback } from '../utils';
-import { arrayMaxMin, getByID, makeDraggable, sendWarning } from '../utils';
+import { arrayMaxMin, getByID, logger, makeDraggable } from '../utils';
import { NumericProperties, NumericProperty } from './data';
import * as styles from '../styles';
@@ -350,7 +350,7 @@ export class MapOptions extends OptionsGroup {
// If we need more symbols than available, we'll send a warning
// and repeat existing ones
if (symbolsCount > POSSIBLE_SYMBOLS_IN_3D.length) {
- sendWarning(
+ logger.warn(
`${symbolsCount} symbols are required, but we only have ${POSSIBLE_SYMBOLS_IN_3D.length}. Some symbols will be repeated`
);
}
diff --git a/src/options.ts b/src/options.ts
index 34ca757aa..d96aaa4cc 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -5,7 +5,7 @@
import assert from 'assert';
-import { getByID, sendWarning } from './utils';
+import { getByID, logger } from './utils';
import { Settings } from './dataset';
/**
@@ -378,7 +378,7 @@ export abstract class OptionsGroup {
// send warning if the value is invalid and omit applying it
if (value === undefined || value === null || Number.isNaN(value)) {
- sendWarning(`ignored setting '${lastKey}' with invalid value '${value}'`);
+ logger.warn(`ignored setting '${lastKey}' with invalid value '${value}'`);
return;
}
@@ -396,7 +396,7 @@ export abstract class OptionsGroup {
/* eslint-enable */
});
if (Object.keys(copy).length !== 0) {
- sendWarning(`ignored unknown settings '${JSON.stringify(copy)}'`);
+ logger.warn(`ignored unknown settings '${JSON.stringify(copy)}'`);
}
}
diff --git a/src/structure/grid.ts b/src/structure/grid.ts
index e297fff31..5faa443b1 100644
--- a/src/structure/grid.ts
+++ b/src/structure/grid.ts
@@ -18,7 +18,7 @@ import {
import { DisplayTarget, EnvironmentIndexer, Indexes } from '../indexer';
import * as styles from '../styles';
import { GUID, PositioningCallback, getElement } from '../utils';
-import { enumerate, generateGUID, getByID, getFirstKey, getNextColor, sendWarning } from '../utils';
+import { enumerate, generateGUID, getByID, getFirstKey, getNextColor, logger } from '../utils';
import { LoadOptions, MoleculeViewer } from './viewer';
@@ -745,7 +745,7 @@ export class ViewersGrid {
const remove = template.content.firstChild as HTMLElement;
remove.onclick = () => {
if (this._cellsData.size === 1) {
- sendWarning('can not remove the last viewer from the grid');
+ logger.warn('can not remove the last viewer from the grid');
return;
}
@@ -818,17 +818,17 @@ export class ViewersGrid {
private _setupGrid(nViewers: number): GUID[] {
const newGUID = [] as GUID[];
if (nViewers < 1) {
- sendWarning('Cannot delete last molecular viewer.');
+ logger.warn('Cannot delete last molecular viewer.');
return newGUID;
} else if (nViewers > this._maxViewers) {
- sendWarning(`Viewer grid cannot contain more than ${this._maxViewers} viewers.`);
+ logger.warn(`Viewer grid cannot contain more than ${this._maxViewers} viewers.`);
return newGUID;
}
// Determine best arrangement for the number of viewers requested
const arrangement = this.bestGridArrangement(nViewers);
if (this._cellsData.size > nViewers) {
- sendWarning(`Eliminating last ${this._cellsData.size - nViewers} viewers.`);
+ logger.warn(`Eliminating last ${this._cellsData.size - nViewers} viewers.`);
let i = 0;
for (const guid of this._cellsData.keys()) {
if (i >= nViewers) {
diff --git a/src/structure/options.ts b/src/structure/options.ts
index 4d4c60983..3622a7baa 100644
--- a/src/structure/options.ts
+++ b/src/structure/options.ts
@@ -10,7 +10,7 @@ import Modal from '../modal';
import { Settings } from '../dataset';
import { HTMLOption, OptionsGroup } from '../options';
import { optionValidator } from '../options';
-import { PositioningCallback, getByID, makeDraggable, sendWarning } from '../utils';
+import { PositioningCallback, getByID, logger, makeDraggable } from '../utils';
// share colormaps with the map widget
import { COLOR_MAPS } from '../map/colorscales';
@@ -188,7 +188,7 @@ export class StructureOptions extends OptionsGroup {
// now the only possible way of doing it
if ('packedCell' in settings) {
if (settings.packedCell !== false) {
- sendWarning(
+ logger.warn(
'packedCell option has been removed, but it is set to true in the settings'
);
}
diff --git a/src/structure/viewer.ts b/src/structure/viewer.ts
index cd75f5526..11c09784f 100644
--- a/src/structure/viewer.ts
+++ b/src/structure/viewer.ts
@@ -8,7 +8,7 @@ import assert from 'assert';
import * as $3Dmol from '3dmol';
import { assignBonds } from './assignBonds';
-import { arrayMaxMin, getElement, sendWarning, unreachable } from '../utils';
+import { arrayMaxMin, getElement, logger, unreachable } from '../utils';
import { PositioningCallback } from '../utils';
import { Environment, Settings, Structure } from '../dataset';
@@ -911,7 +911,7 @@ export class MoleculeViewer {
const values = this._colorValues(property, 'linear');
if (values.some((v) => v === null)) {
- sendWarning(
+ logger.warn(
'The selected structure has undefined properties for some atoms, these atoms will be colored in light gray.'
);
}
@@ -954,7 +954,7 @@ export class MoleculeViewer {
const min = this._options.color.min.value;
const max = this._options.color.max.value;
if (min > max) {
- sendWarning(
+ logger.warn(
`The inserted min and max values in color are such that min > max! The last inserted value was reset.`
);
if (minOrMax === 'min') {
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 8a55964d6..f1a7c82e6 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -7,6 +7,7 @@ import assert from 'assert';
export { makeDraggable } from './draggable';
export { addWarningHandler, sendWarning, WarningHandler } from './warnings';
+export { logger } from './logging';
/** Callback type to position an HTML element.
*
diff --git a/src/utils/logging.ts b/src/utils/logging.ts
new file mode 100644
index 000000000..2303f1ef7
--- /dev/null
+++ b/src/utils/logging.ts
@@ -0,0 +1,89 @@
+/**
+ * @packageDocumentation
+ * @module utils
+ */
+
+/**
+ * Represents the log levels available in the logger
+ */
+type Level = 'info' | 'error' | 'warn';
+
+/**
+ * A simple logging utility that supports logging messages at different levels (info, warn, error)
+ * and allows adding custom handlers for each log level.
+ */
+class Logger {
+ /**
+ * Stores the handlers for each log level. Each log level maps to a set of handler functions
+ * that will be invoked when a message is logged at that level.
+ */
+ private _handlers = new Map void>>();
+
+ /**
+ * Logs an info message
+ * @param message - the info message to log
+ */
+ info(message: string) {
+ this._log('info', message);
+ }
+
+ /**
+ * Logs a warning message
+ * @param message - the warning message to log
+ */
+ warn(message: string) {
+ this._log('warn', message);
+ }
+
+ /**
+ * Logs an error
+ * @param error - the error to log
+ */
+ error(error: unknown) {
+ const message = error instanceof Error ? `${error.message}\n${error.stack}` : String(error);
+ this._log('error', message);
+ }
+
+ /**
+ * Logs a message at a specified level and invokes any registered handlers for that level
+ * @param level - the level at which to log the message
+ * @param message - the message to log
+ */
+ private _log(level: Level, message: string) {
+ // Direct console logging
+ // eslint-disable-next-line no-console
+ console[level](message);
+
+ // Additional processing of the display if handler was provided
+ const handlers = this._handlers.get(level);
+ if (handlers) {
+ handlers.forEach((callback) => callback(message));
+ }
+ }
+
+ /**
+ * Adds a handler for a specific log level
+ * @param level - the log level to add the handler for
+ * @param callback - the handler function to call when a log of the specified level occurs
+ */
+ addHandler(level: Level, callback: (message: string | Error) => void) {
+ if (!this._handlers.has(level)) {
+ this._handlers.set(level, new Set());
+ }
+ this._handlers.get(level)!.add(callback);
+ }
+
+ /**
+ * Removes a handler for a specific log level.
+ * @param level - the log level to remove the handler for
+ * @param callback - the handler function to remove
+ */
+ removeHandler(level: Level, callback: (message: string) => void) {
+ this._handlers.get(level)?.delete(callback);
+ }
+}
+
+/**
+ * Instance of logging to be used across project
+ */
+export const logger = new Logger();