matterviz 0.4.1 → 0.4.2
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 -4
- package/dist/FilePicker.svelte +12 -14
- package/dist/Icon.svelte +7 -4
- package/dist/MillerIndexInput.svelte +2 -2
- package/dist/app.css +53 -0
- package/dist/brillouin/BrillouinZone.svelte +75 -85
- package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
- package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
- package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
- package/dist/brillouin/BrillouinZoneScene.svelte +26 -39
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +33 -38
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +62 -74
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +511 -547
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/async-compute.svelte.js +1 -1
- package/dist/chempot-diagram/controls-state.svelte.js +1 -1
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +19 -20
- package/dist/composition/BarChart.svelte +37 -31
- package/dist/composition/BubbleChart.svelte +17 -17
- package/dist/composition/Composition.svelte +7 -11
- package/dist/composition/Formula.svelte +18 -25
- package/dist/composition/FormulaFilter.svelte +92 -95
- package/dist/composition/PieChart.svelte +55 -41
- package/dist/composition/format.js +1 -1
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.js +14 -0
- package/dist/composition/parse.d.ts +2 -2
- package/dist/composition/parse.js +11 -10
- package/dist/constants.js +11 -9
- package/dist/convex-hull/ConvexHull.svelte +12 -15
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +93 -92
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +109 -140
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +75 -94
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
- package/dist/convex-hull/ConvexHullControls.svelte +46 -48
- package/dist/convex-hull/ConvexHullStats.svelte +193 -195
- package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
- package/dist/convex-hull/GasPressureControls.svelte +5 -12
- package/dist/convex-hull/StructurePopup.svelte +17 -21
- package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
- package/dist/convex-hull/TemperatureSlider.svelte +4 -10
- package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
- package/dist/convex-hull/helpers.d.ts +8 -10
- package/dist/convex-hull/helpers.js +36 -36
- package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
- package/dist/convex-hull/thermodynamics.js +18 -17
- package/dist/coordination/CoordinationBarPlot.svelte +38 -33
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
- package/dist/coordination/calc-coordination.d.ts +1 -1
- package/dist/coordination/calc-coordination.js +28 -25
- package/dist/element/BohrAtom.svelte +5 -8
- package/dist/element/ElementHeading.svelte +4 -1
- package/dist/element/ElementPhoto.svelte +11 -9
- package/dist/element/ElementStats.svelte +12 -10
- package/dist/element/ElementTile.svelte +24 -29
- package/dist/element/Nucleus.svelte +4 -2
- package/dist/element/data.d.ts +1 -2
- package/dist/element/helpers.d.ts +4 -0
- package/dist/element/helpers.js +18 -0
- package/dist/element/index.d.ts +1 -4
- package/dist/element/index.js +3 -18
- package/dist/feedback/DragOverlay.svelte +5 -5
- package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
- package/dist/feedback/StatusMessage.svelte +7 -4
- package/dist/fermi-surface/FermiSlice.svelte +21 -18
- package/dist/fermi-surface/FermiSurface.svelte +14 -22
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -7
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +37 -64
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +3 -3
- package/dist/fermi-surface/parse.js +22 -24
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +109 -148
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/heatmap-matrix/index.d.ts +5 -0
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/io/ExportPane.svelte +10 -6
- package/dist/io/decompress.d.ts +2 -1
- package/dist/io/decompress.js +22 -35
- package/dist/io/export.d.ts +4 -0
- package/dist/io/export.js +5 -1
- package/dist/io/fetch.d.ts +0 -3
- package/dist/io/fetch.js +0 -11
- package/dist/io/index.d.ts +0 -1
- package/dist/io/index.js +2 -9
- package/dist/io/is-binary.d.ts +9 -0
- package/dist/io/is-binary.js +45 -1
- package/dist/io/url-drop.js +13 -25
- package/dist/isosurface/Isosurface.svelte +17 -23
- package/dist/isosurface/IsosurfaceControls.svelte +37 -42
- package/dist/isosurface/parse-vaspwave.d.ts +3 -0
- package/dist/isosurface/parse-vaspwave.js +138 -0
- package/dist/isosurface/parse.js +6 -6
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +43 -13
- package/dist/layout/FullscreenButton.svelte +2 -1
- package/dist/layout/FullscreenToggle.svelte +10 -4
- package/dist/layout/InfoCard.svelte +7 -18
- package/dist/layout/InfoTag.svelte +1 -3
- package/dist/layout/NumberRangeInput.svelte +46 -0
- package/dist/layout/NumberRangeInput.svelte.d.ts +12 -0
- package/dist/layout/PropertyFilter.svelte +2 -6
- package/dist/layout/SettingsSection.svelte +21 -17
- package/dist/layout/SubpageGrid.svelte +3 -1
- package/dist/layout/ViewerChrome.svelte +9 -10
- package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
- package/dist/layout/fullscreen.js +3 -5
- package/dist/layout/fullscreen.svelte.js +1 -2
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +8 -11
- package/dist/layout/json-tree/JsonTree.svelte +53 -77
- package/dist/layout/json-tree/JsonValue.svelte +7 -15
- package/dist/layout/json-tree/utils.js +14 -23
- package/dist/math.d.ts +2 -0
- package/dist/math.js +19 -0
- package/dist/overlays/ContextMenu.svelte +4 -2
- package/dist/overlays/CopyButton.svelte +11 -5
- package/dist/overlays/DragControlTab.svelte +6 -1
- package/dist/overlays/DraggablePane.svelte +81 -26
- package/dist/overlays/DraggablePane.svelte.d.ts +1 -0
- package/dist/overlays/GlassChip.svelte +30 -0
- package/dist/overlays/GlassChip.svelte.d.ts +8 -0
- package/dist/overlays/InfoPaneCards.svelte +11 -23
- package/dist/overlays/index.d.ts +7 -0
- package/dist/overlays/index.js +26 -0
- package/dist/overlays/portal.d.ts +2 -0
- package/dist/overlays/portal.js +14 -0
- package/dist/periodic-table/PeriodicTable.svelte +113 -145
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
- package/dist/periodic-table/PeriodicTableControls.svelte +11 -63
- package/dist/periodic-table/TableInset.svelte +9 -4
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +50 -71
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
- package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
- package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
- package/dist/phase-diagram/build-diagram.js +3 -3
- package/dist/phase-diagram/diagram-input.d.ts +1 -2
- package/dist/phase-diagram/parse.js +15 -15
- package/dist/phase-diagram/svg-to-diagram.js +21 -15
- package/dist/phase-diagram/utils.js +2 -3
- package/dist/plot/bar/BarPlot.svelte +430 -391
- package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
- package/dist/plot/bar/BarPlotControls.svelte +1 -3
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
- package/dist/plot/bar/SpacegroupBarPlot.svelte +29 -36
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +301 -209
- package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
- package/dist/plot/box/Violin.svelte.d.ts +1 -0
- package/dist/plot/box/box-plot.js +1 -1
- package/dist/plot/core/auto-place.js +1 -1
- package/dist/plot/core/axis-utils.d.ts +12 -7
- package/dist/plot/core/axis-utils.js +48 -48
- package/dist/plot/core/components/AxisLabel.svelte +82 -20
- package/dist/plot/core/components/AxisLabel.svelte.d.ts +1 -0
- package/dist/plot/core/components/ColorBar.svelte +101 -106
- package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
- package/dist/plot/core/components/FillArea.svelte +10 -17
- package/dist/plot/core/components/HierarchyControls.svelte +249 -0
- package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
- package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
- package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/core/components/Line.svelte +18 -9
- package/dist/plot/core/components/Line.svelte.d.ts +2 -0
- package/dist/plot/core/components/PlotAxis.svelte +40 -17
- package/dist/plot/core/components/PlotControls.svelte +65 -41
- package/dist/plot/core/components/PlotLegend.svelte +31 -35
- package/dist/plot/core/components/PlotMarginals.svelte +616 -0
- package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
- package/dist/plot/core/components/PlotTooltip.svelte +1 -1
- package/dist/plot/core/components/PortalSelect.svelte +4 -8
- package/dist/plot/core/components/ReferenceLine.svelte +27 -24
- package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
- package/dist/plot/core/components/ReferencePlane.svelte +7 -8
- package/dist/plot/core/components/ZeroLines.svelte +4 -8
- package/dist/plot/core/components/ZoomRect.svelte +1 -2
- package/dist/plot/core/components/index.d.ts +1 -0
- package/dist/plot/core/components/index.js +1 -0
- package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
- package/dist/plot/core/data-cleaning-signal.js +467 -0
- package/dist/plot/core/data-cleaning.d.ts +2 -42
- package/dist/plot/core/data-cleaning.js +27 -474
- package/dist/plot/core/data-transform.js +9 -5
- package/dist/plot/core/fill-utils.d.ts +3 -1
- package/dist/plot/core/fill-utils.js +28 -6
- package/dist/plot/core/index.d.ts +1 -0
- package/dist/plot/core/index.js +1 -0
- package/dist/plot/core/interactions.js +0 -2
- package/dist/plot/core/layout.d.ts +3 -1
- package/dist/plot/core/layout.js +22 -13
- package/dist/plot/core/marginals.d.ts +170 -0
- package/dist/plot/core/marginals.js +436 -0
- package/dist/plot/core/reference-line.js +1 -1
- package/dist/plot/core/scales.d.ts +23 -0
- package/dist/plot/core/scales.js +39 -13
- package/dist/plot/core/types/fills.d.ts +101 -0
- package/dist/plot/core/types/fills.js +15 -0
- package/dist/plot/core/types/plot-3d.d.ts +87 -0
- package/dist/plot/core/types/plot-3d.js +4 -0
- package/dist/plot/core/types/reference-lines.d.ts +140 -0
- package/dist/plot/core/types/reference-lines.js +9 -0
- package/dist/plot/core/types.d.ts +29 -324
- package/dist/plot/core/types.js +23 -20
- package/dist/plot/core/utils/hierarchy-chart.d.ts +51 -0
- package/dist/plot/core/utils/hierarchy-chart.js +179 -0
- package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
- package/dist/plot/core/utils/hierarchy-labels.js +99 -0
- package/dist/plot/core/utils/label-placement.js +2 -2
- package/dist/plot/histogram/Histogram.svelte +304 -325
- package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
- package/dist/plot/histogram/HistogramControls.svelte +30 -28
- package/dist/plot/histogram/histogram.d.ts +31 -0
- package/dist/plot/histogram/histogram.js +70 -0
- package/dist/plot/histogram/index.d.ts +1 -0
- package/dist/plot/histogram/index.js +1 -0
- package/dist/plot/index.d.ts +1 -0
- package/dist/plot/index.js +1 -0
- package/dist/plot/sankey/Sankey.svelte +78 -69
- package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
- package/dist/plot/sankey/SankeyControls.svelte +22 -16
- package/dist/plot/scatter/BinnedScatterPlot.svelte +263 -109
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +477 -385
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
- package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
- package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/scatter/ScatterPoint.svelte +16 -9
- package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
- package/dist/plot/scatter/binned-scatter-types.js +6 -1
- package/dist/plot/scatter/scatter-data.d.ts +1 -1
- package/dist/plot/scatter/scatter-data.js +8 -7
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
- package/dist/plot/sunburst/Sunburst.svelte +251 -329
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
- package/dist/plot/sunburst/index.d.ts +1 -1
- package/dist/plot/sunburst/index.js +2 -1
- package/dist/plot/sunburst/render.d.ts +2 -2
- package/dist/plot/sunburst/render.js +47 -22
- package/dist/plot/sunburst/sunburst.d.ts +5 -1
- package/dist/plot/sunburst/sunburst.js +10 -3
- package/dist/plot/treemap/Treemap.svelte +907 -0
- package/dist/plot/treemap/Treemap.svelte.d.ts +84 -0
- package/dist/plot/treemap/index.d.ts +3 -0
- package/dist/plot/treemap/index.js +4 -0
- package/dist/plot/treemap/treemap.d.ts +26 -0
- package/dist/plot/treemap/treemap.js +99 -0
- package/dist/rdf/RdfPlot.svelte +22 -26
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/calc-rdf.js +4 -8
- package/dist/sanitize.js +1 -1
- package/dist/scene/bind-renderer.svelte.js +14 -6
- package/dist/settings.d.ts +19 -9
- package/dist/settings.js +77 -50
- package/dist/spectral/Bands.svelte +223 -230
- package/dist/spectral/BandsAndDos.svelte +11 -9
- package/dist/spectral/BrillouinBandsDos.svelte +19 -27
- package/dist/spectral/Dos.svelte +70 -73
- package/dist/spectral/helpers.d.ts +3 -3
- package/dist/spectral/helpers.js +25 -35
- package/dist/state.svelte.js +5 -18
- package/dist/structure/Arrow.svelte +5 -18
- package/dist/structure/ArrowInstances.svelte +160 -0
- package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
- package/dist/structure/AtomLegend.svelte +115 -75
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/Bond.svelte +31 -23
- package/dist/structure/CanvasTooltip.svelte +15 -3
- package/dist/structure/CellSelect.svelte +11 -26
- package/dist/structure/Cylinder.svelte +24 -40
- package/dist/structure/InstancedAtoms.svelte +114 -0
- package/dist/structure/InstancedAtoms.svelte.d.ts +15 -0
- package/dist/structure/Lattice.svelte +85 -149
- package/dist/structure/SiteLabels.svelte +122 -0
- package/dist/structure/SiteLabels.svelte.d.ts +18 -0
- package/dist/structure/Structure.svelte +473 -419
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureCarousel.svelte +630 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +157 -399
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureExportPane.svelte +17 -21
- package/dist/structure/StructureInfoPane.svelte +72 -66
- package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
- package/dist/structure/StructureScene.svelte +467 -476
- package/dist/structure/StructureScene.svelte.d.ts +3 -1
- package/dist/structure/StructureViewport.svelte +386 -0
- package/dist/structure/StructureViewport.svelte.d.ts +63 -0
- package/dist/structure/atom-label-placement.d.ts +16 -12
- package/dist/structure/atom-label-placement.js +70 -37
- package/dist/structure/atom-properties.d.ts +2 -0
- package/dist/structure/atom-properties.js +104 -38
- package/dist/structure/bonding.d.ts +10 -3
- package/dist/structure/bonding.js +84 -28
- package/dist/structure/edit-history.d.ts +6 -0
- package/dist/structure/edit-history.js +27 -0
- package/dist/structure/export.d.ts +1 -0
- package/dist/structure/export.js +39 -31
- package/dist/structure/format-detect.d.ts +1 -0
- package/dist/structure/format-detect.js +22 -0
- package/dist/structure/geometry.d.ts +9 -0
- package/dist/structure/geometry.js +27 -0
- package/dist/structure/index.d.ts +16 -1
- package/dist/structure/index.js +13 -2
- package/dist/structure/measure.d.ts +1 -1
- package/dist/structure/measure.js +6 -12
- package/dist/structure/parse.d.ts +2 -1
- package/dist/structure/parse.js +243 -222
- package/dist/structure/partial-occupancy.js +4 -2
- package/dist/structure/pbc.js +78 -98
- package/dist/structure/polyhedra.d.ts +0 -1
- package/dist/structure/polyhedra.js +3 -18
- package/dist/structure/supercell.d.ts +1 -2
- package/dist/structure/supercell.js +7 -13
- package/dist/symmetry/SymmetryElements.svelte +11 -15
- package/dist/symmetry/SymmetryStats.svelte +56 -68
- package/dist/symmetry/WyckoffTable.svelte +5 -24
- package/dist/symmetry/cell-transform.js +1 -0
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +8 -2
- package/dist/symmetry/spacegroups.js +3 -2
- package/dist/table/HeatmapTable.svelte +1102 -365
- package/dist/table/HeatmapTable.svelte.d.ts +12 -2
- package/dist/table/ToggleMenu.svelte +5 -12
- package/dist/table/index.d.ts +21 -6
- package/dist/table/index.js +34 -21
- package/dist/theme/ThemeControl.svelte +16 -8
- package/dist/theme/index.d.ts +1 -5
- package/dist/theme/index.js +4 -10
- package/dist/theme/themes.mjs +14 -7
- package/dist/tooltip/KCoords.svelte +4 -1
- package/dist/tooltip/TooltipContent.svelte +2 -5
- package/dist/trajectory/Trajectory.svelte +515 -308
- package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
- package/dist/trajectory/TrajectoryError.svelte +11 -8
- package/dist/trajectory/TrajectoryExportPane.svelte +14 -31
- package/dist/trajectory/TrajectoryInfoPane.svelte +85 -97
- package/dist/trajectory/extract.js +56 -34
- package/dist/trajectory/format-detect.js +11 -9
- package/dist/trajectory/frame-reader.d.ts +2 -0
- package/dist/trajectory/frame-reader.js +33 -29
- package/dist/trajectory/helpers.d.ts +2 -0
- package/dist/trajectory/helpers.js +18 -7
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +3 -0
- package/dist/trajectory/parse/h5-utils.d.ts +13 -0
- package/dist/trajectory/parse/h5-utils.js +109 -0
- package/dist/trajectory/parse/hdf5.d.ts +1 -1
- package/dist/trajectory/parse/hdf5.js +116 -106
- package/dist/trajectory/parse/index.d.ts +2 -3
- package/dist/trajectory/parse/index.js +17 -20
- package/dist/trajectory/parse/lammps.js +8 -12
- package/dist/trajectory/parse/vasp.js +10 -7
- package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
- package/dist/trajectory/parse/vaspout-electronic.js +207 -0
- package/dist/trajectory/parse/vaspout-h5.d.ts +5 -0
- package/dist/trajectory/parse/vaspout-h5.js +284 -0
- package/dist/trajectory/parse/xyz.js +11 -7
- package/dist/trajectory/plotting.d.ts +8 -9
- package/dist/trajectory/plotting.js +99 -27
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +7 -0
- package/dist/xrd/XrdPlot.svelte +47 -61
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +2 -4
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +56 -73
- package/package.json +61 -33
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { create_cart_to_frac, create_frac_to_cart } from '../math'
|
|
22
22
|
import { DEFAULTS } from '../settings'
|
|
23
23
|
import { colors } from '../state.svelte'
|
|
24
|
+
import StructureViewport from './StructureViewport.svelte'
|
|
24
25
|
import type {
|
|
25
26
|
AnyStructure,
|
|
26
27
|
BondEditMode,
|
|
@@ -28,13 +29,16 @@
|
|
|
28
29
|
Crystal,
|
|
29
30
|
MeasureMode,
|
|
30
31
|
StructureBond,
|
|
32
|
+
StructureView,
|
|
31
33
|
} from './'
|
|
32
34
|
import {
|
|
35
|
+
DEFAULT_STRUCTURE_VIEWS,
|
|
33
36
|
default_vector_configs,
|
|
34
37
|
get_element_counts,
|
|
35
38
|
get_pbc_image_sites,
|
|
36
39
|
get_structure_vector_keys,
|
|
37
40
|
} from './'
|
|
41
|
+
import { push_edit, step_history } from './edit-history'
|
|
38
42
|
import { wrap_to_unit_cell } from './pbc'
|
|
39
43
|
import {
|
|
40
44
|
is_valid_supercell_input,
|
|
@@ -45,13 +49,12 @@
|
|
|
45
49
|
import * as symmetry from '../symmetry'
|
|
46
50
|
import { transform_cell } from '../symmetry'
|
|
47
51
|
import type { MoyoDataset } from '@spglib/moyo-wasm'
|
|
48
|
-
import { Canvas } from '@threlte/core'
|
|
49
52
|
import type { ComponentProps, Snippet } from 'svelte'
|
|
50
53
|
import { untrack } from 'svelte'
|
|
51
54
|
import { click_outside, tooltip } from 'svelte-multiselect/attachments'
|
|
52
55
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
53
56
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity'
|
|
54
|
-
import type { Camera,
|
|
57
|
+
import type { Camera, Scene } from 'three'
|
|
55
58
|
import type { AtomColorConfig } from './atom-properties'
|
|
56
59
|
import { get_property_colors } from './atom-properties'
|
|
57
60
|
import AtomLegend from './AtomLegend.svelte'
|
|
@@ -59,7 +62,11 @@
|
|
|
59
62
|
import { BOND_ORDER_OPTIONS, merge_bond_edits, remap_bonds_after_deletion } from './bonding'
|
|
60
63
|
import type { StructureHandlerData } from './index'
|
|
61
64
|
import { MAX_SELECTED_SITES } from './measure'
|
|
62
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
ensure_lattice_params,
|
|
67
|
+
normalize_fractional_coords,
|
|
68
|
+
parse_any_structure,
|
|
69
|
+
} from './parse'
|
|
63
70
|
import StructureControls from './StructureControls.svelte'
|
|
64
71
|
import StructureExportPane from './StructureExportPane.svelte'
|
|
65
72
|
import StructureInfoPane from './StructureInfoPane.svelte'
|
|
@@ -107,6 +114,8 @@
|
|
|
107
114
|
lattice_props: lattice_props_in = $bindable(),
|
|
108
115
|
controls_open = $bindable(false),
|
|
109
116
|
info_pane_open = $bindable(false),
|
|
117
|
+
multi_view = $bindable(false),
|
|
118
|
+
views = DEFAULT_STRUCTURE_VIEWS,
|
|
110
119
|
enable_measure_mode = $bindable(true),
|
|
111
120
|
measure_mode = $bindable<MeasureMode>(`distance`),
|
|
112
121
|
bond_edit_mode = $bindable<BondEditMode>(`add`),
|
|
@@ -183,95 +192,101 @@
|
|
|
183
192
|
on_camera_reset,
|
|
184
193
|
on_bonds_change,
|
|
185
194
|
...rest
|
|
186
|
-
}:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
195
|
+
}: {
|
|
196
|
+
structure?: AnyStructure
|
|
197
|
+
bonds?: StructureBond[]
|
|
198
|
+
scene_props?: ComponentProps<typeof StructureScene>
|
|
199
|
+
// Controls visibility configuration.
|
|
200
|
+
// - 'always': controls always visible
|
|
201
|
+
// - 'hover': controls visible on component hover (default)
|
|
202
|
+
// - 'never': controls never visible
|
|
203
|
+
// - object: { mode, hidden, style } for fine-grained control
|
|
204
|
+
//
|
|
205
|
+
// Control names: 'reset-camera', 'fullscreen', 'measure-mode', 'info-pane', 'export-pane', 'controls'
|
|
206
|
+
show_controls?: ShowControlsProp
|
|
207
|
+
fullscreen?: boolean
|
|
208
|
+
// bindable width of the canvas
|
|
209
|
+
width?: number
|
|
210
|
+
// bindable height of the canvas
|
|
211
|
+
height?: number
|
|
212
|
+
// Canvas wrapper element (for export pane)
|
|
213
|
+
wrapper?: HTMLDivElement
|
|
214
|
+
// PNG export DPI setting
|
|
215
|
+
png_dpi?: number
|
|
216
|
+
reset_text?: string
|
|
217
|
+
hovered?: boolean
|
|
218
|
+
dragover?: boolean
|
|
219
|
+
allow_file_drop?: boolean
|
|
220
|
+
enable_info_pane?: boolean
|
|
221
|
+
enable_measure_mode?: boolean
|
|
222
|
+
measure_mode?: MeasureMode
|
|
223
|
+
bond_edit_mode?: BondEditMode
|
|
224
|
+
bond_edit_order?: BondOrder
|
|
225
|
+
info_pane_open?: boolean
|
|
226
|
+
// When true, split the canvas into a 2x2 grid showing the structure from
|
|
227
|
+
// different angles (Ovito-style). Each pane has independent orbit controls.
|
|
228
|
+
multi_view?: boolean
|
|
229
|
+
// The 4 (or more) view definitions used by multi_view. Defaults to an
|
|
230
|
+
// Ovito-like set: one perspective + three orthographic axis views.
|
|
231
|
+
views?: StructureView[]
|
|
232
|
+
fullscreen_toggle?: FullscreenToggleProp
|
|
233
|
+
bottom_left?: Snippet<[{ structure?: AnyStructure }]>
|
|
234
|
+
top_right_controls?: Snippet // Additional controls to render at the end of the control buttons row
|
|
235
|
+
data_url?: string // URL to load structure from (alternative to providing structure directly)
|
|
236
|
+
// Generic callback for when files are dropped - receives raw content and filename
|
|
237
|
+
on_file_drop?: (content: string | ArrayBuffer, filename: string) => void
|
|
238
|
+
// spinner props (passed to Spinner component)
|
|
239
|
+
spinner_props?: ComponentProps<typeof Spinner>
|
|
240
|
+
loading?: boolean
|
|
241
|
+
error_msg?: string
|
|
242
|
+
// Performance mode: 'quality' (default) or 'speed' for large structures
|
|
243
|
+
performance_mode?: `quality` | `speed`
|
|
244
|
+
// allow parent components to control highlighted/selected site indices
|
|
245
|
+
selected_sites?: number[]
|
|
246
|
+
highlighted_sites?: number[]
|
|
247
|
+
hovered_site_idx?: number | null
|
|
248
|
+
// explicit measured sites for distance/angle overlays
|
|
249
|
+
measured_sites?: number[]
|
|
250
|
+
// expose the displayed structure (with image atoms and/or supercell) for external
|
|
251
|
+
// use. Output-only: the scene renders from an internal raw copy for performance,
|
|
252
|
+
// so external writes to this prop are ignored (overwritten on the next pipeline run)
|
|
253
|
+
displayed_structure?: AnyStructure
|
|
254
|
+
// Track which elements are hidden (bindable across frames in trajectories)
|
|
255
|
+
hidden_elements?: Set<ElementSymbol>
|
|
256
|
+
// Track which property values are hidden (e.g. Wyckoff positions, coordination numbers)
|
|
257
|
+
hidden_prop_vals?: Set<number | string>
|
|
258
|
+
// Per-element radius overrides (absolute values in Angstroms)
|
|
259
|
+
element_radius_overrides?: Partial<Record<ElementSymbol, number>>
|
|
260
|
+
// Per-site radius overrides (absolute values in Angstroms)
|
|
261
|
+
// Accepts Map or SvelteMap for flexibility with external callers
|
|
262
|
+
site_radius_overrides?: Map<number, number> | SvelteMap<number, number>
|
|
263
|
+
// Symmetry analysis data (bindable for external access)
|
|
264
|
+
sym_data?: MoyoDataset | null
|
|
265
|
+
// Symmetry analysis settings (bindable for external control)
|
|
266
|
+
symmetry_settings?: Partial<SymmetrySettings>
|
|
267
|
+
// Map element symbols to different elements (e.g. {'H': 'Na', 'He': 'Cl'})
|
|
268
|
+
element_mapping?: Partial<Record<ElementSymbol, ElementSymbol>>
|
|
269
|
+
// Cell type: original, conventional, or primitive (requires symmetry analysis)
|
|
270
|
+
cell_type?: CellType
|
|
271
|
+
// Volumetric data for isosurface rendering (parsed from CHGCAR or .cube files)
|
|
272
|
+
volumetric_data?: VolumetricData[]
|
|
273
|
+
// Isosurface rendering settings
|
|
274
|
+
isosurface_settings?: IsosurfaceSettings
|
|
275
|
+
// Active volume index when multiple volumes are present
|
|
276
|
+
active_volume_idx?: number
|
|
277
|
+
// structure content as string (alternative to providing structure directly or via data_url)
|
|
278
|
+
structure_string?: string
|
|
279
|
+
// Atom coloring configuration
|
|
280
|
+
atom_color_config?: Partial<AtomColorConfig>
|
|
281
|
+
children?: Snippet<[{ structure?: AnyStructure; fullscreen: boolean }]>
|
|
282
|
+
on_file_load?: EventHandler
|
|
283
|
+
on_error?: EventHandler
|
|
284
|
+
on_fullscreen_change?: EventHandler
|
|
285
|
+
on_camera_move?: EventHandler
|
|
286
|
+
on_camera_reset?: EventHandler
|
|
287
|
+
on_bonds_change?: (bonds: StructureBond[] | undefined) => void
|
|
288
|
+
} & Omit<ComponentProps<typeof StructureControls>, `children` | `onclose`> &
|
|
289
|
+
Omit<HTMLAttributes<HTMLDivElement>, `children`> = $props()
|
|
275
290
|
|
|
276
291
|
// Initialize models from incoming props; mutations come from UI controls; we mirror into local dicts (NOTE only doing shallow merge)
|
|
277
292
|
$effect.pre(() => {
|
|
@@ -283,41 +298,54 @@
|
|
|
283
298
|
}
|
|
284
299
|
})
|
|
285
300
|
|
|
286
|
-
// Load structure from URL when data_url is provided
|
|
301
|
+
// Load structure from URL when data_url is provided. A monotonic load_id ignores stale
|
|
302
|
+
// completions so a newer data_url (or an externally-supplied structure, via the cleanup)
|
|
303
|
+
// can't be clobbered by a slow earlier fetch.
|
|
304
|
+
let data_url_load_id = 0
|
|
287
305
|
$effect(() => {
|
|
288
|
-
if (data_url
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
306
|
+
if (!data_url || structure) return
|
|
307
|
+
const load_id = ++data_url_load_id
|
|
308
|
+
const is_current = () => load_id === data_url_load_id
|
|
309
|
+
loading = true
|
|
310
|
+
error_msg = undefined
|
|
311
|
+
|
|
312
|
+
load_from_url(data_url, (content, filename) => {
|
|
313
|
+
if (!is_current()) return // stale response
|
|
314
|
+
if (on_file_drop) on_file_drop(content, filename)
|
|
315
|
+
else {
|
|
316
|
+
// Parse structure internally when no handler provided
|
|
317
|
+
try {
|
|
318
|
+
const text_content =
|
|
319
|
+
content instanceof ArrayBuffer ? new TextDecoder().decode(content) : content
|
|
320
|
+
const parsed = parse_file_content(text_content, filename)
|
|
321
|
+
emit_file_load_event(parsed, filename, content)
|
|
322
|
+
} catch (error) {
|
|
323
|
+
error_msg = `Failed to parse structure: ${to_error(error).message}`
|
|
324
|
+
on_error?.({ error_msg, filename })
|
|
308
325
|
}
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
.catch((error: Error) => {
|
|
329
|
+
if (!is_current()) return
|
|
330
|
+
console.error(`Failed to load structure from URL:`, error)
|
|
331
|
+
error_msg = `Failed to load structure: ${error.message}`
|
|
332
|
+
on_error?.({ error_msg, filename: data_url })
|
|
333
|
+
})
|
|
334
|
+
.finally(() => {
|
|
335
|
+
if (is_current()) loading = false
|
|
309
336
|
})
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
337
|
+
|
|
338
|
+
return () => {
|
|
339
|
+
// invalidate in-flight load on data_url change / structure arrival / unmount
|
|
340
|
+
if (is_current()) {
|
|
341
|
+
data_url_load_id += 1
|
|
342
|
+
loading = false
|
|
343
|
+
}
|
|
317
344
|
}
|
|
318
345
|
})
|
|
319
346
|
|
|
320
|
-
$effect(() => {
|
|
347
|
+
$effect(() => {
|
|
348
|
+
// Parse structure from string when structure_string is provided
|
|
321
349
|
if (!structure_string || data_url) return
|
|
322
350
|
loading = true
|
|
323
351
|
error_msg = undefined
|
|
@@ -331,9 +359,7 @@
|
|
|
331
359
|
throw new Error(`Failed to parse structure from string`)
|
|
332
360
|
}
|
|
333
361
|
} catch (err) {
|
|
334
|
-
error_msg = `Failed to parse structure from string: ${
|
|
335
|
-
to_error(err).message
|
|
336
|
-
}`
|
|
362
|
+
error_msg = `Failed to parse structure from string: ${to_error(err).message}`
|
|
337
363
|
untrack(() => on_error?.({ error_msg, filename: `string` }))
|
|
338
364
|
} finally {
|
|
339
365
|
loading = false
|
|
@@ -389,12 +415,7 @@
|
|
|
389
415
|
|
|
390
416
|
// Compute property-based colors for legend display
|
|
391
417
|
let property_colors = $derived(
|
|
392
|
-
get_property_colors(
|
|
393
|
-
structure,
|
|
394
|
-
atom_color_config,
|
|
395
|
-
scene_props.bonding_strategy,
|
|
396
|
-
sym_data,
|
|
397
|
-
),
|
|
418
|
+
get_property_colors(structure, atom_color_config, scene_props.bonding_strategy, sym_data),
|
|
398
419
|
)
|
|
399
420
|
|
|
400
421
|
let symmetry_run_id = 0
|
|
@@ -426,7 +447,7 @@
|
|
|
426
447
|
} else {
|
|
427
448
|
// Keep previous symmetry data while recomputing so bound consumers
|
|
428
449
|
// (e.g. SymmetryStats inputs) do not unmount and lose focus.
|
|
429
|
-
untrack(() => symmetry_error = undefined)
|
|
450
|
+
untrack(() => (symmetry_error = undefined))
|
|
430
451
|
}
|
|
431
452
|
const run_id = ++symmetry_run_id
|
|
432
453
|
// Destructure symmetry_settings to ensure Svelte tracks changes to symprec and algo
|
|
@@ -436,20 +457,21 @@
|
|
|
436
457
|
// Skip symmetry auto-analysis in unit tests; happy-dom can't fetch WASM assets
|
|
437
458
|
if (typeof process !== `undefined` && process.env?.VITEST) return
|
|
438
459
|
|
|
439
|
-
symmetry
|
|
460
|
+
symmetry
|
|
461
|
+
.ensure_moyo_wasm_ready()
|
|
440
462
|
.then(() =>
|
|
441
463
|
run_id === symmetry_run_id
|
|
442
464
|
? symmetry.analyze_structure_symmetry(current_structure, current_settings)
|
|
443
|
-
: null
|
|
465
|
+
: null,
|
|
444
466
|
)
|
|
445
467
|
.then((data) => {
|
|
446
468
|
if (data && run_id === symmetry_run_id) {
|
|
447
|
-
untrack(() => sym_data = data)
|
|
469
|
+
untrack(() => (sym_data = data))
|
|
448
470
|
}
|
|
449
471
|
})
|
|
450
472
|
.catch((err) => {
|
|
451
473
|
if (run_id === symmetry_run_id) {
|
|
452
|
-
untrack(() => sym_data = null)
|
|
474
|
+
untrack(() => (sym_data = null))
|
|
453
475
|
symmetry_error = `Symmetry analysis failed: ${err?.message || err}`
|
|
454
476
|
console.error(`Symmetry analysis failed:`, err)
|
|
455
477
|
}
|
|
@@ -474,8 +496,7 @@
|
|
|
474
496
|
let last_emitted_bond_signature = $state<string>()
|
|
475
497
|
let bond_edit_snapshot = $state.raw<BondEditSnapshot>()
|
|
476
498
|
let has_bond_edits = $derived(
|
|
477
|
-
added_bonds.length > 0 || removed_bonds.length > 0 ||
|
|
478
|
-
bond_order_overrides.length > 0,
|
|
499
|
+
added_bonds.length > 0 || removed_bonds.length > 0 || bond_order_overrides.length > 0,
|
|
479
500
|
)
|
|
480
501
|
|
|
481
502
|
const clone_bonds = (edit_bonds: StructureBond[]): StructureBond[] =>
|
|
@@ -556,10 +577,10 @@
|
|
|
556
577
|
const current_source_bond_signature = (): string => {
|
|
557
578
|
const raw_signature = bond_signature(current_source_bonds())
|
|
558
579
|
if (raw_signature !== last_emitted_bond_signature) return raw_signature
|
|
559
|
-
return
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
580
|
+
return (
|
|
581
|
+
bond_history_context?.source_bond_signature ??
|
|
582
|
+
(bond_edit_snapshot ? bond_signature(bond_edit_snapshot.bonds) : raw_signature)
|
|
583
|
+
)
|
|
563
584
|
}
|
|
564
585
|
|
|
565
586
|
const current_bond_edit_context = (): BondEditContext => ({
|
|
@@ -646,8 +667,15 @@
|
|
|
646
667
|
|
|
647
668
|
// === Edit-atoms mode state ===
|
|
648
669
|
let dragging_atoms = $state(false)
|
|
649
|
-
|
|
650
|
-
|
|
670
|
+
// History entries snapshot the bindable `bonds` prop alongside the structure:
|
|
671
|
+
// atom deletion remaps `bonds` to the shifted site indices, so restoring the
|
|
672
|
+
// structure alone would leave explicit bonds pointing at wrong sites.
|
|
673
|
+
type EditSnapshot = { structure: AnyStructure; bonds: StructureBond[] | undefined }
|
|
674
|
+
// $state.raw: stacks hold full structure snapshots — deep-proxying them would make
|
|
675
|
+
// every restored structure (and all downstream site reads) go through proxy traps.
|
|
676
|
+
// Mutations happen via reassignment so .length stays reactive in the template.
|
|
677
|
+
let undo_stack = $state.raw<EditSnapshot[]>([])
|
|
678
|
+
let redo_stack = $state.raw<EditSnapshot[]>([])
|
|
651
679
|
const MAX_HISTORY = 20
|
|
652
680
|
// Flag set before internal edits (undo/redo/delete/add/move) to distinguish
|
|
653
681
|
// them from external structure changes (file load, trajectory step, etc.)
|
|
@@ -655,7 +683,6 @@
|
|
|
655
683
|
// Add-atom sub-mode state (bound to StructureScene)
|
|
656
684
|
let add_atom_mode = $state(false)
|
|
657
685
|
let add_element = $state<ElementSymbol>(`C` as ElementSymbol)
|
|
658
|
-
let canvas_cursor = $state(`default`)
|
|
659
686
|
let is_measure_selection_mode = $derived(
|
|
660
687
|
measure_mode === `distance` || measure_mode === `angle`,
|
|
661
688
|
)
|
|
@@ -693,28 +720,40 @@
|
|
|
693
720
|
dragging_atoms = false
|
|
694
721
|
}
|
|
695
722
|
|
|
723
|
+
const snapshot_edit_state = (current: AnyStructure): EditSnapshot => ({
|
|
724
|
+
structure: $state.snapshot(current) as AnyStructure,
|
|
725
|
+
bonds: bonds && ($state.snapshot(bonds) as StructureBond[]),
|
|
726
|
+
})
|
|
727
|
+
|
|
696
728
|
function push_undo() {
|
|
697
729
|
if (!structure) return
|
|
698
|
-
|
|
699
|
-
undo_stack
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
730
|
+
;[undo_stack, redo_stack] = push_edit(
|
|
731
|
+
[undo_stack, redo_stack],
|
|
732
|
+
snapshot_edit_state(structure),
|
|
733
|
+
MAX_HISTORY,
|
|
734
|
+
)
|
|
703
735
|
}
|
|
704
736
|
|
|
705
|
-
// Shared undo/redo:
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (!
|
|
737
|
+
// Shared undo/redo: restore the top of that direction's stack, pushing the
|
|
738
|
+
// current state onto the opposite one ($state.raw stacks update by reassignment)
|
|
739
|
+
function apply_history(direction: `undo` | `redo`) {
|
|
740
|
+
// Check the stack before snapshotting: $state.snapshot deep-clones the structure
|
|
741
|
+
if (!structure || (direction === `undo` ? undo_stack : redo_stack).length === 0) return
|
|
742
|
+
const result = step_history(
|
|
743
|
+
[undo_stack, redo_stack],
|
|
744
|
+
direction,
|
|
745
|
+
snapshot_edit_state(structure),
|
|
746
|
+
)
|
|
747
|
+
if (!result) return
|
|
710
748
|
is_internal_edit = true
|
|
711
|
-
|
|
712
|
-
structure = restored
|
|
749
|
+
;[undo_stack, redo_stack] = result.stacks
|
|
750
|
+
structure = result.restored.structure
|
|
751
|
+
bonds = result.restored.bonds
|
|
713
752
|
clear_selection()
|
|
714
753
|
}
|
|
715
754
|
|
|
716
|
-
const undo = () => apply_history(
|
|
717
|
-
const redo = () => apply_history(
|
|
755
|
+
const undo = () => apply_history(`undo`)
|
|
756
|
+
const redo = () => apply_history(`redo`)
|
|
718
757
|
|
|
719
758
|
// Clear undo/redo stacks when structure changes externally (file load, etc.)
|
|
720
759
|
// Internal edits set is_internal_edit=true before modifying structure.
|
|
@@ -757,7 +796,10 @@
|
|
|
757
796
|
$effect(() => {
|
|
758
797
|
void bond_edit_mode
|
|
759
798
|
untrack(() => {
|
|
760
|
-
if (
|
|
799
|
+
if (
|
|
800
|
+
measure_mode === `edit-bonds` &&
|
|
801
|
+
(selected_sites.length > 0 || measured_sites.length > 0)
|
|
802
|
+
) {
|
|
761
803
|
clear_selection()
|
|
762
804
|
}
|
|
763
805
|
})
|
|
@@ -794,10 +836,13 @@
|
|
|
794
836
|
])
|
|
795
837
|
|
|
796
838
|
// Normalize structure coordinates: wrap fractional coords to [0,1) and recompute Cartesian
|
|
797
|
-
// This ensures atoms are rendered inside the unit cell regardless of data source
|
|
839
|
+
// This ensures atoms are rendered inside the unit cell regardless of data source.
|
|
840
|
+
// ensure_lattice_params covers structures that bypass parse_any_structure (direct
|
|
841
|
+
// props, trajectory JSON frames) with matrix-only pymatgen lattices, which would
|
|
842
|
+
// otherwise NaN the camera auto-fit and render blank.
|
|
798
843
|
let normalized_structure = $derived.by(() => {
|
|
799
844
|
if (!structure || !(`lattice` in structure)) return structure
|
|
800
|
-
return normalize_fractional_coords(structure) as AnyStructure
|
|
845
|
+
return ensure_lattice_params(normalize_fractional_coords(structure)) as AnyStructure
|
|
801
846
|
})
|
|
802
847
|
|
|
803
848
|
let structure_with_bonds = $derived.by(() => {
|
|
@@ -812,7 +857,8 @@
|
|
|
812
857
|
// This must happen BEFORE supercell transformation
|
|
813
858
|
let cell_transformed_structure = $derived.by(() => {
|
|
814
859
|
if (
|
|
815
|
-
!structure_with_bonds ||
|
|
860
|
+
!structure_with_bonds ||
|
|
861
|
+
!(`lattice` in structure_with_bonds) ||
|
|
816
862
|
cell_type === `original`
|
|
817
863
|
) {
|
|
818
864
|
return structure_with_bonds
|
|
@@ -829,8 +875,11 @@
|
|
|
829
875
|
}
|
|
830
876
|
})
|
|
831
877
|
|
|
832
|
-
// Create supercell if needed (uses cell_transformed_structure as base)
|
|
833
|
-
|
|
878
|
+
// Create supercell if needed (uses cell_transformed_structure as base).
|
|
879
|
+
// $state.raw: supercells can hold thousands of sites — deep-proxying them makes
|
|
880
|
+
// every downstream read (bonding, PBC images, instancing) traverse proxy traps,
|
|
881
|
+
// measured ~15x slower for compute_bonds on a 704-site supercell.
|
|
882
|
+
let supercell_structure = $state.raw(structure)
|
|
834
883
|
let supercell_loading = $state(false)
|
|
835
884
|
let has_supercell = $derived(
|
|
836
885
|
Boolean(supercell_scaling) && ![``, `1x1x1`, `1`].includes(supercell_scaling),
|
|
@@ -863,21 +912,34 @@
|
|
|
863
912
|
})
|
|
864
913
|
|
|
865
914
|
let supercell_timeout: ReturnType<typeof setTimeout> | undefined
|
|
915
|
+
// Compute the supercell, falling back to the unscaled structure on error
|
|
916
|
+
const make_supercell_safe = (base: Crystal): Crystal => {
|
|
917
|
+
try {
|
|
918
|
+
return make_supercell(base, supercell_scaling)
|
|
919
|
+
} catch (error) {
|
|
920
|
+
console.error(`Failed to create supercell:`, error)
|
|
921
|
+
show_toast(`Failed to create supercell: ${to_error(error).message}`)
|
|
922
|
+
return base
|
|
923
|
+
}
|
|
924
|
+
}
|
|
866
925
|
$effect(() => {
|
|
867
926
|
const base_structure = cell_transformed_structure
|
|
868
927
|
clearTimeout(supercell_timeout)
|
|
869
|
-
if (
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
928
|
+
if (
|
|
929
|
+
!base_structure ||
|
|
930
|
+
!(`lattice` in base_structure) ||
|
|
931
|
+
!has_supercell ||
|
|
932
|
+
!is_valid_supercell_input(supercell_scaling)
|
|
933
|
+
) {
|
|
873
934
|
supercell_structure = base_structure
|
|
874
935
|
supercell_loading = false
|
|
875
936
|
} else {
|
|
876
937
|
// For large supercells, show loading state and use async generation
|
|
877
938
|
const sites_count = base_structure.sites?.length || 0
|
|
878
|
-
|
|
879
|
-
const scaling_mult =
|
|
880
|
-
(
|
|
939
|
+
// lenient parse just for a size estimate (invalid factors count as 1)
|
|
940
|
+
const scaling_mult = supercell_scaling
|
|
941
|
+
.split(/[x×]/)
|
|
942
|
+
.reduce((product, factor) => product * (Number(factor) || 1), 1)
|
|
881
943
|
const estimated_sites = sites_count * scaling_mult
|
|
882
944
|
|
|
883
945
|
// Show spinner for supercells with >1000 estimated sites or scaling >8
|
|
@@ -887,27 +949,11 @@
|
|
|
887
949
|
supercell_loading = true
|
|
888
950
|
// Use setTimeout to allow UI to update before heavy computation
|
|
889
951
|
supercell_timeout = setTimeout(() => {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
supercell_structure = make_supercell(
|
|
893
|
-
base_structure as Crystal,
|
|
894
|
-
supercell_scaling,
|
|
895
|
-
)
|
|
896
|
-
}
|
|
897
|
-
} catch (error) {
|
|
898
|
-
console.error(`Failed to create supercell:`, error)
|
|
899
|
-
supercell_structure = base_structure
|
|
900
|
-
} finally {
|
|
901
|
-
supercell_loading = false
|
|
902
|
-
}
|
|
952
|
+
supercell_structure = make_supercell_safe(base_structure as Crystal)
|
|
953
|
+
supercell_loading = false
|
|
903
954
|
}, 10)
|
|
904
955
|
} else {
|
|
905
|
-
|
|
906
|
-
supercell_structure = make_supercell(
|
|
907
|
-
base_structure as Crystal,
|
|
908
|
-
supercell_scaling,
|
|
909
|
-
)
|
|
910
|
-
}
|
|
956
|
+
supercell_structure = make_supercell_safe(base_structure as Crystal)
|
|
911
957
|
supercell_loading = false
|
|
912
958
|
}
|
|
913
959
|
}
|
|
@@ -934,9 +980,12 @@
|
|
|
934
980
|
})
|
|
935
981
|
})
|
|
936
982
|
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
983
|
+
// Element-map + PBC image atoms. Skipped during drags (doubled site count drops frames);
|
|
984
|
+
// images return on release.
|
|
985
|
+
// The render path reads the internal $state.raw copy, NOT the bindable prop:
|
|
986
|
+
// if a consumer binds displayed_structure to a deep $state, reading the prop
|
|
987
|
+
// back would re-proxy every site and slow all scene computations ~10x.
|
|
988
|
+
let internal_displayed_structure = $state.raw<AnyStructure | undefined>(undefined)
|
|
940
989
|
$effect(() => {
|
|
941
990
|
let struct = supercell_structure
|
|
942
991
|
if (struct && element_mapping && Object.keys(element_mapping).length > 0) {
|
|
@@ -953,124 +1002,90 @@
|
|
|
953
1002
|
})),
|
|
954
1003
|
}
|
|
955
1004
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1005
|
+
// Local var, NOT a read-back of the states written below: reading our own
|
|
1006
|
+
// fresh-object write inside this effect would self-retrigger it forever.
|
|
1007
|
+
const next =
|
|
1008
|
+
!dragging_atoms && show_image_atoms && struct && `lattice` in struct && struct.lattice
|
|
959
1009
|
? get_pbc_image_sites(struct)
|
|
960
1010
|
: struct
|
|
1011
|
+
internal_displayed_structure = next
|
|
1012
|
+
displayed_structure = next
|
|
961
1013
|
})
|
|
962
1014
|
|
|
963
|
-
//
|
|
964
|
-
|
|
965
|
-
let camera_is_moving = $state(false)
|
|
1015
|
+
// scene + camera of the primary pane, bound out for the export pane. All other camera
|
|
1016
|
+
// handling (move tracking, reset, re-framing) lives in StructureViewport.
|
|
966
1017
|
let scene = $state<Scene | undefined>(undefined)
|
|
967
1018
|
let camera = $state<Camera | undefined>(undefined)
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
let
|
|
1019
|
+
|
|
1020
|
+
// Multi-side view state: index of the pane the pointer is over (gets edit interactions),
|
|
1021
|
+
// a token bumped to reset every pane, and the set of panes whose camera has moved (so
|
|
1022
|
+
// the reset button stays visible until every moved pane is reset). Pane 0 = primary.
|
|
1023
|
+
let active_pane_idx = $state(0)
|
|
1024
|
+
let reset_token = $state(0)
|
|
1025
|
+
// SvelteSet is already reactive; do NOT wrap in $state (double-proxying breaks it)
|
|
1026
|
+
const moved_panes = new SvelteSet<number>()
|
|
1027
|
+
let any_camera_moved = $derived(moved_panes.size > 0)
|
|
1028
|
+
|
|
1029
|
+
// Inputs shared by every StructureViewport (single + all multi-view panes). Camera,
|
|
1030
|
+
// selection bindings, and per-pane chrome differ and stay on each snippet below.
|
|
1031
|
+
let shared_viewport_props = $derived({
|
|
1032
|
+
structure: internal_displayed_structure,
|
|
1033
|
+
base_structure: cell_transformed_structure,
|
|
1034
|
+
scene_props,
|
|
1035
|
+
gizmo: scene_gizmo,
|
|
1036
|
+
lattice_props,
|
|
1037
|
+
volumetric_data: supercell_volume,
|
|
1038
|
+
isosurface_settings,
|
|
1039
|
+
bond_edits_enabled,
|
|
1040
|
+
bond_edit_order,
|
|
1041
|
+
measure_mode,
|
|
1042
|
+
atom_color_config,
|
|
1043
|
+
sym_data,
|
|
1044
|
+
active_sites: active_scene_sites,
|
|
1045
|
+
on_sites_moved: handle_sites_moved,
|
|
1046
|
+
on_operation_start: push_undo,
|
|
1047
|
+
on_bond_edit_start: push_bond_undo,
|
|
1048
|
+
on_add_atom: handle_add_atom,
|
|
1049
|
+
})
|
|
973
1050
|
|
|
974
1051
|
// Mutual exclusion: opening one pane closes others
|
|
975
1052
|
$effect(() => {
|
|
976
1053
|
if (info_pane_open) {
|
|
977
|
-
untrack(() => [controls_open, export_pane_open] = [false, false])
|
|
1054
|
+
untrack(() => ([controls_open, export_pane_open] = [false, false]))
|
|
978
1055
|
}
|
|
979
1056
|
})
|
|
980
1057
|
$effect(() => {
|
|
981
1058
|
if (controls_open) {
|
|
982
|
-
untrack(() => [info_pane_open, export_pane_open] = [false, false])
|
|
1059
|
+
untrack(() => ([info_pane_open, export_pane_open] = [false, false]))
|
|
983
1060
|
}
|
|
984
1061
|
})
|
|
985
1062
|
$effect(() => {
|
|
986
1063
|
if (export_pane_open) {
|
|
987
|
-
untrack(() => [info_pane_open, controls_open] = [false, false])
|
|
1064
|
+
untrack(() => ([info_pane_open, controls_open] = [false, false]))
|
|
988
1065
|
}
|
|
989
1066
|
})
|
|
990
1067
|
|
|
991
|
-
// Reset tracking when structure changes
|
|
1068
|
+
// Reset moved-pane tracking when structure changes
|
|
992
1069
|
$effect(() => {
|
|
993
|
-
|
|
1070
|
+
// untrack: clearing must not add moved_panes as a dependency, else a pane move
|
|
1071
|
+
// (which adds to moved_panes) would immediately re-run this and clear it again.
|
|
1072
|
+
if (structure) untrack(() => moved_panes.clear())
|
|
994
1073
|
})
|
|
995
1074
|
|
|
996
1075
|
// Clear stale camera target and position so StructureScene uses the new
|
|
997
1076
|
// structure's rotation_target (unit cell center) and auto-positions the camera.
|
|
998
1077
|
function clear_camera_state() {
|
|
1078
|
+
// Reset to a fresh [0,0,0] so the primary viewport re-frames the new structure.
|
|
1079
|
+
// Side panes reset their local camera state in StructureViewport's structure effect.
|
|
999
1080
|
scene_props.camera_target = undefined
|
|
1000
1081
|
scene_props.camera_position = [0, 0, 0]
|
|
1001
1082
|
}
|
|
1002
1083
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
const read_camera_position = (): Vec3 | undefined =>
|
|
1010
|
-
camera
|
|
1011
|
-
? [camera.position.x, camera.position.y, camera.position.z]
|
|
1012
|
-
: scene_props.camera_position
|
|
1013
|
-
|
|
1014
|
-
// Emit debounced camera updates while controls are active.
|
|
1015
|
-
$effect(() => {
|
|
1016
|
-
if (!camera_is_moving) return
|
|
1017
|
-
camera_has_moved = true
|
|
1018
|
-
|
|
1019
|
-
const emit_camera_move = () => {
|
|
1020
|
-
const camera_position = read_camera_position()
|
|
1021
|
-
if (camera_position === undefined) return
|
|
1022
|
-
const camera_target = read_orbit_target()
|
|
1023
|
-
scene_props.camera_position = camera_position
|
|
1024
|
-
scene_props.camera_target = camera_target
|
|
1025
|
-
on_camera_move?.({
|
|
1026
|
-
structure,
|
|
1027
|
-
camera_has_moved,
|
|
1028
|
-
camera_position,
|
|
1029
|
-
camera_target,
|
|
1030
|
-
})
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
emit_camera_move()
|
|
1034
|
-
const emit_interval = setInterval(emit_camera_move, 200)
|
|
1035
|
-
return () => clearInterval(emit_interval)
|
|
1036
|
-
})
|
|
1037
|
-
|
|
1038
|
-
function reset_camera() {
|
|
1039
|
-
// Reset camera position to trigger automatic positioning.
|
|
1040
|
-
scene_props.camera_position = [0, 0, 0]
|
|
1041
|
-
scene_props.camera_target = rotation_target_ref
|
|
1042
|
-
camera_has_moved = false
|
|
1043
|
-
|
|
1044
|
-
let camera_position: Vec3 = [0, 0, 0]
|
|
1045
|
-
let camera_target: Vec3 | undefined = rotation_target_ref
|
|
1046
|
-
|
|
1047
|
-
// Reset pan/zoom and ensure controls target returns to structure center.
|
|
1048
|
-
if (orbit_controls && camera) {
|
|
1049
|
-
if (
|
|
1050
|
-
`reset` in orbit_controls &&
|
|
1051
|
-
typeof orbit_controls.reset === `function`
|
|
1052
|
-
) orbit_controls.reset()
|
|
1053
|
-
if (orbit_controls.target && rotation_target_ref) {
|
|
1054
|
-
const [target_x, target_y, target_z] = rotation_target_ref
|
|
1055
|
-
orbit_controls.target.set(target_x, target_y, target_z)
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
// Reset zoom for orthographic camera
|
|
1059
|
-
if (`zoom` in camera && initial_computed_zoom !== undefined) {
|
|
1060
|
-
const ortho_camera = camera as OrthographicCamera
|
|
1061
|
-
ortho_camera.zoom = initial_computed_zoom
|
|
1062
|
-
ortho_camera.updateProjectionMatrix()
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// Call update to apply changes immediately
|
|
1066
|
-
if (typeof orbit_controls.update === `function`) orbit_controls.update()
|
|
1067
|
-
camera_position = read_camera_position() ?? camera_position
|
|
1068
|
-
camera_target = read_orbit_target()
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
scene_props.camera_position = camera_position
|
|
1072
|
-
scene_props.camera_target = camera_target
|
|
1073
|
-
on_camera_reset?.({ structure, camera_has_moved, camera_position, camera_target })
|
|
1084
|
+
// Reset every pane's camera (each StructureViewport resets on a reset_token bump and,
|
|
1085
|
+
// for the primary, emits on_camera_reset).
|
|
1086
|
+
function reset_all_cameras() {
|
|
1087
|
+
reset_token += 1
|
|
1088
|
+
moved_panes.clear()
|
|
1074
1089
|
}
|
|
1075
1090
|
|
|
1076
1091
|
const emit_file_load_event = (
|
|
@@ -1081,19 +1096,14 @@
|
|
|
1081
1096
|
on_file_load?.({
|
|
1082
1097
|
structure: loaded_structure,
|
|
1083
1098
|
filename,
|
|
1084
|
-
file_size: typeof content === `string`
|
|
1085
|
-
? new Blob([content]).size
|
|
1086
|
-
: content.byteLength,
|
|
1099
|
+
file_size: typeof content === `string` ? new Blob([content]).size : content.byteLength,
|
|
1087
1100
|
total_atoms: loaded_structure.sites?.length || 0,
|
|
1088
1101
|
})
|
|
1089
1102
|
|
|
1090
1103
|
// Try to parse content as a volumetric file, setting both structure and volumetric data.
|
|
1091
1104
|
// Delegates format detection entirely to parse_volumetric_file (filename + content sniffing).
|
|
1092
1105
|
// Returns the parsed structure on success, or null if the file isn't a volumetric format.
|
|
1093
|
-
function try_parse_volumetric(
|
|
1094
|
-
text_content: string,
|
|
1095
|
-
filename: string,
|
|
1096
|
-
): AnyStructure | null {
|
|
1106
|
+
function try_parse_volumetric(text_content: string, filename: string): AnyStructure | null {
|
|
1097
1107
|
const vol_result = parse_volumetric_file(text_content, filename)
|
|
1098
1108
|
if (!vol_result) return null
|
|
1099
1109
|
// parse_volumetric_file extracts structure from file header;
|
|
@@ -1128,15 +1138,12 @@
|
|
|
1128
1138
|
on_drop: (content, filename) => {
|
|
1129
1139
|
if (on_file_drop) return on_file_drop(content, filename)
|
|
1130
1140
|
try {
|
|
1131
|
-
const text_content =
|
|
1132
|
-
? new TextDecoder().decode(content)
|
|
1133
|
-
: content
|
|
1141
|
+
const text_content =
|
|
1142
|
+
content instanceof ArrayBuffer ? new TextDecoder().decode(content) : content
|
|
1134
1143
|
const parsed = parse_file_content(text_content, filename)
|
|
1135
1144
|
emit_file_load_event(parsed, filename, content)
|
|
1136
1145
|
} catch (err) {
|
|
1137
|
-
error_msg = `Failed to parse structure: ${
|
|
1138
|
-
to_error(err).message
|
|
1139
|
-
}`
|
|
1146
|
+
error_msg = `Failed to parse structure: ${to_error(err).message}`
|
|
1140
1147
|
on_error?.({ error_msg, filename })
|
|
1141
1148
|
}
|
|
1142
1149
|
},
|
|
@@ -1263,8 +1270,10 @@
|
|
|
1263
1270
|
}
|
|
1264
1271
|
// Duplicate selected atoms at a small offset
|
|
1265
1272
|
if (
|
|
1266
|
-
key === `d` &&
|
|
1267
|
-
|
|
1273
|
+
key === `d` &&
|
|
1274
|
+
(event.ctrlKey || event.metaKey) &&
|
|
1275
|
+
selected_sites.length > 0 &&
|
|
1276
|
+
structure?.sites
|
|
1268
1277
|
) {
|
|
1269
1278
|
is_internal_edit = true
|
|
1270
1279
|
push_undo()
|
|
@@ -1273,11 +1282,7 @@
|
|
|
1273
1282
|
const new_sites = structure.sites
|
|
1274
1283
|
.filter((_, idx) => orig_indices.has(idx))
|
|
1275
1284
|
.map((site) => {
|
|
1276
|
-
const new_xyz: Vec3 = [
|
|
1277
|
-
site.xyz[0] + 0.5,
|
|
1278
|
-
site.xyz[1] + 0.5,
|
|
1279
|
-
site.xyz[2] + 0.5,
|
|
1280
|
-
]
|
|
1285
|
+
const new_xyz: Vec3 = [site.xyz[0] + 0.5, site.xyz[1] + 0.5, site.xyz[2] + 0.5]
|
|
1281
1286
|
return {
|
|
1282
1287
|
...site,
|
|
1283
1288
|
xyz: new_xyz,
|
|
@@ -1293,9 +1298,7 @@
|
|
|
1293
1298
|
// Select the newly duplicated atoms
|
|
1294
1299
|
selected_sites = new_sites.map((_, idx) => base_idx + idx)
|
|
1295
1300
|
measured_sites = [...selected_sites]
|
|
1296
|
-
show_toast(
|
|
1297
|
-
`Duplicated ${new_sites.length} site${new_sites.length > 1 ? `s` : ``}`,
|
|
1298
|
-
)
|
|
1301
|
+
show_toast(`Duplicated ${new_sites.length} site${new_sites.length > 1 ? `s` : ``}`)
|
|
1299
1302
|
return true
|
|
1300
1303
|
}
|
|
1301
1304
|
|
|
@@ -1320,6 +1323,9 @@
|
|
|
1320
1323
|
} else if (event.key === `i` && has_modifier && enable_info_pane) {
|
|
1321
1324
|
info_pane_open = !info_pane_open
|
|
1322
1325
|
return true
|
|
1326
|
+
} else if (event.key === `g` && has_modifier && controls_config.visible(`multi-view`)) {
|
|
1327
|
+
multi_view = !multi_view
|
|
1328
|
+
return true
|
|
1323
1329
|
} else if (event.key === `Escape`) {
|
|
1324
1330
|
// Prioritize closing panes, then exit edit modes, then exit fullscreen
|
|
1325
1331
|
if (info_pane_open) info_pane_open = false
|
|
@@ -1351,7 +1357,7 @@
|
|
|
1351
1357
|
): SvelteSet<number> {
|
|
1352
1358
|
const result = new SvelteSet<number>()
|
|
1353
1359
|
for (const scene_idx of scene_indices) {
|
|
1354
|
-
const displayed_site =
|
|
1360
|
+
const displayed_site = internal_displayed_structure?.sites?.[scene_idx]
|
|
1355
1361
|
if (!displayed_site) continue
|
|
1356
1362
|
if (skip_image_atoms && displayed_site.properties?.orig_site_idx != null) {
|
|
1357
1363
|
continue
|
|
@@ -1389,10 +1395,9 @@
|
|
|
1389
1395
|
const orig_indices = scene_to_structure_indices(scene_indices)
|
|
1390
1396
|
// For crystals, wrap to [0,1) inline so normalize_fractional_coords fast-paths.
|
|
1391
1397
|
// For molecules (no lattice), just apply the Cartesian delta directly.
|
|
1392
|
-
const lattice = `lattice` in structure
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
const cart_to_frac = lattice ? create_cart_to_frac(lattice) : null
|
|
1398
|
+
const lattice = `lattice` in structure ? (structure as Crystal).lattice.matrix : null
|
|
1399
|
+
// get_cart_to_frac guards matrix_inverse_3x3, which throws on singular lattices
|
|
1400
|
+
const cart_to_frac = get_cart_to_frac()
|
|
1396
1401
|
const frac_to_cart = lattice ? create_frac_to_cart(lattice) : null
|
|
1397
1402
|
structure = {
|
|
1398
1403
|
...structure,
|
|
@@ -1434,9 +1439,7 @@
|
|
|
1434
1439
|
change_element_mode = false
|
|
1435
1440
|
change_element_value = ``
|
|
1436
1441
|
show_toast(
|
|
1437
|
-
`Changed ${orig_indices.size} site${
|
|
1438
|
-
orig_indices.size > 1 ? `s` : ``
|
|
1439
|
-
} to ${elem}`,
|
|
1442
|
+
`Changed ${orig_indices.size} site${orig_indices.size > 1 ? `s` : ``} to ${elem}`,
|
|
1440
1443
|
)
|
|
1441
1444
|
}
|
|
1442
1445
|
|
|
@@ -1451,13 +1454,16 @@
|
|
|
1451
1454
|
push_undo()
|
|
1452
1455
|
structure = {
|
|
1453
1456
|
...structure,
|
|
1454
|
-
sites: [
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1457
|
+
sites: [
|
|
1458
|
+
...structure.sites,
|
|
1459
|
+
{
|
|
1460
|
+
species: [{ element: elem, occu: 1, oxidation_state: 0 }],
|
|
1461
|
+
xyz,
|
|
1462
|
+
abc: get_cart_to_frac()?.(xyz) ?? xyz,
|
|
1463
|
+
label: elem,
|
|
1464
|
+
properties: {},
|
|
1465
|
+
},
|
|
1466
|
+
],
|
|
1461
1467
|
}
|
|
1462
1468
|
show_toast(`Added ${elem} at (${xyz.map((coord) => coord.toFixed(2)).join(`, `)})`)
|
|
1463
1469
|
}
|
|
@@ -1469,10 +1475,7 @@
|
|
|
1469
1475
|
const alpha_hex = Math.round(background_opacity * 255)
|
|
1470
1476
|
.toString(16)
|
|
1471
1477
|
.padStart(2, `0`)
|
|
1472
|
-
wrapper.style.setProperty(
|
|
1473
|
-
`--struct-bg-override`,
|
|
1474
|
-
`${background_color}${alpha_hex}`,
|
|
1475
|
-
)
|
|
1478
|
+
wrapper.style.setProperty(`--struct-bg-override`, `${background_color}${alpha_hex}`)
|
|
1476
1479
|
} else if (typeof window !== `undefined` && wrapper) {
|
|
1477
1480
|
// Remove override to use theme system
|
|
1478
1481
|
wrapper.style.removeProperty(`--struct-bg-override`)
|
|
@@ -1498,9 +1501,9 @@
|
|
|
1498
1501
|
class:dragover
|
|
1499
1502
|
class:active={info_pane_open || controls_open || export_pane_open}
|
|
1500
1503
|
class:gizmo-visible={Boolean(scene_gizmo)}
|
|
1504
|
+
class:multi-view={multi_view}
|
|
1501
1505
|
role="application"
|
|
1502
1506
|
tabindex="0"
|
|
1503
|
-
style:--canvas-cursor={canvas_cursor}
|
|
1504
1507
|
aria-label="Structure viewer"
|
|
1505
1508
|
bind:this={wrapper}
|
|
1506
1509
|
bind:clientWidth={width}
|
|
@@ -1513,22 +1516,14 @@
|
|
|
1513
1516
|
focused = false
|
|
1514
1517
|
}
|
|
1515
1518
|
}}
|
|
1516
|
-
ondblclick={(event) => {
|
|
1517
|
-
const target = event.target
|
|
1518
|
-
if (!(target instanceof HTMLElement)) return
|
|
1519
|
-
// Don't reset if double-click was on UI controls/panes/legend
|
|
1520
|
-
if (
|
|
1521
|
-
[`.control-buttons`, `.structure-legend`, `.atom-legend`, `.info-pane`, `.export-pane`, `.controls-pane`].some((selector) => target.closest(selector))
|
|
1522
|
-
|| target.tagName === `BUTTON` || target.tagName === `INPUT` || target.tagName === `SELECT`
|
|
1523
|
-
) return
|
|
1524
|
-
// Reset camera for double-clicks on the 3D scene
|
|
1525
|
-
reset_camera()
|
|
1526
|
-
}}
|
|
1527
1519
|
ondrop={handle_file_drop}
|
|
1528
|
-
{...drag_over_handlers({
|
|
1520
|
+
{...drag_over_handlers({
|
|
1521
|
+
allow: () => allow_file_drop,
|
|
1522
|
+
set_dragover: (over) => (dragover = over),
|
|
1523
|
+
})}
|
|
1529
1524
|
onkeydown={handle_and_prevent(handle_keydown)}
|
|
1530
1525
|
{...rest}
|
|
1531
|
-
class=
|
|
1526
|
+
class={[`structure`, rest.class]}
|
|
1532
1527
|
>
|
|
1533
1528
|
{@render children?.({ structure, fullscreen })}
|
|
1534
1529
|
{#if loading}
|
|
@@ -1541,8 +1536,13 @@
|
|
|
1541
1536
|
<StatusMessage bind:message={error_msg} type="error" dismissible />
|
|
1542
1537
|
{:else if (structure?.sites?.length ?? 0) > 0}
|
|
1543
1538
|
{#snippet reset_camera_btn()}
|
|
1544
|
-
{#if
|
|
1545
|
-
<button
|
|
1539
|
+
{#if any_camera_moved && controls_config.visible(`reset-camera`)}
|
|
1540
|
+
<button
|
|
1541
|
+
class="reset-camera"
|
|
1542
|
+
onclick={reset_all_cameras}
|
|
1543
|
+
title={reset_text}
|
|
1544
|
+
aria-label={reset_text}
|
|
1545
|
+
>
|
|
1546
1546
|
<!-- Target/Focus icon for reset camera -->
|
|
1547
1547
|
<Icon icon="Reset" />
|
|
1548
1548
|
</button>
|
|
@@ -1557,18 +1557,34 @@
|
|
|
1557
1557
|
before={reset_camera_btn}
|
|
1558
1558
|
style="--viewer-buttons-gap: 4pt; --viewer-buttons-btn-padding: 1px 6px; --viewer-buttons-align: stretch"
|
|
1559
1559
|
>
|
|
1560
|
+
{#if controls_config.visible(`multi-view`)}
|
|
1561
|
+
<button
|
|
1562
|
+
class="multi-view-toggle"
|
|
1563
|
+
class:active={multi_view}
|
|
1564
|
+
onclick={() => (multi_view = !multi_view)}
|
|
1565
|
+
title="Toggle multi-side view 2×2 grid (Cmd/Ctrl+G)"
|
|
1566
|
+
aria-label="Toggle multi-side view"
|
|
1567
|
+
aria-pressed={multi_view}
|
|
1568
|
+
{@attach tooltip()}
|
|
1569
|
+
>
|
|
1570
|
+
<Icon icon="Grid2x2" />
|
|
1571
|
+
</button>
|
|
1572
|
+
{/if}
|
|
1573
|
+
|
|
1560
1574
|
{#if enable_measure_mode && controls_config.visible(`measure-mode`)}
|
|
1561
1575
|
<div
|
|
1562
1576
|
class="measure-mode-dropdown"
|
|
1563
|
-
{@attach click_outside({ callback: () => measure_menu_open = false })}
|
|
1577
|
+
{@attach click_outside({ callback: () => (measure_menu_open = false) })}
|
|
1564
1578
|
>
|
|
1565
1579
|
<button
|
|
1566
1580
|
onclick={() => (measure_menu_open = !measure_menu_open)}
|
|
1567
1581
|
title="Measure / Edit"
|
|
1582
|
+
aria-label="Measure / Edit"
|
|
1568
1583
|
class="view-mode-button"
|
|
1569
1584
|
class:active={measure_menu_open}
|
|
1570
1585
|
aria-expanded={measure_menu_open}
|
|
1571
1586
|
style="transform: scale(1.2)"
|
|
1587
|
+
{@attach tooltip()}
|
|
1572
1588
|
>
|
|
1573
1589
|
{#if show_measure_selection_limit}
|
|
1574
1590
|
<span class="selection-limit-text">
|
|
@@ -1576,18 +1592,17 @@
|
|
|
1576
1592
|
</span>
|
|
1577
1593
|
{:else}
|
|
1578
1594
|
<Icon
|
|
1579
|
-
icon={(
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1595
|
+
icon={(
|
|
1596
|
+
{
|
|
1597
|
+
distance: `Ruler`,
|
|
1598
|
+
angle: `Angle`,
|
|
1599
|
+
'edit-bonds': `Link`,
|
|
1600
|
+
'edit-atoms': `Edit`,
|
|
1601
|
+
} as const
|
|
1602
|
+
)[measure_mode]}
|
|
1585
1603
|
/>
|
|
1586
1604
|
{/if}
|
|
1587
|
-
<Icon
|
|
1588
|
-
icon="Arrow{measure_menu_open ? `Up` : `Down`}"
|
|
1589
|
-
style="margin-left: -2px"
|
|
1590
|
-
/>
|
|
1605
|
+
<Icon icon="Arrow{measure_menu_open ? `Up` : `Down`}" style="margin-left: -2px" />
|
|
1591
1606
|
</button>
|
|
1592
1607
|
{#if show_selection_reset}
|
|
1593
1608
|
<button
|
|
@@ -1603,12 +1618,7 @@
|
|
|
1603
1618
|
{/if}
|
|
1604
1619
|
{#if measure_menu_open}
|
|
1605
1620
|
<div class="view-mode-dropdown">
|
|
1606
|
-
{#each [
|
|
1607
|
-
{ mode: `distance`, icon: `Ruler`, label: `Distance`, scale: 1.1 },
|
|
1608
|
-
{ mode: `angle`, icon: `Angle`, label: `Angle`, scale: 1.3 },
|
|
1609
|
-
{ mode: `edit-atoms`, icon: `Edit`, label: `Edit Atoms`, scale: 1.0 },
|
|
1610
|
-
{ mode: `edit-bonds`, icon: `Link`, label: `Edit Bonds`, scale: 1.0 },
|
|
1611
|
-
] as const as { mode, icon, label, scale } (mode)}
|
|
1621
|
+
{#each [{ mode: `distance`, icon: `Ruler`, label: `Distance`, scale: 1.1 }, { mode: `angle`, icon: `Angle`, label: `Angle`, scale: 1.3 }, { mode: `edit-atoms`, icon: `Edit`, label: `Edit Atoms`, scale: 1.0 }, { mode: `edit-bonds`, icon: `Link`, label: `Edit Bonds`, scale: 1.0 }] as const as { mode, icon, label, scale } (mode)}
|
|
1612
1622
|
<button
|
|
1613
1623
|
class="view-mode-option"
|
|
1614
1624
|
class:selected={measure_mode === mode}
|
|
@@ -1674,10 +1684,7 @@
|
|
|
1674
1684
|
</label>
|
|
1675
1685
|
{/if}
|
|
1676
1686
|
<div class="bond-edit-mode-toggle">
|
|
1677
|
-
{#each [
|
|
1678
|
-
{ mode: `add`, label: `Add`, title: `Add: click two atoms` },
|
|
1679
|
-
{ mode: `delete`, label: `Delete`, title: `Delete: click a bond` },
|
|
1680
|
-
] as const as { mode, label, title } (mode)}
|
|
1687
|
+
{#each [{ mode: `add`, label: `Add`, title: `Add: click two atoms` }, { mode: `delete`, label: `Delete`, title: `Delete: click a bond` }] as const as { mode, label, title } (mode)}
|
|
1681
1688
|
<button
|
|
1682
1689
|
type="button"
|
|
1683
1690
|
class:selected={bond_edit_mode === mode}
|
|
@@ -1738,8 +1745,7 @@
|
|
|
1738
1745
|
{/if}
|
|
1739
1746
|
|
|
1740
1747
|
<!-- Change-element input (shown when 'e' pressed with selection) -->
|
|
1741
|
-
{#if measure_mode === `edit-atoms` && change_element_mode &&
|
|
1742
|
-
selected_sites.length > 0}
|
|
1748
|
+
{#if measure_mode === `edit-atoms` && change_element_mode && selected_sites.length > 0}
|
|
1743
1749
|
<div class="add-atom-input">
|
|
1744
1750
|
<label>
|
|
1745
1751
|
<span>New element:</span>
|
|
@@ -1767,8 +1773,7 @@
|
|
|
1767
1773
|
{/if}
|
|
1768
1774
|
{/if}
|
|
1769
1775
|
|
|
1770
|
-
{#if enable_info_pane && normalized_structure &&
|
|
1771
|
-
controls_config.visible(`info-pane`)}
|
|
1776
|
+
{#if enable_info_pane && normalized_structure && controls_config.visible(`info-pane`)}
|
|
1772
1777
|
<StructureInfoPane
|
|
1773
1778
|
structure={normalized_structure}
|
|
1774
1779
|
bind:pane_open={info_pane_open}
|
|
@@ -1827,7 +1832,7 @@
|
|
|
1827
1832
|
bind:element_radius_overrides
|
|
1828
1833
|
bind:site_radius_overrides
|
|
1829
1834
|
selected_sites={atom_legend_selected_sites}
|
|
1830
|
-
structure={
|
|
1835
|
+
structure={internal_displayed_structure}
|
|
1831
1836
|
show_mode_toggle={viewer_active}
|
|
1832
1837
|
{sym_data}
|
|
1833
1838
|
>
|
|
@@ -1845,60 +1850,90 @@
|
|
|
1845
1850
|
{/snippet}
|
|
1846
1851
|
</AtomLegend>
|
|
1847
1852
|
|
|
1853
|
+
<!-- One StructureViewport renders the single view; four render the 2x2 multi-view.
|
|
1854
|
+
The primary pane (index 0) carries the external camera API: scene/camera are bound
|
|
1855
|
+
out for export and camera_position/target persist into scene_props, and it emits
|
|
1856
|
+
on_camera_move/on_camera_reset. All camera handling itself lives in StructureViewport. -->
|
|
1857
|
+
{#snippet primary_viewport(view: StructureView)}
|
|
1858
|
+
<StructureViewport
|
|
1859
|
+
in_grid={multi_view}
|
|
1860
|
+
label={multi_view ? view.label : undefined}
|
|
1861
|
+
active={multi_view && active_pane_idx === 0}
|
|
1862
|
+
interactive={!multi_view || active_pane_idx === 0}
|
|
1863
|
+
onactivate={() => (active_pane_idx = 0)}
|
|
1864
|
+
{reset_token}
|
|
1865
|
+
report_moved={(moved) => (moved ? moved_panes.add(0) : moved_panes.delete(0))}
|
|
1866
|
+
{on_camera_move}
|
|
1867
|
+
{on_camera_reset}
|
|
1868
|
+
{...shared_viewport_props}
|
|
1869
|
+
camera_direction={view.direction}
|
|
1870
|
+
camera_projection={view.projection ?? scene_props.camera_projection}
|
|
1871
|
+
bind:camera_position={scene_props.camera_position}
|
|
1872
|
+
bind:camera_target={scene_props.camera_target}
|
|
1873
|
+
bind:scene
|
|
1874
|
+
bind:camera
|
|
1875
|
+
bind:selected_sites
|
|
1876
|
+
bind:measured_sites
|
|
1877
|
+
bind:hovered_site_idx
|
|
1878
|
+
bind:hidden_elements
|
|
1879
|
+
bind:hidden_prop_vals
|
|
1880
|
+
bind:element_radius_overrides
|
|
1881
|
+
bind:site_radius_overrides
|
|
1882
|
+
bind:added_bonds
|
|
1883
|
+
bind:removed_bonds
|
|
1884
|
+
bind:bond_order_overrides
|
|
1885
|
+
bind:bond_edit_mode
|
|
1886
|
+
bind:add_atom_mode
|
|
1887
|
+
bind:add_element
|
|
1888
|
+
bind:dragging_atoms
|
|
1889
|
+
bind:polyhedra_rendered_elements
|
|
1890
|
+
/>
|
|
1891
|
+
{/snippet}
|
|
1892
|
+
|
|
1893
|
+
{#snippet extra_viewport(view: StructureView, pane_idx: number)}
|
|
1894
|
+
<StructureViewport
|
|
1895
|
+
in_grid
|
|
1896
|
+
label={view.label}
|
|
1897
|
+
active={active_pane_idx === pane_idx}
|
|
1898
|
+
interactive={active_pane_idx === pane_idx}
|
|
1899
|
+
onactivate={() => (active_pane_idx = pane_idx)}
|
|
1900
|
+
{reset_token}
|
|
1901
|
+
report_moved={(moved) =>
|
|
1902
|
+
moved ? moved_panes.add(pane_idx) : moved_panes.delete(pane_idx)}
|
|
1903
|
+
{...shared_viewport_props}
|
|
1904
|
+
camera_direction={view.direction}
|
|
1905
|
+
camera_projection={view.projection ?? scene_props.camera_projection}
|
|
1906
|
+
bind:selected_sites
|
|
1907
|
+
bind:measured_sites
|
|
1908
|
+
bind:hovered_site_idx
|
|
1909
|
+
bind:hidden_elements
|
|
1910
|
+
bind:hidden_prop_vals
|
|
1911
|
+
bind:element_radius_overrides
|
|
1912
|
+
bind:site_radius_overrides
|
|
1913
|
+
bind:added_bonds
|
|
1914
|
+
bind:removed_bonds
|
|
1915
|
+
bind:bond_order_overrides
|
|
1916
|
+
bind:bond_edit_mode
|
|
1917
|
+
bind:add_atom_mode
|
|
1918
|
+
bind:add_element
|
|
1919
|
+
bind:dragging_atoms
|
|
1920
|
+
/>
|
|
1921
|
+
{/snippet}
|
|
1922
|
+
|
|
1848
1923
|
<!-- prevent from rendering in vitest runner since WebGLRenderingContext not available -->
|
|
1849
1924
|
{#if typeof WebGLRenderingContext !== `undefined`}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
gizmo={scene_gizmo}
|
|
1858
|
-
{lattice_props}
|
|
1859
|
-
volumetric_data={supercell_volume}
|
|
1860
|
-
{isosurface_settings}
|
|
1861
|
-
bind:camera_is_moving
|
|
1862
|
-
bind:selected_sites
|
|
1863
|
-
active_sites={active_scene_sites}
|
|
1864
|
-
bind:hovered_idx={hovered_site_idx}
|
|
1865
|
-
bind:measured_sites
|
|
1866
|
-
bind:scene
|
|
1867
|
-
bind:camera
|
|
1868
|
-
bind:orbit_controls
|
|
1869
|
-
bind:rotation_target_ref
|
|
1870
|
-
bind:initial_computed_zoom
|
|
1871
|
-
bind:hidden_elements
|
|
1872
|
-
bind:hidden_prop_vals
|
|
1873
|
-
bind:element_radius_overrides
|
|
1874
|
-
bind:site_radius_overrides
|
|
1875
|
-
bind:added_bonds
|
|
1876
|
-
bind:removed_bonds
|
|
1877
|
-
bind:bond_order_overrides
|
|
1878
|
-
{bond_edits_enabled}
|
|
1879
|
-
bind:bond_edit_mode
|
|
1880
|
-
{bond_edit_order}
|
|
1881
|
-
{measure_mode}
|
|
1882
|
-
{width}
|
|
1883
|
-
{height}
|
|
1884
|
-
{atom_color_config}
|
|
1885
|
-
{sym_data}
|
|
1886
|
-
on_sites_moved={handle_sites_moved}
|
|
1887
|
-
on_operation_start={push_undo}
|
|
1888
|
-
on_bond_edit_start={push_bond_undo}
|
|
1889
|
-
on_add_atom={handle_add_atom}
|
|
1890
|
-
bind:add_atom_mode
|
|
1891
|
-
bind:add_element
|
|
1892
|
-
bind:cursor={canvas_cursor}
|
|
1893
|
-
bind:dragging_atoms
|
|
1894
|
-
bind:polyhedra_rendered_elements
|
|
1895
|
-
/>
|
|
1896
|
-
</Canvas>
|
|
1925
|
+
<div class:multi={multi_view} class="viewport-stage">
|
|
1926
|
+
{@render primary_viewport(multi_view ? (views[0] ?? {}) : {})}
|
|
1927
|
+
{#if multi_view}
|
|
1928
|
+
{#each views.slice(1) as view, idx (idx)}
|
|
1929
|
+
{@render extra_viewport(view, idx + 1)}
|
|
1930
|
+
{/each}
|
|
1931
|
+
{/if}
|
|
1897
1932
|
</div>
|
|
1898
1933
|
{/if}
|
|
1899
1934
|
|
|
1900
1935
|
<div class="bottom-left">
|
|
1901
|
-
{@render bottom_left?.({ structure:
|
|
1936
|
+
{@render bottom_left?.({ structure: internal_displayed_structure })}
|
|
1902
1937
|
</div>
|
|
1903
1938
|
|
|
1904
1939
|
{#if toast_msg}
|
|
@@ -1908,9 +1943,7 @@
|
|
|
1908
1943
|
{#if symmetry_error}
|
|
1909
1944
|
<div class="symmetry-error">
|
|
1910
1945
|
<span>{symmetry_error}</span>
|
|
1911
|
-
<button onclick={() => (symmetry_error = undefined)} aria-label="Dismiss">
|
|
1912
|
-
×
|
|
1913
|
-
</button>
|
|
1946
|
+
<button onclick={() => (symmetry_error = undefined)} aria-label="Dismiss"> × </button>
|
|
1914
1947
|
</div>
|
|
1915
1948
|
{/if}
|
|
1916
1949
|
{:else if structure}
|
|
@@ -1940,7 +1973,9 @@
|
|
|
1940
1973
|
background: var(--struct-bg-fullscreen, var(--struct-bg));
|
|
1941
1974
|
overflow: hidden;
|
|
1942
1975
|
}
|
|
1943
|
-
|
|
1976
|
+
/* Single view: stretch the lone canvas to the full screen in fullscreen mode.
|
|
1977
|
+
In multi-view the grid fills the screen and each canvas fills its 1fr cell. */
|
|
1978
|
+
.structure:fullscreen:not(.multi-view) :global(canvas) {
|
|
1944
1979
|
height: 100vh !important;
|
|
1945
1980
|
width: 100vw !important;
|
|
1946
1981
|
}
|
|
@@ -1948,6 +1983,22 @@
|
|
|
1948
1983
|
background: var(--struct-dragover-bg, var(--dragover-bg));
|
|
1949
1984
|
border: var(--struct-dragover-border, var(--dragover-border));
|
|
1950
1985
|
}
|
|
1986
|
+
.viewport-stage {
|
|
1987
|
+
height: 100%;
|
|
1988
|
+
width: 100%;
|
|
1989
|
+
}
|
|
1990
|
+
/* 2x2 multi-side view grid: four equal subcanvases. grid-auto-rows keeps rows
|
|
1991
|
+
equal-height if a custom `views` array supplies more than four entries. */
|
|
1992
|
+
.viewport-stage.multi {
|
|
1993
|
+
display: grid;
|
|
1994
|
+
grid-template-columns: 1fr 1fr;
|
|
1995
|
+
grid-template-rows: 1fr 1fr;
|
|
1996
|
+
grid-auto-rows: 1fr;
|
|
1997
|
+
gap: var(--struct-viewport-gap, 2px);
|
|
1998
|
+
}
|
|
1999
|
+
.multi-view-toggle.active {
|
|
2000
|
+
color: var(--accent-color, #4a9eff);
|
|
2001
|
+
}
|
|
1951
2002
|
/* Ensure canvas is transparent so the themed --struct-bg shows through */
|
|
1952
2003
|
.structure :global(canvas) {
|
|
1953
2004
|
background: transparent;
|
|
@@ -1978,7 +2029,9 @@
|
|
|
1978
2029
|
right: 0;
|
|
1979
2030
|
background: var(--surface-bg);
|
|
1980
2031
|
border-radius: var(--border-radius, 3pt);
|
|
1981
|
-
box-shadow:
|
|
2032
|
+
box-shadow:
|
|
2033
|
+
0 8px 16px -4px rgba(0, 0, 0, 0.3),
|
|
2034
|
+
0 4px 8px -2px rgba(0, 0, 0, 0.1);
|
|
1982
2035
|
display: flex;
|
|
1983
2036
|
flex-direction: column;
|
|
1984
2037
|
}
|
|
@@ -2017,7 +2070,7 @@
|
|
|
2017
2070
|
.measure-mode-dropdown > button {
|
|
2018
2071
|
background: transparent;
|
|
2019
2072
|
padding: 1px 6px;
|
|
2020
|
-
font-size: clamp(0.
|
|
2073
|
+
font-size: var(--ctrl-btn-icon-size, clamp(0.7rem, 2cqmin, 0.85rem));
|
|
2021
2074
|
}
|
|
2022
2075
|
.selection-limit-text {
|
|
2023
2076
|
font-weight: bold;
|
|
@@ -2079,7 +2132,8 @@
|
|
|
2079
2132
|
opacity: 0;
|
|
2080
2133
|
}
|
|
2081
2134
|
@keyframes toast-fade {
|
|
2082
|
-
0%,
|
|
2135
|
+
0%,
|
|
2136
|
+
70% {
|
|
2083
2137
|
opacity: 1;
|
|
2084
2138
|
}
|
|
2085
2139
|
100% {
|