matterviz 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EmptyState.svelte +10 -2
- package/dist/FilePicker.svelte +123 -82
- package/dist/Icon.svelte +18 -12
- package/dist/MillerIndexInput.svelte +27 -21
- package/dist/api/optimade.js +6 -6
- package/dist/app.css +216 -207
- package/dist/brillouin/BrillouinZone.svelte +292 -149
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
- package/dist/brillouin/BrillouinZoneExportPane.svelte +69 -42
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
- package/dist/brillouin/BrillouinZoneScene.svelte +275 -163
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
- package/dist/brillouin/compute.js +11 -6
- package/dist/chempot-diagram/ChemPotDiagram.svelte +162 -27
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +451 -281
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2148 -1642
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -5
- package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
- package/dist/chempot-diagram/async-compute.svelte.js +77 -0
- package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
- package/dist/chempot-diagram/chempot-worker.js +11 -0
- package/dist/chempot-diagram/color.js +1 -2
- package/dist/chempot-diagram/compute.d.ts +10 -0
- package/dist/chempot-diagram/compute.js +250 -88
- package/dist/chempot-diagram/index.d.ts +2 -1
- package/dist/chempot-diagram/index.js +2 -1
- package/dist/chempot-diagram/temperature.js +8 -9
- package/dist/chempot-diagram/types.d.ts +3 -0
- package/dist/chempot-diagram/types.js +1 -0
- package/dist/colors/index.d.ts +1 -1
- package/dist/colors/index.js +5 -3
- package/dist/composition/BarChart.svelte +128 -55
- package/dist/composition/BubbleChart.svelte +102 -49
- package/dist/composition/Composition.svelte +100 -79
- package/dist/composition/Formula.svelte +108 -62
- package/dist/composition/FormulaFilter.svelte +665 -537
- package/dist/composition/PieChart.svelte +183 -108
- package/dist/composition/format.d.ts +5 -0
- package/dist/composition/format.js +20 -3
- package/dist/composition/parse.js +14 -9
- package/dist/convex-hull/ConvexHull.svelte +93 -40
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +549 -360
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +1296 -827
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +1004 -688
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +115 -28
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
- package/dist/convex-hull/ConvexHullStats.svelte +425 -328
- package/dist/convex-hull/ConvexHullTooltip.svelte +40 -16
- package/dist/convex-hull/GasPressureControls.svelte +104 -61
- package/dist/convex-hull/StructurePopup.svelte +25 -4
- package/dist/convex-hull/TemperatureSlider.svelte +45 -25
- package/dist/convex-hull/barycentric-coords.js +13 -7
- package/dist/convex-hull/demo-temperature.js +8 -4
- package/dist/convex-hull/gas-thermodynamics.js +17 -12
- package/dist/convex-hull/helpers.d.ts +9 -0
- package/dist/convex-hull/helpers.js +77 -34
- package/dist/convex-hull/thermodynamics.js +61 -56
- package/dist/convex-hull/types.d.ts +9 -14
- package/dist/convex-hull/types.js +0 -17
- package/dist/coordination/CoordinationBarPlot.svelte +227 -154
- package/dist/element/BohrAtom.svelte +55 -12
- package/dist/element/ElementHeading.svelte +7 -2
- package/dist/element/ElementPhoto.svelte +15 -9
- package/dist/element/ElementStats.svelte +10 -4
- package/dist/element/ElementTile.svelte +137 -73
- package/dist/element/Nucleus.svelte +39 -11
- package/dist/feedback/ClickFeedback.svelte +16 -5
- package/dist/feedback/DragOverlay.svelte +10 -2
- package/dist/feedback/Spinner.svelte +4 -2
- package/dist/feedback/StatusMessage.svelte +8 -2
- package/dist/fermi-surface/FermiSlice.svelte +118 -88
- package/dist/fermi-surface/FermiSurface.svelte +328 -187
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +535 -342
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
- package/dist/fermi-surface/compute.js +16 -20
- package/dist/fermi-surface/parse.js +24 -14
- package/dist/fermi-surface/symmetry.js +2 -7
- package/dist/fermi-surface/types.d.ts +3 -5
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1019 -765
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +1 -1
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +76 -22
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +2 -3
- package/dist/icons.js +47 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/io/decompress.js +1 -1
- package/dist/io/export.d.ts +3 -0
- package/dist/io/export.js +129 -143
- package/dist/io/is-binary.js +2 -3
- package/dist/io/url-drop.js +1 -2
- package/dist/isosurface/Isosurface.svelte +202 -148
- package/dist/isosurface/IsosurfaceControls.svelte +46 -28
- package/dist/isosurface/parse.js +34 -29
- package/dist/isosurface/slice.js +5 -10
- package/dist/isosurface/types.d.ts +2 -1
- package/dist/isosurface/types.js +61 -12
- package/dist/labels.js +11 -8
- package/dist/layout/FullscreenToggle.svelte +11 -2
- package/dist/layout/InfoCard.svelte +38 -6
- package/dist/layout/InfoTag.svelte +63 -32
- package/dist/layout/PropertyFilter.svelte +82 -37
- package/dist/layout/SettingsSection.svelte +85 -55
- package/dist/layout/SubpageGrid.svelte +10 -2
- package/dist/layout/json-tree/JsonNode.svelte +183 -138
- package/dist/layout/json-tree/JsonTree.svelte +499 -413
- package/dist/layout/json-tree/JsonValue.svelte +127 -99
- package/dist/layout/json-tree/utils.js +4 -2
- package/dist/marching-cubes.js +25 -2
- package/dist/math.d.ts +13 -17
- package/dist/math.js +133 -67
- package/dist/overlays/ContextMenu.svelte +65 -40
- package/dist/overlays/DraggablePane.svelte +211 -139
- package/dist/periodic-table/PeriodicTable.svelte +278 -145
- package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
- package/dist/periodic-table/PropertySelect.svelte +25 -7
- package/dist/periodic-table/TableInset.svelte +8 -3
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +446 -309
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramControls.svelte +102 -43
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +63 -40
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +71 -28
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +158 -101
- package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
- package/dist/phase-diagram/build-diagram.js +9 -9
- package/dist/phase-diagram/colors.js +1 -3
- package/dist/phase-diagram/parse.js +10 -9
- package/dist/phase-diagram/svg-to-diagram.js +53 -49
- package/dist/phase-diagram/utils.d.ts +1 -0
- package/dist/phase-diagram/utils.js +80 -25
- package/dist/plot/AxisLabel.svelte +28 -3
- package/dist/plot/BarPlot.svelte +1182 -734
- package/dist/plot/BarPlot.svelte.d.ts +2 -2
- package/dist/plot/BarPlotControls.svelte +31 -5
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +479 -329
- package/dist/plot/ColorScaleSelect.svelte +27 -6
- package/dist/plot/ElementScatter.svelte +36 -15
- package/dist/plot/FillArea.svelte +152 -95
- package/dist/plot/Histogram.svelte +934 -571
- package/dist/plot/Histogram.svelte.d.ts +1 -1
- package/dist/plot/HistogramControls.svelte +53 -9
- package/dist/plot/HistogramControls.svelte.d.ts +1 -1
- package/dist/plot/InteractiveAxisLabel.svelte +34 -11
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/Line.svelte +63 -28
- package/dist/plot/PlotControls.svelte +157 -114
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +174 -91
- package/dist/plot/PlotTooltip.svelte +45 -6
- package/dist/plot/PortalSelect.svelte +175 -147
- package/dist/plot/ReferenceLine.svelte +76 -22
- package/dist/plot/ReferenceLine3D.svelte +132 -107
- package/dist/plot/ReferencePlane.svelte +146 -121
- package/dist/plot/ScatterPlot.svelte +1681 -1091
- package/dist/plot/ScatterPlot.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3D.svelte +256 -131
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +113 -63
- package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
- package/dist/plot/ScatterPlot3DScene.svelte +608 -403
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +65 -25
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ScatterPoint.svelte +98 -26
- package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
- package/dist/plot/SpacegroupBarPlot.svelte +142 -85
- package/dist/plot/Surface3D.svelte +159 -108
- package/dist/plot/ZeroLines.svelte +55 -3
- package/dist/plot/ZoomRect.svelte +4 -2
- package/dist/plot/axis-utils.js +1 -3
- package/dist/plot/data-cleaning.js +12 -28
- package/dist/plot/data-transform.js +2 -1
- package/dist/plot/fill-utils.js +2 -0
- package/dist/plot/layout.d.ts +4 -1
- package/dist/plot/layout.js +33 -14
- package/dist/plot/reference-line.d.ts +2 -2
- package/dist/plot/reference-line.js +7 -5
- package/dist/plot/scales.js +24 -36
- package/dist/plot/types.d.ts +11 -23
- package/dist/plot/types.js +6 -11
- package/dist/plot/utils/label-placement.d.ts +32 -15
- package/dist/plot/utils/label-placement.js +227 -66
- package/dist/plot/utils/series-visibility.js +2 -3
- package/dist/rdf/RdfPlot.svelte +143 -91
- package/dist/rdf/calc-rdf.js +4 -5
- package/dist/sanitize.d.ts +4 -0
- package/dist/sanitize.js +107 -0
- package/dist/settings.d.ts +18 -6
- package/dist/settings.js +46 -16
- package/dist/spectral/Bands.svelte +632 -453
- package/dist/spectral/BandsAndDos.svelte +90 -49
- package/dist/spectral/BrillouinBandsDos.svelte +151 -93
- package/dist/spectral/Dos.svelte +389 -258
- package/dist/spectral/helpers.js +55 -43
- package/dist/state.svelte.d.ts +1 -1
- package/dist/state.svelte.js +3 -2
- package/dist/structure/Arrow.svelte +59 -20
- package/dist/structure/AtomLegend.svelte +215 -134
- package/dist/structure/Bond.svelte +73 -47
- package/dist/structure/CanvasTooltip.svelte +10 -2
- package/dist/structure/CellSelect.svelte +72 -45
- package/dist/structure/Cylinder.svelte +33 -17
- package/dist/structure/Lattice.svelte +88 -33
- package/dist/structure/Structure.svelte +1063 -797
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureControls.svelte +349 -118
- package/dist/structure/StructureExportPane.svelte +124 -89
- package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +304 -237
- package/dist/structure/StructureScene.svelte +879 -443
- package/dist/structure/StructureScene.svelte.d.ts +15 -7
- package/dist/structure/atom-properties.js +8 -8
- package/dist/structure/bonding.js +6 -7
- package/dist/structure/export.js +14 -29
- package/dist/structure/ferrox-wasm.js +1 -1
- package/dist/structure/index.d.ts +13 -3
- package/dist/structure/index.js +83 -23
- package/dist/structure/measure.d.ts +2 -2
- package/dist/structure/measure.js +4 -44
- package/dist/structure/parse.js +113 -141
- package/dist/structure/partial-occupancy.js +7 -10
- package/dist/structure/pbc.d.ts +1 -0
- package/dist/structure/pbc.js +16 -6
- package/dist/structure/supercell.d.ts +2 -2
- package/dist/structure/supercell.js +12 -22
- package/dist/structure/validation.js +1 -2
- package/dist/symmetry/SymmetryStats.svelte +84 -41
- package/dist/symmetry/WyckoffTable.svelte +26 -6
- package/dist/symmetry/cell-transform.js +5 -3
- package/dist/symmetry/index.js +8 -7
- package/dist/symmetry/spacegroups.js +148 -148
- package/dist/table/HeatmapTable.svelte +790 -554
- package/dist/table/HeatmapTable.svelte.d.ts +1 -1
- package/dist/table/ToggleMenu.svelte +125 -92
- package/dist/table/index.js +2 -4
- package/dist/theme/ThemeControl.svelte +21 -12
- package/dist/time.js +4 -1
- package/dist/tooltip/TooltipContent.svelte +33 -8
- package/dist/trajectory/Trajectory.svelte +758 -558
- package/dist/trajectory/TrajectoryError.svelte +14 -3
- package/dist/trajectory/TrajectoryExportPane.svelte +137 -83
- package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
- package/dist/trajectory/extract.js +10 -26
- package/dist/trajectory/format-detect.js +5 -5
- package/dist/trajectory/frame-reader.d.ts +1 -1
- package/dist/trajectory/frame-reader.js +5 -12
- package/dist/trajectory/helpers.d.ts +0 -1
- package/dist/trajectory/helpers.js +2 -17
- package/dist/trajectory/index.js +14 -12
- package/dist/trajectory/parse/ase.js +5 -4
- package/dist/trajectory/parse/hdf5.js +26 -18
- package/dist/trajectory/parse/index.js +13 -18
- package/dist/trajectory/parse/lammps.js +17 -7
- package/dist/trajectory/parse/vasp.js +5 -2
- package/dist/trajectory/parse/xyz.js +8 -7
- package/dist/trajectory/plotting.js +13 -8
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +13 -0
- package/dist/xrd/XrdPlot.svelte +337 -247
- package/dist/xrd/broadening.js +14 -9
- package/dist/xrd/calc-xrd.js +12 -18
- package/dist/xrd/parse.d.ts +1 -1
- package/dist/xrd/parse.js +17 -17
- package/package.json +99 -103
- package/readme.md +1 -1
- /package/dist/theme/{themes.js → themes.mjs} +0 -0
|
@@ -34,24 +34,11 @@ export const convert_atomic_numbers = (numbers) => numbers.map((num) => {
|
|
|
34
34
|
}
|
|
35
35
|
return symbol;
|
|
36
36
|
});
|
|
37
|
-
// Cache inverse matrices by original matrix reference for performance
|
|
38
|
-
// IMPORTANT: This cache assumes lattice matrices are immutable. Mutating a cached
|
|
39
|
-
// matrix in place yields incorrect inverses. Always create new matrix instances
|
|
40
|
-
// if modifications are needed.
|
|
41
|
-
const matrix_cache = new WeakMap();
|
|
42
|
-
export const get_inverse_matrix = (matrix) => {
|
|
43
|
-
const cached = matrix_cache.get(matrix);
|
|
44
|
-
if (cached)
|
|
45
|
-
return cached;
|
|
46
|
-
const inverse = math.matrix_inverse_3x3(matrix);
|
|
47
|
-
matrix_cache.set(matrix, inverse);
|
|
48
|
-
return inverse;
|
|
49
|
-
};
|
|
50
37
|
export const create_structure = (positions, elements, lattice_matrix, pbc, force_data) => {
|
|
51
38
|
if (positions.length !== elements.length) {
|
|
52
39
|
throw new Error(`create_structure requires matching positions and elements lengths, got positions=${positions.length}, elements=${elements.length}`);
|
|
53
40
|
}
|
|
54
|
-
const
|
|
41
|
+
const cart_to_frac = lattice_matrix ? math.create_cart_to_frac(lattice_matrix) : null;
|
|
55
42
|
const is_valid_vec3 = (coords) => Array.isArray(coords) &&
|
|
56
43
|
coords.length === 3 &&
|
|
57
44
|
coords.every((value) => typeof value === `number` && Number.isFinite(value));
|
|
@@ -60,9 +47,7 @@ export const create_structure = (positions, elements, lattice_matrix, pbc, force
|
|
|
60
47
|
throw new Error(`Invalid position at index ${idx}: expected 3 finite coordinates`);
|
|
61
48
|
}
|
|
62
49
|
const xyz = pos;
|
|
63
|
-
const abc =
|
|
64
|
-
? math.mat3x3_vec3_multiply(inv_matrix, xyz)
|
|
65
|
-
: [0, 0, 0];
|
|
50
|
+
const abc = cart_to_frac ? cart_to_frac(xyz) : [0, 0, 0];
|
|
66
51
|
const force = force_data?.[idx];
|
|
67
52
|
const properties = is_valid_vec3(force) ? { force } : {};
|
|
68
53
|
return {
|
package/dist/trajectory/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { default as TrajectoryError } from './TrajectoryError.svelte';
|
|
|
3
3
|
export { default as TrajectoryExportPane } from './TrajectoryExportPane.svelte';
|
|
4
4
|
export { default as TrajectoryInfoPane } from './TrajectoryInfoPane.svelte';
|
|
5
5
|
export function validate_trajectory(trajectory) {
|
|
6
|
+
// with detailed error reporting
|
|
6
7
|
const errors = [];
|
|
7
8
|
const { frames, total_frames, indexed_frames, plot_metadata, is_indexed } = trajectory;
|
|
8
9
|
if (!frames?.length)
|
|
@@ -42,8 +43,7 @@ export function validate_trajectory(trajectory) {
|
|
|
42
43
|
else if (frame_idx.frame_number < 0) {
|
|
43
44
|
errors.push(`indexed_frames[${idx}] frame_number (${frame_idx.frame_number}) must be non-negative`);
|
|
44
45
|
}
|
|
45
|
-
else if (idx > 0 &&
|
|
46
|
-
frame_idx.frame_number <= indexed_frames[idx - 1].frame_number) {
|
|
46
|
+
else if (idx > 0 && frame_idx.frame_number <= indexed_frames[idx - 1].frame_number) {
|
|
47
47
|
errors.push(`indexed_frames[${idx}] frame_number (${frame_idx.frame_number}) must be strictly increasing`);
|
|
48
48
|
}
|
|
49
49
|
if (typeof frame_idx.byte_offset !== `number`) {
|
|
@@ -85,16 +85,18 @@ export function get_trajectory_stats(trajectory) {
|
|
|
85
85
|
if (frames.length > 0) {
|
|
86
86
|
const [first_frame, last_frame] = [frames[0], frames.at(-1) ?? frames[0]];
|
|
87
87
|
const max_sample = 100;
|
|
88
|
-
const sampled = frames.length <= max_sample
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
result
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
88
|
+
const sampled = frames.length <= max_sample
|
|
89
|
+
? frames
|
|
90
|
+
: (() => {
|
|
91
|
+
const interval = Math.floor(frames.length / max_sample);
|
|
92
|
+
const result = [first_frame];
|
|
93
|
+
for (let idx = interval; idx < frames.length - 1; idx += interval) {
|
|
94
|
+
result.push(frames[idx]);
|
|
95
|
+
}
|
|
96
|
+
if (result.at(-1) !== last_frame)
|
|
97
|
+
result.push(last_frame);
|
|
98
|
+
return result;
|
|
99
|
+
})();
|
|
98
100
|
const counts = sampled.map((frame) => frame.structure.sites.length);
|
|
99
101
|
const constant = counts.every((c) => c === counts[0]);
|
|
100
102
|
const all_counts = constant
|
|
@@ -15,8 +15,7 @@ export function parse_ase_trajectory(buffer, filename) {
|
|
|
15
15
|
const offsets_pos = Number(view.getBigInt64(offset, true));
|
|
16
16
|
if (n_items <= 0)
|
|
17
17
|
throw new Error(`Invalid frame count`);
|
|
18
|
-
if (offsets_pos < 0 ||
|
|
19
|
-
offsets_pos + n_items * 8 > buffer.byteLength) {
|
|
18
|
+
if (offsets_pos < 0 || offsets_pos + n_items * 8 > buffer.byteLength) {
|
|
20
19
|
throw new Error(`Invalid ASE frame offsets table bounds: offsets_pos=${offsets_pos}, n_items=${n_items}, byte_length=${buffer.byteLength}`);
|
|
21
20
|
}
|
|
22
21
|
const frame_offsets = Array.from({ length: n_items }, (_, idx) => Number(view.getBigInt64(offsets_pos + idx * 8, true)));
|
|
@@ -61,8 +60,10 @@ export function parse_ase_trajectory(buffer, filename) {
|
|
|
61
60
|
if (frames.length === 0)
|
|
62
61
|
throw new Error(`No valid frames found`);
|
|
63
62
|
const first_struct = frames[0]?.structure;
|
|
64
|
-
const periodic_boundary_conditions = first_struct !== null &&
|
|
65
|
-
|
|
63
|
+
const periodic_boundary_conditions = first_struct !== null &&
|
|
64
|
+
first_struct !== undefined &&
|
|
65
|
+
typeof first_struct === `object` &&
|
|
66
|
+
`lattice` in first_struct
|
|
66
67
|
? first_struct.lattice.pbc
|
|
67
68
|
: [true, true, true];
|
|
68
69
|
const metadata = {
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
import { calc_lattice_params, transpose_3x3_matrix } from '../../math';
|
|
3
3
|
import * as h5wasm from 'h5wasm';
|
|
4
4
|
import { convert_atomic_numbers, create_trajectory_frame, validate_3x3_matrix, } from '../helpers';
|
|
5
|
-
const is_hdf5_dataset = (entity) => entity !== null &&
|
|
6
|
-
const is_hdf5_group = (entity) => entity !== null &&
|
|
5
|
+
const is_hdf5_dataset = (entity) => entity !== null && `to_array` in entity && entity instanceof h5wasm.Dataset;
|
|
6
|
+
const is_hdf5_group = (entity) => entity !== null && `keys` in entity && entity instanceof h5wasm.Group;
|
|
7
7
|
export async function parse_torch_sim_hdf5(buffer, filename) {
|
|
8
8
|
const { FS } = await h5wasm.ready;
|
|
9
|
-
const file_basename = filename
|
|
9
|
+
const file_basename = filename
|
|
10
|
+
?.split(`/`)
|
|
11
|
+
.at(-1)
|
|
12
|
+
?.replace(/[^\w.-]/g, `_`) || `temp`;
|
|
10
13
|
const unique_suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
11
14
|
const temp_filename = `${file_basename}-${unique_suffix}.h5`;
|
|
12
15
|
FS.writeFile(temp_filename, new Uint8Array(buffer));
|
|
@@ -37,9 +40,13 @@ export async function parse_torch_sim_hdf5(buffer, filename) {
|
|
|
37
40
|
};
|
|
38
41
|
return discover(h5_file);
|
|
39
42
|
};
|
|
40
|
-
const positions_data = find_dataset([`positions`, `coords`, `coordinates`])
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
const positions_data = find_dataset([`positions`, `coords`, `coordinates`])?.to_array();
|
|
44
|
+
const atomic_numbers_data = find_dataset([
|
|
45
|
+
`atomic_numbers`,
|
|
46
|
+
`numbers`,
|
|
47
|
+
`Z`,
|
|
48
|
+
`species`,
|
|
49
|
+
])?.to_array();
|
|
43
50
|
const cells_data = find_dataset([`cell`, `cells`, `lattice`])?.to_array();
|
|
44
51
|
const energies_data = find_dataset([`potential_energy`, `energy`])?.to_array();
|
|
45
52
|
if (!positions_data || !atomic_numbers_data) {
|
|
@@ -68,9 +75,7 @@ export async function parse_torch_sim_hdf5(buffer, filename) {
|
|
|
68
75
|
const frame_atomic_numbers = atomic_numbers[idx] || atomic_numbers[0];
|
|
69
76
|
const frame_elements = convert_atomic_numbers(frame_atomic_numbers);
|
|
70
77
|
const cell = cells_data?.[idx];
|
|
71
|
-
const lattice_mat = cell
|
|
72
|
-
? transpose_3x3_matrix(validate_3x3_matrix(cell))
|
|
73
|
-
: undefined;
|
|
78
|
+
const lattice_mat = cell ? transpose_3x3_matrix(validate_3x3_matrix(cell)) : undefined;
|
|
74
79
|
const energy_entry = energies_data?.[idx];
|
|
75
80
|
const energy = Array.isArray(energy_entry) ? energy_entry[0] : energy_entry;
|
|
76
81
|
const metadata = {};
|
|
@@ -82,27 +87,28 @@ export async function parse_torch_sim_hdf5(buffer, filename) {
|
|
|
82
87
|
const pbc = lattice_mat ? [true, true, true] : [false, false, false];
|
|
83
88
|
return create_trajectory_frame(frame_pos, frame_elements, lattice_mat, pbc, idx, metadata);
|
|
84
89
|
});
|
|
85
|
-
const first_frame_elements = frames[0]?.structure.sites.map((site) => site.species[0].element) ??
|
|
86
|
-
[];
|
|
90
|
+
const first_frame_elements = frames[0]?.structure.sites.map((site) => site.species[0].element) ?? [];
|
|
87
91
|
return {
|
|
88
92
|
frames,
|
|
89
93
|
metadata: {
|
|
90
94
|
source_format: `hdf5_trajectory`,
|
|
91
95
|
frame_count: frames.length,
|
|
92
96
|
num_atoms: first_frame_elements.length,
|
|
93
|
-
periodic_boundary_conditions: cells_data
|
|
94
|
-
? [true, true, true]
|
|
95
|
-
: [false, false, false],
|
|
97
|
+
periodic_boundary_conditions: cells_data ? [true, true, true] : [false, false, false],
|
|
96
98
|
element_counts: first_frame_elements.reduce((counts, element) => {
|
|
97
99
|
counts[element] = (counts[element] || 0) + 1;
|
|
98
100
|
return counts;
|
|
99
101
|
}, {}),
|
|
100
102
|
discovered_datasets: {
|
|
101
|
-
positions: found_paths.positions ||
|
|
103
|
+
positions: found_paths.positions ||
|
|
104
|
+
found_paths.coords ||
|
|
102
105
|
found_paths.coordinates ||
|
|
103
106
|
`unknown`,
|
|
104
|
-
atomic_numbers: found_paths.atomic_numbers ||
|
|
105
|
-
found_paths.
|
|
107
|
+
atomic_numbers: found_paths.atomic_numbers ||
|
|
108
|
+
found_paths.numbers ||
|
|
109
|
+
found_paths.Z ||
|
|
110
|
+
found_paths.species ||
|
|
111
|
+
`unknown`,
|
|
106
112
|
cells: found_paths.cell || found_paths.cells || found_paths.lattice,
|
|
107
113
|
energies: found_paths.potential_energy || found_paths.energy,
|
|
108
114
|
},
|
|
@@ -116,6 +122,8 @@ export async function parse_torch_sim_hdf5(buffer, filename) {
|
|
|
116
122
|
try {
|
|
117
123
|
FS.unlink(temp_filename);
|
|
118
124
|
}
|
|
119
|
-
catch {
|
|
125
|
+
catch {
|
|
126
|
+
/* temp file cleanup is best-effort */
|
|
127
|
+
}
|
|
120
128
|
}
|
|
121
129
|
}
|
|
@@ -10,9 +10,8 @@ import { parse_torch_sim_hdf5 } from './hdf5';
|
|
|
10
10
|
import { parse_lammps_trajectory } from './lammps';
|
|
11
11
|
import { parse_vasp_xdatcar } from './vasp';
|
|
12
12
|
import { parse_xyz_trajectory } from './xyz';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
13
|
+
// Silently swallow expected parse fallbacks — the caller throws if ALL formats fail
|
|
14
|
+
const log_parse_debug = (_message, _error) => { };
|
|
16
15
|
// Re-export constants and types for consumers
|
|
17
16
|
export { INDEX_SAMPLE_RATE, LARGE_FILE_THRESHOLD, MAX_BIN_FILE_SIZE, MAX_METADATA_SIZE, MAX_SAFE_STRING_LENGTH, MAX_TEXT_FILE_SIZE, } from '../constants';
|
|
18
17
|
export { is_trajectory_file, TrajFrameReader };
|
|
@@ -56,7 +55,7 @@ export async function parse_trajectory_data(data, filename, atom_type_mapping) {
|
|
|
56
55
|
}
|
|
57
56
|
catch (error) {
|
|
58
57
|
log_parse_debug(`JSON parse failed for ${filename ?? `unknown file`}:`, error);
|
|
59
|
-
throw new Error(`Unsupported text format
|
|
58
|
+
throw new Error(`Unsupported text format`, { cause: error });
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
if (!data || typeof data !== `object`)
|
|
@@ -89,7 +88,8 @@ export async function parse_trajectory_data(data, filename, atom_type_mapping) {
|
|
|
89
88
|
const raw_properties = frame_properties[idx] || {};
|
|
90
89
|
const processed_properties = {};
|
|
91
90
|
Object.entries(raw_properties).forEach(([key, value]) => {
|
|
92
|
-
if (value &&
|
|
91
|
+
if (value &&
|
|
92
|
+
typeof value === `object` &&
|
|
93
93
|
value[`@class`] === `array`) {
|
|
94
94
|
// Extract numpy array data
|
|
95
95
|
const array_obj = value;
|
|
@@ -99,9 +99,8 @@ export async function parse_trajectory_data(data, filename, atom_type_mapping) {
|
|
|
99
99
|
const forces = array_obj.data;
|
|
100
100
|
const force_magnitudes = forces.map((force) => Math.hypot(...force));
|
|
101
101
|
if (force_magnitudes.length > 0) {
|
|
102
|
-
processed_properties.force_max = force_magnitudes.reduce((max_val, magnitude) => magnitude > max_val ? magnitude : max_val, force_magnitudes[0]);
|
|
103
|
-
processed_properties.force_norm = Math.sqrt(force_magnitudes.reduce((sum, f) => sum + f ** 2, 0) /
|
|
104
|
-
force_magnitudes.length);
|
|
102
|
+
processed_properties.force_max = force_magnitudes.reduce((max_val, magnitude) => (magnitude > max_val ? magnitude : max_val), force_magnitudes[0]);
|
|
103
|
+
processed_properties.force_norm = Math.sqrt(force_magnitudes.reduce((sum, f) => sum + f ** 2, 0) / force_magnitudes.length);
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
// Calculate stress statistics for stress tensor
|
|
@@ -193,9 +192,7 @@ export async function parse_trajectory_async(data, filename, on_progress, option
|
|
|
193
192
|
const update_progress = (current, stage) => on_progress?.({ current, total: 100, stage });
|
|
194
193
|
try {
|
|
195
194
|
update_progress(0, `Detecting format...`);
|
|
196
|
-
const data_size = data instanceof ArrayBuffer
|
|
197
|
-
? data.byteLength
|
|
198
|
-
: new TextEncoder().encode(data).byteLength;
|
|
195
|
+
const data_size = data instanceof ArrayBuffer ? data.byteLength : new TextEncoder().encode(data).byteLength;
|
|
199
196
|
const is_large_file = data_size > LARGE_FILE_THRESHOLD;
|
|
200
197
|
const should_use_indexing = use_indexing ?? is_large_file;
|
|
201
198
|
if (is_large_file) {
|
|
@@ -260,14 +257,12 @@ async function parse_with_unified_loader(data, filename, options, on_progress) {
|
|
|
260
257
|
}
|
|
261
258
|
const stage = `Ready: ${total_frames} frames indexed`;
|
|
262
259
|
on_progress?.({ current: 100, total: 100, stage });
|
|
260
|
+
const source_format = filename.toLowerCase().endsWith(`.traj`)
|
|
261
|
+
? `ase_trajectory`
|
|
262
|
+
: `xyz_trajectory`;
|
|
263
263
|
return {
|
|
264
264
|
frames,
|
|
265
|
-
metadata: {
|
|
266
|
-
source_format: filename.toLowerCase().endsWith(`.traj`)
|
|
267
|
-
? `ase_trajectory`
|
|
268
|
-
: `xyz_trajectory`,
|
|
269
|
-
frame_count: total_frames,
|
|
270
|
-
},
|
|
265
|
+
metadata: { source_format, frame_count: total_frames },
|
|
271
266
|
total_frames,
|
|
272
267
|
indexed_frames: frame_index,
|
|
273
268
|
plot_metadata,
|
|
@@ -277,7 +272,7 @@ async function parse_with_unified_loader(data, filename, options, on_progress) {
|
|
|
277
272
|
}
|
|
278
273
|
// Factory function for frame loader (simplified)
|
|
279
274
|
export function create_frame_loader(filename) {
|
|
280
|
-
if (
|
|
275
|
+
if (!/\.(xyz|extxyz|traj)$/.exec(filename.toLowerCase())) {
|
|
281
276
|
throw new Error(`Unsupported format for frame loading: ${filename}`);
|
|
282
277
|
}
|
|
283
278
|
return new TrajFrameReader(filename);
|
|
@@ -15,15 +15,22 @@ export function parse_lammps_box(box_lines, is_triclinic) {
|
|
|
15
15
|
if (!is_triclinic) {
|
|
16
16
|
// Orthogonal: bounds = [lo, hi] per dimension
|
|
17
17
|
const [[lo_x, hi_x], [lo_y, hi_y], [lo_z, hi_z]] = bounds;
|
|
18
|
-
return [
|
|
18
|
+
return [
|
|
19
|
+
[hi_x - lo_x, 0, 0],
|
|
20
|
+
[0, hi_y - lo_y, 0],
|
|
21
|
+
[0, 0, hi_z - lo_z],
|
|
22
|
+
];
|
|
19
23
|
}
|
|
20
24
|
// Triclinic: bounds = [lo_bound, hi_bound, tilt] with tilts xy, xz, yz
|
|
21
25
|
const [[xlo_b, xhi_b, xy], [ylo_b, yhi_b, xz], [zlo_b, zhi_b, yz]] = bounds;
|
|
22
|
-
const lx =
|
|
23
|
-
|
|
24
|
-
const ly = (yhi_b - Math.max(0, yz)) - (ylo_b - Math.min(0, yz));
|
|
26
|
+
const lx = xhi_b - Math.max(0, xy, xz, xy + xz) - (xlo_b - Math.min(0, xy, xz, xy + xz));
|
|
27
|
+
const ly = yhi_b - Math.max(0, yz) - (ylo_b - Math.min(0, yz));
|
|
25
28
|
const lz = zhi_b - zlo_b;
|
|
26
|
-
return [
|
|
29
|
+
return [
|
|
30
|
+
[lx, 0, 0],
|
|
31
|
+
[xy, ly, 0],
|
|
32
|
+
[xz, yz, lz],
|
|
33
|
+
];
|
|
27
34
|
}
|
|
28
35
|
// Parse LAMMPS trajectory (.lammpstrj). Atom types mapped to elements via atom_type_mapping
|
|
29
36
|
// or by default: 1→H, 2→He, etc. Supports orthogonal and triclinic simulation boxes.
|
|
@@ -140,7 +147,10 @@ export function parse_lammps_trajectory(content, filename, atom_type_mapping) {
|
|
|
140
147
|
}
|
|
141
148
|
if (positions.length === elements.length && positions.length === num_atoms) {
|
|
142
149
|
const { volume } = math.calc_lattice_params(lattice_matrix);
|
|
143
|
-
frames.push(create_trajectory_frame(positions, elements, lattice_matrix, pbc, timestep, {
|
|
150
|
+
frames.push(create_trajectory_frame(positions, elements, lattice_matrix, pbc, timestep, {
|
|
151
|
+
volume,
|
|
152
|
+
timestep,
|
|
153
|
+
}));
|
|
144
154
|
}
|
|
145
155
|
}
|
|
146
156
|
if (frames.length === 0) {
|
|
@@ -159,7 +169,7 @@ export function parse_lammps_trajectory(content, filename, atom_type_mapping) {
|
|
|
159
169
|
source_format: `lammps_trajectory`,
|
|
160
170
|
frame_count: frames.length,
|
|
161
171
|
total_atoms: first_frame.structure.sites.length,
|
|
162
|
-
periodic_boundary_conditions:
|
|
172
|
+
periodic_boundary_conditions: `lattice` in first_frame.structure
|
|
163
173
|
? first_frame.structure.lattice.pbc
|
|
164
174
|
: [true, true, true],
|
|
165
175
|
atom_types: Array.from(atom_types_found).sort((a, b) => a - b),
|
|
@@ -7,7 +7,10 @@ export function parse_vasp_xdatcar(content, filename) {
|
|
|
7
7
|
const scale = parseFloat(lines[1]);
|
|
8
8
|
if (isNaN(scale))
|
|
9
9
|
throw new Error(`Invalid scale factor`);
|
|
10
|
-
const lattice_matrix = validate_3x3_matrix(lines.slice(2, 5).map((line) => line
|
|
10
|
+
const lattice_matrix = validate_3x3_matrix(lines.slice(2, 5).map((line) => line
|
|
11
|
+
.trim()
|
|
12
|
+
.split(/\s+/)
|
|
13
|
+
.map((x) => parseFloat(x) * scale)));
|
|
11
14
|
const element_names = lines[5].trim().split(/\s+/);
|
|
12
15
|
const element_counts = lines[6].trim().split(/\s+/).map(Number);
|
|
13
16
|
if (element_names.length !== element_counts.length) {
|
|
@@ -32,7 +35,7 @@ export function parse_vasp_xdatcar(content, filename) {
|
|
|
32
35
|
break;
|
|
33
36
|
const config_line = lines[config_idx];
|
|
34
37
|
line_idx = config_idx + 1;
|
|
35
|
-
const step_match =
|
|
38
|
+
const step_match = /configuration=\s*(\d+)/.exec(config_line);
|
|
36
39
|
const step = step_match ? parseInt(step_match[1]) : frames.length + 1;
|
|
37
40
|
const positions = [];
|
|
38
41
|
for (let idx = 0; idx < elements.length && line_idx < lines.length; idx++) {
|
|
@@ -36,16 +36,16 @@ export function parse_xyz_trajectory(content) {
|
|
|
36
36
|
metadata[key] = parseFloat(match[1]);
|
|
37
37
|
});
|
|
38
38
|
// Extract lattice matrix
|
|
39
|
-
const lattice_match =
|
|
39
|
+
const lattice_match = /Lattice\s*=\s*"([^"]+)"/i.exec(comment);
|
|
40
40
|
let lattice_matrix;
|
|
41
41
|
if (lattice_match) {
|
|
42
42
|
const values = lattice_match[1].split(/\s+/).map(Number);
|
|
43
43
|
if (values.length === 9 && values.every((value) => Number.isFinite(value))) {
|
|
44
|
-
lattice_matrix = [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
lattice_matrix = [
|
|
45
|
+
[values[0], values[1], values[2]],
|
|
46
|
+
[values[3], values[4], values[5]],
|
|
47
|
+
[values[6], values[7], values[8]],
|
|
48
|
+
];
|
|
49
49
|
metadata.volume = math.calc_lattice_params(lattice_matrix).volume;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -63,7 +63,8 @@ export function parse_xyz_trajectory(content) {
|
|
|
63
63
|
const x_coord = parseFloat(parts[1]);
|
|
64
64
|
const y_coord = parseFloat(parts[2]);
|
|
65
65
|
const z_coord = parseFloat(parts[3]);
|
|
66
|
-
if (!Number.isFinite(x_coord) ||
|
|
66
|
+
if (!Number.isFinite(x_coord) ||
|
|
67
|
+
!Number.isFinite(y_coord) ||
|
|
67
68
|
!Number.isFinite(z_coord)) {
|
|
68
69
|
console.warn(`Skipping XYZ atom with invalid coordinates in frame ${frames.length} at line ${line_idx + 1}`);
|
|
69
70
|
continue;
|
|
@@ -107,7 +107,8 @@ function group_and_assign_series(series, default_visible_properties) {
|
|
|
107
107
|
unit_map.set(unit, group);
|
|
108
108
|
}
|
|
109
109
|
// Create unit groups with priority and visibility
|
|
110
|
-
const groups = Array.from(unit_map.entries())
|
|
110
|
+
const groups = Array.from(unit_map.entries())
|
|
111
|
+
.map(([unit, group_series]) => {
|
|
111
112
|
const priority = calculate_priority(unit, group_series);
|
|
112
113
|
const has_default_visible = group_series.some((srs) => {
|
|
113
114
|
const metadata = Array.isArray(srs.metadata) ? srs.metadata[0] : srs.metadata;
|
|
@@ -115,7 +116,8 @@ function group_and_assign_series(series, default_visible_properties) {
|
|
|
115
116
|
return is_default_visible(property_key, default_visible_properties);
|
|
116
117
|
});
|
|
117
118
|
return { unit, series: group_series, priority, is_visible: has_default_visible };
|
|
118
|
-
})
|
|
119
|
+
})
|
|
120
|
+
.sort((a, b) => a.priority - b.priority);
|
|
119
121
|
// Apply 2-group visibility limit
|
|
120
122
|
const visible_groups = groups.filter((g) => g.is_visible);
|
|
121
123
|
if (visible_groups.length > 2) {
|
|
@@ -183,7 +185,11 @@ function calculate_priority(unit, group_series) {
|
|
|
183
185
|
}
|
|
184
186
|
// Normalize property keys for robust matching (handles case, underscores, and common aliases)
|
|
185
187
|
const normalize_property_key = (key) => {
|
|
186
|
-
const normalized = key
|
|
188
|
+
const normalized = key
|
|
189
|
+
.toLowerCase()
|
|
190
|
+
.replace(/<[^>]*>/g, ``)
|
|
191
|
+
.replace(/_/g, ` `)
|
|
192
|
+
.trim();
|
|
187
193
|
// Map common force property aliases to canonical form
|
|
188
194
|
return [`fmax`, `f`, `force maximum`].includes(normalized) ? `force max` : normalized;
|
|
189
195
|
};
|
|
@@ -213,7 +219,8 @@ export function toggle_series_visibility(series, target_series_idx) {
|
|
|
213
219
|
const visible_groups = unit_groups.filter((group) => group.is_visible);
|
|
214
220
|
if (visible_groups.length >= 2) {
|
|
215
221
|
// Hide lowest priority group (highest priority number)
|
|
216
|
-
const lowest_priority_group = visible_groups
|
|
222
|
+
const lowest_priority_group = visible_groups
|
|
223
|
+
.sort((g1, g2) => g1.priority - g2.priority)
|
|
217
224
|
.pop(); // Get the last (lowest priority) group
|
|
218
225
|
if (lowest_priority_group) {
|
|
219
226
|
lowest_priority_group.is_visible = false;
|
|
@@ -358,8 +365,7 @@ export function generate_streaming_plot_series(metadata_list, options = {}) {
|
|
|
358
365
|
if (!is_energy && !has_significant_variation(data_points.map((p) => p.y)))
|
|
359
366
|
continue;
|
|
360
367
|
const { clean_label, unit } = extract_label_and_unit(property_key, property_config);
|
|
361
|
-
const is_visible = is_default_visible(property_key, default_visible_properties) ||
|
|
362
|
-
color_idx < 2;
|
|
368
|
+
const is_visible = is_default_visible(property_key, default_visible_properties) || color_idx < 2;
|
|
363
369
|
const color = colors[color_idx % colors.length];
|
|
364
370
|
if (is_visible)
|
|
365
371
|
visible_props.push({ property: property_key, unit });
|
|
@@ -411,8 +417,7 @@ function determine_axis_from_groups(property, unit, visible_properties) {
|
|
|
411
417
|
}));
|
|
412
418
|
const groups = group_and_assign_series(mock_series, new Set([property]));
|
|
413
419
|
const target_group = groups.find((group) => group.series.some((srs) => srs.label === property && srs.unit === unit));
|
|
414
|
-
return target_group &&
|
|
415
|
-
groups.filter((group) => group.is_visible).indexOf(target_group) === 1
|
|
420
|
+
return target_group && groups.filter((group) => group.is_visible).indexOf(target_group) === 1
|
|
416
421
|
? `y2`
|
|
417
422
|
: `y1`;
|
|
418
423
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export declare function merge_nested<T extends Record<string, unknown>>(obj1: T,
|
|
|
2
2
|
export declare const escape_html: (unsafe_string: string) => string;
|
|
3
3
|
export declare const normalize_unicode_minus: (value: string) => string;
|
|
4
4
|
export declare const normalize_scientific_notation: (value: string) => string;
|
|
5
|
+
export declare function decode_url_safe_base64(encoded: string): string | undefined;
|
package/dist/utils.js
CHANGED
|
@@ -21,3 +21,16 @@ export const escape_html = (unsafe_string) => unsafe_string
|
|
|
21
21
|
export const normalize_unicode_minus = (value) => value.replace(/−/g, `-`);
|
|
22
22
|
// Normalize scientific notation variants (d/D exponent, Mathematica *^).
|
|
23
23
|
export const normalize_scientific_notation = (value) => normalize_unicode_minus(value).toLowerCase().replace(/d/g, `e`).replace(/\*\^/g, `e`);
|
|
24
|
+
// Decode a URL-safe base64 string (RFC 4648 §5) to its original text.
|
|
25
|
+
// Converts `-` → `+`, `_` → `/`, restores padding, then decodes.
|
|
26
|
+
// Returns undefined if decoding fails.
|
|
27
|
+
export function decode_url_safe_base64(encoded) {
|
|
28
|
+
const std_b64 = encoded.replace(/-/g, `+`).replace(/_/g, `/`);
|
|
29
|
+
const padded = std_b64 + `=`.repeat((4 - (std_b64.length % 4)) % 4);
|
|
30
|
+
try {
|
|
31
|
+
return atob(padded);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|