matterviz 0.4.0 → 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 +132 -219
- package/dist/brillouin/BrillouinZone.svelte.d.ts +9 -20
- package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
- package/dist/brillouin/BrillouinZoneExportPane.svelte +47 -96
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +15 -42
- package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
- package/dist/brillouin/BrillouinZoneScene.svelte +63 -230
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +3 -23
- package/dist/brillouin/ReciprocalVectors.svelte +39 -0
- package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
- package/dist/brillouin/compute.d.ts +2 -0
- package/dist/brillouin/compute.js +80 -77
- package/dist/brillouin/geometry.d.ts +8 -0
- package/dist/brillouin/geometry.js +57 -0
- package/dist/brillouin/index.d.ts +2 -0
- package/dist/brillouin/index.js +2 -0
- package/dist/brillouin/types.d.ts +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +33 -38
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +143 -246
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +652 -976
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
- package/dist/chempot-diagram/async-compute.svelte.js +1 -1
- package/dist/chempot-diagram/color.d.ts +3 -6
- package/dist/chempot-diagram/color.js +5 -5
- package/dist/chempot-diagram/compute.d.ts +3 -3
- package/dist/chempot-diagram/compute.js +3 -1
- package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
- package/dist/chempot-diagram/controls-state.svelte.js +42 -0
- package/dist/chempot-diagram/export.d.ts +47 -0
- package/dist/chempot-diagram/export.js +133 -0
- package/dist/chempot-diagram/index.d.ts +1 -0
- package/dist/chempot-diagram/index.js +1 -0
- package/dist/chempot-diagram/pointer.d.ts +0 -10
- package/dist/chempot-diagram/pointer.js +4 -4
- package/dist/chempot-diagram/types.d.ts +3 -3
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +21 -22
- 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 +96 -98
- package/dist/composition/PieChart.svelte +60 -46
- package/dist/composition/chem-sys.js +3 -2
- package/dist/composition/format.js +4 -3
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.js +14 -0
- package/dist/composition/parse.d.ts +2 -3
- package/dist/composition/parse.js +27 -28
- package/dist/constants.js +11 -9
- package/dist/controls.d.ts +1 -0
- package/dist/controls.js +0 -1
- package/dist/convex-hull/ConvexHull.svelte +20 -25
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
- package/dist/convex-hull/ConvexHull2D.svelte +176 -256
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +269 -804
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +236 -755
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullChrome.svelte +264 -0
- package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
- package/dist/convex-hull/ConvexHullControls.svelte +128 -49
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
- package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
- package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
- package/dist/convex-hull/ConvexHullStats.svelte +210 -351
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +16 -14
- package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
- 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/barycentric-coords.d.ts +2 -4
- package/dist/convex-hull/barycentric-coords.js +6 -33
- package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
- package/dist/convex-hull/canvas-interactions.svelte.js +279 -0
- package/dist/convex-hull/helpers.d.ts +47 -17
- package/dist/convex-hull/helpers.js +186 -101
- package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
- package/dist/convex-hull/hull-state.svelte.js +124 -0
- package/dist/convex-hull/index.d.ts +9 -7
- package/dist/convex-hull/index.js +7 -2
- package/dist/convex-hull/thermodynamics.js +109 -937
- package/dist/convex-hull/types.d.ts +12 -4
- package/dist/convex-hull/types.js +12 -0
- package/dist/coordination/CoordinationBarPlot.svelte +41 -43
- 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 +6 -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/ElementTile.svelte.d.ts +1 -1
- 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 -0
- package/dist/element/index.js +3 -0
- package/dist/feedback/DragOverlay.svelte +5 -3
- package/dist/feedback/DragOverlay.svelte.d.ts +2 -1
- package/dist/feedback/StatusMessage.svelte +20 -7
- package/dist/fermi-surface/FermiSlice.svelte +21 -18
- package/dist/fermi-surface/FermiSurface.svelte +78 -165
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +9 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceScene.svelte +93 -272
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +14 -13
- package/dist/fermi-surface/export.js +4 -15
- package/dist/fermi-surface/index.d.ts +0 -1
- package/dist/fermi-surface/index.js +0 -1
- package/dist/fermi-surface/parse.d.ts +1 -1
- package/dist/fermi-surface/parse.js +84 -97
- package/dist/fermi-surface/types.d.ts +2 -2
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +161 -185
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +6 -5
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +12 -9
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
- package/dist/heatmap-matrix/index.d.ts +8 -2
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/io/ExportPane.svelte +170 -0
- package/dist/io/ExportPane.svelte.d.ts +17 -0
- package/dist/io/decompress.d.ts +2 -1
- package/dist/io/decompress.js +22 -36
- package/dist/io/export.d.ts +9 -1
- package/dist/io/export.js +37 -29
- package/dist/io/fetch.d.ts +2 -4
- package/dist/io/fetch.js +0 -11
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +13 -0
- package/dist/io/index.d.ts +1 -0
- package/dist/io/index.js +3 -0
- package/dist/io/is-binary.d.ts +9 -0
- package/dist/io/is-binary.js +45 -1
- package/dist/io/types.d.ts +13 -0
- 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 +50 -48
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +44 -14
- package/dist/layout/FullscreenButton.svelte +34 -0
- package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
- package/dist/layout/FullscreenToggle.svelte +12 -12
- 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 +115 -0
- package/dist/layout/ViewerChrome.svelte.d.ts +18 -0
- package/dist/layout/fullscreen.d.ts +4 -0
- package/dist/layout/fullscreen.js +3 -5
- package/dist/layout/fullscreen.svelte.d.ts +8 -0
- package/dist/layout/fullscreen.svelte.js +36 -0
- package/dist/layout/index.d.ts +4 -0
- package/dist/layout/index.js +4 -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 +9 -3
- package/dist/math.js +37 -21
- 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 +11 -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 +115 -146
- 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 +73 -96
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +4 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +49 -134
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
- 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/colors.js +1 -1
- package/dist/phase-diagram/diagram-input.d.ts +1 -2
- package/dist/phase-diagram/parse.d.ts +2 -1
- 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 +494 -692
- package/dist/plot/bar/BarPlot.svelte.d.ts +9 -15
- package/dist/plot/bar/BarPlotControls.svelte +1 -3
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +5 -7
- package/dist/plot/bar/SpacegroupBarPlot.svelte +31 -37
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +361 -439
- package/dist/plot/box/BoxPlot.svelte.d.ts +6 -3
- package/dist/plot/box/BoxPlotControls.svelte.d.ts +1 -1
- package/dist/plot/box/Violin.svelte.d.ts +2 -1
- package/dist/plot/box/box-plot.d.ts +3 -2
- package/dist/plot/box/box-plot.js +6 -3
- package/dist/plot/box/kde.d.ts +2 -1
- package/dist/plot/box/kde.js +4 -4
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +5 -2
- 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 +106 -111
- package/dist/plot/core/components/ColorBar.svelte.d.ts +5 -4
- 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 +21 -11
- package/dist/plot/core/components/Line.svelte.d.ts +5 -2
- package/dist/plot/core/components/PlotAxis.svelte +42 -18
- package/dist/plot/core/components/PlotAxis.svelte.d.ts +2 -1
- package/dist/plot/core/components/PlotControls.svelte +65 -41
- package/dist/plot/core/components/PlotControls.svelte.d.ts +1 -1
- 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 +56 -59
- package/dist/plot/core/components/ReferenceLine3D.svelte.d.ts +4 -4
- package/dist/plot/core/components/ReferencePlane.svelte +9 -10
- package/dist/plot/core/components/ReferencePlane.svelte.d.ts +4 -4
- 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 +29 -476
- package/dist/plot/core/data-transform.js +9 -5
- package/dist/plot/core/fill-utils.d.ts +7 -4
- package/dist/plot/core/fill-utils.js +34 -9
- package/dist/plot/core/index.d.ts +1 -0
- package/dist/plot/core/index.js +1 -0
- package/dist/plot/core/interactions.d.ts +5 -1
- package/dist/plot/core/interactions.js +14 -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/pan-zoom.svelte.d.ts +35 -0
- package/dist/plot/core/pan-zoom.svelte.js +221 -0
- package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -0
- package/dist/plot/core/reference-line.d.ts +10 -10
- package/dist/plot/core/reference-line.js +7 -7
- package/dist/plot/core/scales.d.ts +40 -25
- package/dist/plot/core/scales.js +49 -21
- package/dist/plot/core/svg.d.ts +2 -1
- 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 +47 -331
- 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.d.ts +1 -1
- package/dist/plot/core/utils/label-placement.js +5 -5
- package/dist/plot/core/utils.d.ts +2 -1
- package/dist/plot/histogram/Histogram.svelte +373 -631
- package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
- package/dist/plot/histogram/HistogramControls.svelte +30 -28
- package/dist/plot/histogram/HistogramControls.svelte.d.ts +1 -1
- 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 +79 -73
- package/dist/plot/sankey/Sankey.svelte.d.ts +3 -3
- package/dist/plot/sankey/SankeyControls.svelte +22 -16
- package/dist/plot/sankey/sankey.js +3 -1
- package/dist/plot/scatter/BinnedScatterPlot.svelte +264 -112
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +11 -4
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +623 -821
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +9 -15
- 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 +6 -12
- package/dist/plot/scatter/binned-scatter-types.js +6 -1
- package/dist/plot/scatter/index.d.ts +1 -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 +40 -60
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +6 -14
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +17 -15
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte.d.ts +5 -5
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +232 -234
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +5 -14
- package/dist/plot/scatter-3d/Surface3D.svelte +9 -17
- package/dist/plot/scatter-3d/Surface3D.svelte.d.ts +4 -3
- package/dist/plot/sunburst/Sunburst.svelte +256 -338
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +4 -3
- 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 +14 -4
- 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 +14 -3
- package/dist/scene/SceneCamera.svelte +62 -0
- package/dist/scene/SceneCamera.svelte.d.ts +19 -0
- package/dist/scene/bind-renderer.svelte.d.ts +2 -0
- package/dist/scene/bind-renderer.svelte.js +22 -0
- package/dist/scene/index.d.ts +4 -0
- package/dist/scene/index.js +5 -0
- package/dist/scene/props.js +52 -0
- package/dist/scene/types.d.ts +26 -0
- package/dist/scene/types.js +1 -0
- package/dist/settings.d.ts +33 -11
- package/dist/settings.js +136 -51
- package/dist/spectral/Bands.svelte +231 -237
- package/dist/spectral/Bands.svelte.d.ts +3 -2
- package/dist/spectral/BandsAndDos.svelte +29 -29
- package/dist/spectral/BrillouinBandsDos.svelte +21 -29
- package/dist/spectral/Dos.svelte +75 -77
- package/dist/spectral/Dos.svelte.d.ts +2 -1
- package/dist/spectral/helpers.d.ts +9 -9
- package/dist/spectral/helpers.js +66 -70
- package/dist/state.svelte.d.ts +0 -7
- package/dist/state.svelte.js +5 -24
- package/dist/structure/Arrow.svelte +6 -21
- 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 +16 -3
- package/dist/structure/CellSelect.svelte +22 -29
- package/dist/structure/CellSelect.svelte.d.ts +2 -1
- 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 +725 -735
- package/dist/structure/Structure.svelte.d.ts +8 -16
- package/dist/structure/StructureCarousel.svelte +630 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +298 -325
- package/dist/structure/StructureControls.svelte.d.ts +5 -3
- package/dist/structure/StructureExportPane.svelte +62 -168
- package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
- package/dist/structure/StructureInfoPane.svelte +77 -69
- package/dist/structure/StructureInfoPane.svelte.d.ts +6 -6
- package/dist/structure/StructureScene.svelte +758 -600
- package/dist/structure/StructureScene.svelte.d.ts +24 -20
- package/dist/structure/StructureViewport.svelte +386 -0
- package/dist/structure/StructureViewport.svelte.d.ts +63 -0
- package/dist/structure/atom-label-placement.d.ts +18 -0
- package/dist/structure/atom-label-placement.js +115 -0
- package/dist/structure/atom-properties.d.ts +3 -1
- package/dist/structure/atom-properties.js +115 -54
- package/dist/structure/bond-order-perception.js +2 -4
- package/dist/structure/bonding.d.ts +12 -2
- package/dist/structure/bonding.js +172 -73
- package/dist/structure/edit-history.d.ts +6 -0
- package/dist/structure/edit-history.js +27 -0
- package/dist/structure/export.d.ts +25 -4
- package/dist/structure/export.js +103 -153
- 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 +18 -1
- package/dist/structure/index.js +15 -2
- package/dist/structure/measure.d.ts +1 -1
- package/dist/structure/measure.js +6 -12
- package/dist/structure/parse.d.ts +5 -3
- package/dist/structure/parse.js +551 -567
- package/dist/structure/partial-occupancy.d.ts +0 -1
- package/dist/structure/partial-occupancy.js +5 -3
- package/dist/structure/pbc.d.ts +1 -1
- package/dist/structure/pbc.js +204 -51
- package/dist/structure/polyhedra.d.ts +40 -0
- package/dist/structure/polyhedra.js +587 -0
- package/dist/structure/site.d.ts +4 -0
- package/dist/structure/site.js +1 -0
- package/dist/structure/supercell.d.ts +1 -2
- package/dist/structure/supercell.js +10 -15
- package/dist/structure/validation.js +5 -6
- package/dist/symmetry/SymmetryElementControls.svelte +69 -0
- package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
- package/dist/symmetry/SymmetryElements.svelte +350 -0
- package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
- package/dist/symmetry/SymmetryStats.svelte +142 -49
- package/dist/symmetry/WyckoffTable.svelte +58 -16
- package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
- package/dist/symmetry/cell-transform.js +8 -14
- package/dist/symmetry/index.d.ts +15 -4
- package/dist/symmetry/index.js +308 -81
- package/dist/symmetry/spacegroups.d.ts +5 -1
- package/dist/symmetry/spacegroups.js +18 -3
- package/dist/symmetry/symmetry-elements.d.ts +33 -0
- package/dist/symmetry/symmetry-elements.js +521 -0
- package/dist/symmetry/wyckoff-db.d.ts +9 -0
- package/dist/symmetry/wyckoff-db.js +87 -0
- package/dist/table/HeatmapTable.svelte +1104 -378
- 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 +556 -352
- package/dist/trajectory/Trajectory.svelte.d.ts +10 -22
- package/dist/trajectory/TrajectoryError.svelte +11 -8
- package/dist/trajectory/TrajectoryExportPane.svelte +28 -54
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
- package/dist/trajectory/TrajectoryInfoPane.svelte +88 -99
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
- package/dist/trajectory/constants.js +6 -2
- package/dist/trajectory/extract.js +73 -71
- package/dist/trajectory/format-detect.d.ts +0 -1
- package/dist/trajectory/format-detect.js +13 -17
- package/dist/trajectory/frame-reader.d.ts +2 -1
- package/dist/trajectory/frame-reader.js +74 -136
- package/dist/trajectory/helpers.d.ts +12 -2
- package/dist/trajectory/helpers.js +68 -37
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +3 -0
- package/dist/trajectory/parse/ase.d.ts +9 -1
- package/dist/trajectory/parse/ase.js +47 -32
- package/dist/trajectory/parse/diagnostics.d.ts +3 -0
- package/dist/trajectory/parse/diagnostics.js +14 -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 +3 -4
- package/dist/trajectory/parse/index.js +68 -119
- package/dist/trajectory/parse/lammps.d.ts +0 -2
- package/dist/trajectory/parse/lammps.js +14 -16
- package/dist/trajectory/parse/pymatgen.d.ts +2 -0
- package/dist/trajectory/parse/pymatgen.js +74 -0
- package/dist/trajectory/parse/vasp.js +13 -9
- 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.d.ts +9 -21
- package/dist/trajectory/parse/xyz.js +38 -39
- package/dist/trajectory/plotting.d.ts +8 -10
- package/dist/trajectory/plotting.js +102 -127
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +8 -1
- package/dist/xrd/XrdPlot.svelte +56 -85
- package/dist/xrd/broadening.d.ts +2 -1
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +8 -15
- package/dist/xrd/index.d.ts +2 -2
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +56 -73
- package/package.json +77 -36
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/element/data.json +0 -11864
- package/dist/fermi-surface/marching-cubes.d.ts +0 -2
- package/dist/fermi-surface/marching-cubes.js +0 -2
- package/dist/plot/core/hover-lock.svelte.d.ts +0 -14
- package/dist/plot/core/hover-lock.svelte.js +0 -45
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
- package/dist/structure/label-placement.d.ts +0 -14
- package/dist/structure/label-placement.js +0 -72
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ColorSchemeName } from '../colors'
|
|
3
3
|
import { ELEMENT_COLOR_SCHEMES } from '../colors'
|
|
4
|
-
import type
|
|
5
|
-
import {
|
|
6
|
-
import type { ElementSymbol } from '../element'
|
|
4
|
+
import { normalize_show_controls, type ShowControlsProp } from '../controls'
|
|
5
|
+
import { coerce_elem_symbol, type ElementSymbol } from '../element'
|
|
7
6
|
import { StatusMessage } from '../feedback'
|
|
8
7
|
import Spinner from '../feedback/Spinner.svelte'
|
|
9
8
|
import Icon from '../Icon.svelte'
|
|
10
|
-
import { create_file_drop_handler, load_from_url } from '../io'
|
|
9
|
+
import { create_file_drop_handler, drag_over_handlers, load_from_url } from '../io'
|
|
11
10
|
import { forward_window_keydown, handle_and_prevent } from '../keyboard'
|
|
12
11
|
import { parse_volumetric_file } from '../isosurface/parse'
|
|
13
12
|
import type { IsosurfaceSettings, VolumetricData } from '../isosurface/types'
|
|
@@ -16,12 +15,13 @@
|
|
|
16
15
|
DEFAULT_ISOSURFACE_SETTINGS,
|
|
17
16
|
tile_volumetric_data,
|
|
18
17
|
} from '../isosurface/types'
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
18
|
+
import { type FullscreenToggleProp, toggle_fullscreen, ViewerChrome } from '../layout'
|
|
19
|
+
import { sync_fullscreen } from '../layout/fullscreen.svelte'
|
|
21
20
|
import type { Vec3 } from '../math'
|
|
22
21
|
import { create_cart_to_frac, create_frac_to_cart } from '../math'
|
|
23
22
|
import { DEFAULTS } from '../settings'
|
|
24
23
|
import { colors } from '../state.svelte'
|
|
24
|
+
import StructureViewport from './StructureViewport.svelte'
|
|
25
25
|
import type {
|
|
26
26
|
AnyStructure,
|
|
27
27
|
BondEditMode,
|
|
@@ -29,13 +29,16 @@
|
|
|
29
29
|
Crystal,
|
|
30
30
|
MeasureMode,
|
|
31
31
|
StructureBond,
|
|
32
|
+
StructureView,
|
|
32
33
|
} from './'
|
|
33
34
|
import {
|
|
35
|
+
DEFAULT_STRUCTURE_VIEWS,
|
|
34
36
|
default_vector_configs,
|
|
35
37
|
get_element_counts,
|
|
36
38
|
get_pbc_image_sites,
|
|
37
39
|
get_structure_vector_keys,
|
|
38
40
|
} from './'
|
|
41
|
+
import { push_edit, step_history } from './edit-history'
|
|
39
42
|
import { wrap_to_unit_cell } from './pbc'
|
|
40
43
|
import {
|
|
41
44
|
is_valid_supercell_input,
|
|
@@ -46,13 +49,12 @@
|
|
|
46
49
|
import * as symmetry from '../symmetry'
|
|
47
50
|
import { transform_cell } from '../symmetry'
|
|
48
51
|
import type { MoyoDataset } from '@spglib/moyo-wasm'
|
|
49
|
-
import { Canvas } from '@threlte/core'
|
|
50
52
|
import type { ComponentProps, Snippet } from 'svelte'
|
|
51
53
|
import { untrack } from 'svelte'
|
|
52
54
|
import { click_outside, tooltip } from 'svelte-multiselect/attachments'
|
|
53
55
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
54
56
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity'
|
|
55
|
-
import type { Camera,
|
|
57
|
+
import type { Camera, Scene } from 'three'
|
|
56
58
|
import type { AtomColorConfig } from './atom-properties'
|
|
57
59
|
import { get_property_colors } from './atom-properties'
|
|
58
60
|
import AtomLegend from './AtomLegend.svelte'
|
|
@@ -60,7 +62,11 @@
|
|
|
60
62
|
import { BOND_ORDER_OPTIONS, merge_bond_edits, remap_bonds_after_deletion } from './bonding'
|
|
61
63
|
import type { StructureHandlerData } from './index'
|
|
62
64
|
import { MAX_SELECTED_SITES } from './measure'
|
|
63
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
ensure_lattice_params,
|
|
67
|
+
normalize_fractional_coords,
|
|
68
|
+
parse_any_structure,
|
|
69
|
+
} from './parse'
|
|
64
70
|
import StructureControls from './StructureControls.svelte'
|
|
65
71
|
import StructureExportPane from './StructureExportPane.svelte'
|
|
66
72
|
import StructureInfoPane from './StructureInfoPane.svelte'
|
|
@@ -108,6 +114,8 @@
|
|
|
108
114
|
lattice_props: lattice_props_in = $bindable(),
|
|
109
115
|
controls_open = $bindable(false),
|
|
110
116
|
info_pane_open = $bindable(false),
|
|
117
|
+
multi_view = $bindable(false),
|
|
118
|
+
views = DEFAULT_STRUCTURE_VIEWS,
|
|
111
119
|
enable_measure_mode = $bindable(true),
|
|
112
120
|
measure_mode = $bindable<MeasureMode>(`distance`),
|
|
113
121
|
bond_edit_mode = $bindable<BondEditMode>(`add`),
|
|
@@ -184,97 +192,101 @@
|
|
|
184
192
|
on_camera_reset,
|
|
185
193
|
on_bonds_change,
|
|
186
194
|
...rest
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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()
|
|
278
290
|
|
|
279
291
|
// Initialize models from incoming props; mutations come from UI controls; we mirror into local dicts (NOTE only doing shallow merge)
|
|
280
292
|
$effect.pre(() => {
|
|
@@ -286,41 +298,54 @@
|
|
|
286
298
|
}
|
|
287
299
|
})
|
|
288
300
|
|
|
289
|
-
// 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
|
|
290
305
|
$effect(() => {
|
|
291
|
-
if (data_url
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
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 })
|
|
311
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
|
|
312
336
|
})
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
+
}
|
|
320
344
|
}
|
|
321
345
|
})
|
|
322
346
|
|
|
323
|
-
$effect(() => {
|
|
347
|
+
$effect(() => {
|
|
348
|
+
// Parse structure from string when structure_string is provided
|
|
324
349
|
if (!structure_string || data_url) return
|
|
325
350
|
loading = true
|
|
326
351
|
error_msg = undefined
|
|
@@ -334,9 +359,7 @@
|
|
|
334
359
|
throw new Error(`Failed to parse structure from string`)
|
|
335
360
|
}
|
|
336
361
|
} catch (err) {
|
|
337
|
-
error_msg = `Failed to parse structure from string: ${
|
|
338
|
-
to_error(err).message
|
|
339
|
-
}`
|
|
362
|
+
error_msg = `Failed to parse structure from string: ${to_error(err).message}`
|
|
340
363
|
untrack(() => on_error?.({ error_msg, filename: `string` }))
|
|
341
364
|
} finally {
|
|
342
365
|
loading = false
|
|
@@ -392,12 +415,7 @@
|
|
|
392
415
|
|
|
393
416
|
// Compute property-based colors for legend display
|
|
394
417
|
let property_colors = $derived(
|
|
395
|
-
get_property_colors(
|
|
396
|
-
structure,
|
|
397
|
-
atom_color_config,
|
|
398
|
-
scene_props.bonding_strategy,
|
|
399
|
-
sym_data,
|
|
400
|
-
),
|
|
418
|
+
get_property_colors(structure, atom_color_config, scene_props.bonding_strategy, sym_data),
|
|
401
419
|
)
|
|
402
420
|
|
|
403
421
|
let symmetry_run_id = 0
|
|
@@ -429,7 +447,7 @@
|
|
|
429
447
|
} else {
|
|
430
448
|
// Keep previous symmetry data while recomputing so bound consumers
|
|
431
449
|
// (e.g. SymmetryStats inputs) do not unmount and lose focus.
|
|
432
|
-
untrack(() => symmetry_error = undefined)
|
|
450
|
+
untrack(() => (symmetry_error = undefined))
|
|
433
451
|
}
|
|
434
452
|
const run_id = ++symmetry_run_id
|
|
435
453
|
// Destructure symmetry_settings to ensure Svelte tracks changes to symprec and algo
|
|
@@ -439,20 +457,21 @@
|
|
|
439
457
|
// Skip symmetry auto-analysis in unit tests; happy-dom can't fetch WASM assets
|
|
440
458
|
if (typeof process !== `undefined` && process.env?.VITEST) return
|
|
441
459
|
|
|
442
|
-
symmetry
|
|
460
|
+
symmetry
|
|
461
|
+
.ensure_moyo_wasm_ready()
|
|
443
462
|
.then(() =>
|
|
444
463
|
run_id === symmetry_run_id
|
|
445
464
|
? symmetry.analyze_structure_symmetry(current_structure, current_settings)
|
|
446
|
-
: null
|
|
465
|
+
: null,
|
|
447
466
|
)
|
|
448
467
|
.then((data) => {
|
|
449
468
|
if (data && run_id === symmetry_run_id) {
|
|
450
|
-
untrack(() => sym_data = data)
|
|
469
|
+
untrack(() => (sym_data = data))
|
|
451
470
|
}
|
|
452
471
|
})
|
|
453
472
|
.catch((err) => {
|
|
454
473
|
if (run_id === symmetry_run_id) {
|
|
455
|
-
untrack(() => sym_data = null)
|
|
474
|
+
untrack(() => (sym_data = null))
|
|
456
475
|
symmetry_error = `Symmetry analysis failed: ${err?.message || err}`
|
|
457
476
|
console.error(`Symmetry analysis failed:`, err)
|
|
458
477
|
}
|
|
@@ -477,8 +496,7 @@
|
|
|
477
496
|
let last_emitted_bond_signature = $state<string>()
|
|
478
497
|
let bond_edit_snapshot = $state.raw<BondEditSnapshot>()
|
|
479
498
|
let has_bond_edits = $derived(
|
|
480
|
-
added_bonds.length > 0 || removed_bonds.length > 0 ||
|
|
481
|
-
bond_order_overrides.length > 0,
|
|
499
|
+
added_bonds.length > 0 || removed_bonds.length > 0 || bond_order_overrides.length > 0,
|
|
482
500
|
)
|
|
483
501
|
|
|
484
502
|
const clone_bonds = (edit_bonds: StructureBond[]): StructureBond[] =>
|
|
@@ -559,10 +577,10 @@
|
|
|
559
577
|
const current_source_bond_signature = (): string => {
|
|
560
578
|
const raw_signature = bond_signature(current_source_bonds())
|
|
561
579
|
if (raw_signature !== last_emitted_bond_signature) return raw_signature
|
|
562
|
-
return
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
580
|
+
return (
|
|
581
|
+
bond_history_context?.source_bond_signature ??
|
|
582
|
+
(bond_edit_snapshot ? bond_signature(bond_edit_snapshot.bonds) : raw_signature)
|
|
583
|
+
)
|
|
566
584
|
}
|
|
567
585
|
|
|
568
586
|
const current_bond_edit_context = (): BondEditContext => ({
|
|
@@ -643,10 +661,21 @@
|
|
|
643
661
|
emit_bonds(edited_bonds)
|
|
644
662
|
})
|
|
645
663
|
|
|
664
|
+
// Elements currently anchoring polyhedra (written by StructureScene, read by
|
|
665
|
+
// StructureControls so per-element toggles reflect actual render state)
|
|
666
|
+
let polyhedra_rendered_elements = $state<string[]>([])
|
|
667
|
+
|
|
646
668
|
// === Edit-atoms mode state ===
|
|
647
669
|
let dragging_atoms = $state(false)
|
|
648
|
-
|
|
649
|
-
|
|
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[]>([])
|
|
650
679
|
const MAX_HISTORY = 20
|
|
651
680
|
// Flag set before internal edits (undo/redo/delete/add/move) to distinguish
|
|
652
681
|
// them from external structure changes (file load, trajectory step, etc.)
|
|
@@ -654,7 +683,6 @@
|
|
|
654
683
|
// Add-atom sub-mode state (bound to StructureScene)
|
|
655
684
|
let add_atom_mode = $state(false)
|
|
656
685
|
let add_element = $state<ElementSymbol>(`C` as ElementSymbol)
|
|
657
|
-
let canvas_cursor = $state(`default`)
|
|
658
686
|
let is_measure_selection_mode = $derived(
|
|
659
687
|
measure_mode === `distance` || measure_mode === `angle`,
|
|
660
688
|
)
|
|
@@ -682,9 +710,8 @@
|
|
|
682
710
|
|
|
683
711
|
// Normalize and validate element symbol (e.g. "fe" → "Fe", "Xx" → null)
|
|
684
712
|
function normalize_element(input: string): ElementSymbol | null {
|
|
685
|
-
const normalized =
|
|
686
|
-
|
|
687
|
-
return ELEM_SYMBOLS.includes(normalized) ? normalized : null
|
|
713
|
+
const normalized = input.charAt(0).toUpperCase() + input.slice(1).toLowerCase()
|
|
714
|
+
return coerce_elem_symbol(normalized) ?? null
|
|
688
715
|
}
|
|
689
716
|
|
|
690
717
|
function clear_selection() {
|
|
@@ -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,35 +1475,22 @@
|
|
|
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`)
|
|
1479
1482
|
}
|
|
1480
1483
|
})
|
|
1481
1484
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
set_fullscreen_bg(wrapper, fullscreen, `--struct-bg-fullscreen`)
|
|
1485
|
+
sync_fullscreen({
|
|
1486
|
+
get_wrapper: () => wrapper,
|
|
1487
|
+
get_fullscreen: () => fullscreen,
|
|
1488
|
+
set_fullscreen: (val) => (fullscreen = val),
|
|
1489
|
+
bg_css_var: `--struct-bg-fullscreen`,
|
|
1490
|
+
on_change: (val) => on_fullscreen_change?.({ structure, fullscreen: val }),
|
|
1491
1491
|
})
|
|
1492
1492
|
</script>
|
|
1493
1493
|
|
|
1494
|
-
<svelte:document
|
|
1495
|
-
onfullscreenchange={() => {
|
|
1496
|
-
fullscreen = Boolean(document.fullscreenElement)
|
|
1497
|
-
on_fullscreen_change?.({ structure, fullscreen })
|
|
1498
|
-
}}
|
|
1499
|
-
/>
|
|
1500
|
-
|
|
1501
1494
|
<!-- Forward shortcuts to the hovered viewer when focus is on <body> (see
|
|
1502
1495
|
forward_window_keydown). Edit modes are excluded so destructive keys
|
|
1503
1496
|
(delete/undo) still require focus, not just a hovering mouse. -->
|
|
@@ -1508,9 +1501,9 @@
|
|
|
1508
1501
|
class:dragover
|
|
1509
1502
|
class:active={info_pane_open || controls_open || export_pane_open}
|
|
1510
1503
|
class:gizmo-visible={Boolean(scene_gizmo)}
|
|
1504
|
+
class:multi-view={multi_view}
|
|
1511
1505
|
role="application"
|
|
1512
1506
|
tabindex="0"
|
|
1513
|
-
style:--canvas-cursor={canvas_cursor}
|
|
1514
1507
|
aria-label="Structure viewer"
|
|
1515
1508
|
bind:this={wrapper}
|
|
1516
1509
|
bind:clientWidth={width}
|
|
@@ -1523,30 +1516,14 @@
|
|
|
1523
1516
|
focused = false
|
|
1524
1517
|
}
|
|
1525
1518
|
}}
|
|
1526
|
-
ondblclick={(event) => {
|
|
1527
|
-
const target = event.target
|
|
1528
|
-
if (!(target instanceof HTMLElement)) return
|
|
1529
|
-
// Don't reset if double-click was on UI controls/panes/legend
|
|
1530
|
-
if (
|
|
1531
|
-
[`.control-buttons`, `.structure-legend`, `.atom-legend`, `.info-pane`, `.export-pane`, `.controls-pane`].some((selector) => target.closest(selector))
|
|
1532
|
-
|| target.tagName === `BUTTON` || target.tagName === `INPUT` || target.tagName === `SELECT`
|
|
1533
|
-
) return
|
|
1534
|
-
// Reset camera for double-clicks on the 3D scene
|
|
1535
|
-
reset_camera()
|
|
1536
|
-
}}
|
|
1537
1519
|
ondrop={handle_file_drop}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
}}
|
|
1543
|
-
ondragleave={(event) => {
|
|
1544
|
-
event.preventDefault()
|
|
1545
|
-
dragover = false
|
|
1546
|
-
}}
|
|
1520
|
+
{...drag_over_handlers({
|
|
1521
|
+
allow: () => allow_file_drop,
|
|
1522
|
+
set_dragover: (over) => (dragover = over),
|
|
1523
|
+
})}
|
|
1547
1524
|
onkeydown={handle_and_prevent(handle_keydown)}
|
|
1548
1525
|
{...rest}
|
|
1549
|
-
class=
|
|
1526
|
+
class={[`structure`, rest.class]}
|
|
1550
1527
|
>
|
|
1551
1528
|
{@render children?.({ structure, fullscreen })}
|
|
1552
1529
|
{#if loading}
|
|
@@ -1558,294 +1535,292 @@
|
|
|
1558
1535
|
{:else if error_msg}
|
|
1559
1536
|
<StatusMessage bind:message={error_msg} type="error" dismissible />
|
|
1560
1537
|
{:else if (structure?.sites?.length ?? 0) > 0}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1538
|
+
{#snippet reset_camera_btn()}
|
|
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
|
+
<!-- Target/Focus icon for reset camera -->
|
|
1547
|
+
<Icon icon="Reset" />
|
|
1548
|
+
</button>
|
|
1549
|
+
{/if}
|
|
1550
|
+
{/snippet}
|
|
1551
|
+
<ViewerChrome
|
|
1552
|
+
{controls_config}
|
|
1553
|
+
{fullscreen}
|
|
1554
|
+
{fullscreen_toggle}
|
|
1555
|
+
fullscreen_btn_style="padding: 0 3px"
|
|
1556
|
+
{wrapper}
|
|
1557
|
+
before={reset_camera_btn}
|
|
1558
|
+
style="--viewer-buttons-gap: 4pt; --viewer-buttons-btn-padding: 1px 6px; --viewer-buttons-align: stretch"
|
|
1564
1559
|
>
|
|
1565
|
-
{#if controls_config.
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
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
|
+
|
|
1574
|
+
{#if enable_measure_mode && controls_config.visible(`measure-mode`)}
|
|
1575
|
+
<div
|
|
1576
|
+
class="measure-mode-dropdown"
|
|
1577
|
+
{@attach click_outside({ callback: () => (measure_menu_open = false) })}
|
|
1578
|
+
>
|
|
1573
1579
|
<button
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
class=
|
|
1579
|
-
|
|
1580
|
+
onclick={() => (measure_menu_open = !measure_menu_open)}
|
|
1581
|
+
title="Measure / Edit"
|
|
1582
|
+
aria-label="Measure / Edit"
|
|
1583
|
+
class="view-mode-button"
|
|
1584
|
+
class:active={measure_menu_open}
|
|
1585
|
+
aria-expanded={measure_menu_open}
|
|
1586
|
+
style="transform: scale(1.2)"
|
|
1580
1587
|
{@attach tooltip()}
|
|
1581
1588
|
>
|
|
1582
|
-
{#if
|
|
1583
|
-
|
|
1589
|
+
{#if show_measure_selection_limit}
|
|
1590
|
+
<span class="selection-limit-text">
|
|
1591
|
+
{measured_sites.length}/{MAX_SELECTED_SITES}
|
|
1592
|
+
</span>
|
|
1584
1593
|
{:else}
|
|
1585
|
-
<Icon
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
{/if}
|
|
1589
|
-
|
|
1590
|
-
{#if enable_measure_mode && controls_config.visible(`measure-mode`)}
|
|
1591
|
-
<div
|
|
1592
|
-
class="measure-mode-dropdown"
|
|
1593
|
-
{@attach click_outside({ callback: () => measure_menu_open = false })}
|
|
1594
|
-
>
|
|
1595
|
-
<button
|
|
1596
|
-
onclick={() => (measure_menu_open = !measure_menu_open)}
|
|
1597
|
-
title="Measure / Edit"
|
|
1598
|
-
class="view-mode-button"
|
|
1599
|
-
class:active={measure_menu_open}
|
|
1600
|
-
aria-expanded={measure_menu_open}
|
|
1601
|
-
style="transform: scale(1.2)"
|
|
1602
|
-
>
|
|
1603
|
-
{#if show_measure_selection_limit}
|
|
1604
|
-
<span class="selection-limit-text">
|
|
1605
|
-
{measured_sites.length}/{MAX_SELECTED_SITES}
|
|
1606
|
-
</span>
|
|
1607
|
-
{:else}
|
|
1608
|
-
<Icon
|
|
1609
|
-
icon={({
|
|
1594
|
+
<Icon
|
|
1595
|
+
icon={(
|
|
1596
|
+
{
|
|
1610
1597
|
distance: `Ruler`,
|
|
1611
1598
|
angle: `Angle`,
|
|
1612
1599
|
'edit-bonds': `Link`,
|
|
1613
1600
|
'edit-atoms': `Edit`,
|
|
1614
|
-
} as const
|
|
1615
|
-
|
|
1616
|
-
{/if}
|
|
1617
|
-
<Icon
|
|
1618
|
-
icon="Arrow{measure_menu_open ? `Up` : `Down`}"
|
|
1619
|
-
style="margin-left: -2px"
|
|
1601
|
+
} as const
|
|
1602
|
+
)[measure_mode]}
|
|
1620
1603
|
/>
|
|
1621
|
-
</button>
|
|
1622
|
-
{#if show_selection_reset}
|
|
1623
|
-
<button
|
|
1624
|
-
type="button"
|
|
1625
|
-
aria-label="Reset selection and bond edits"
|
|
1626
|
-
onclick={() => {
|
|
1627
|
-
clear_selection()
|
|
1628
|
-
clear_bond_edits()
|
|
1629
|
-
}}
|
|
1630
|
-
>
|
|
1631
|
-
<Icon icon="Reset" style="margin-left: -4px" />
|
|
1632
|
-
</button>
|
|
1633
|
-
{/if}
|
|
1634
|
-
{#if measure_menu_open}
|
|
1635
|
-
<div class="view-mode-dropdown">
|
|
1636
|
-
{#each [
|
|
1637
|
-
{ mode: `distance`, icon: `Ruler`, label: `Distance`, scale: 1.1 },
|
|
1638
|
-
{ mode: `angle`, icon: `Angle`, label: `Angle`, scale: 1.3 },
|
|
1639
|
-
{ mode: `edit-atoms`, icon: `Edit`, label: `Edit Atoms`, scale: 1.0 },
|
|
1640
|
-
{ mode: `edit-bonds`, icon: `Link`, label: `Edit Bonds`, scale: 1.0 },
|
|
1641
|
-
] as const as { mode, icon, label, scale } (mode)}
|
|
1642
|
-
<button
|
|
1643
|
-
class="view-mode-option"
|
|
1644
|
-
class:selected={measure_mode === mode}
|
|
1645
|
-
disabled={mode === `edit-bonds` && !bond_edits_enabled}
|
|
1646
|
-
title={mode === `edit-bonds` && !bond_edits_enabled
|
|
1647
|
-
? `Bond editing is only available for the original 1x1x1 cell`
|
|
1648
|
-
: label}
|
|
1649
|
-
onclick={() => {
|
|
1650
|
-
if (mode === `edit-bonds` && !bond_edits_enabled) return
|
|
1651
|
-
;[measure_mode, measure_menu_open] = [mode, false]
|
|
1652
|
-
}}
|
|
1653
|
-
>
|
|
1654
|
-
<Icon {icon} style="transform: scale({scale})" />
|
|
1655
|
-
<span>{label}</span>
|
|
1656
|
-
</button>
|
|
1657
|
-
{/each}
|
|
1658
|
-
</div>
|
|
1659
1604
|
{/if}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
<Icon icon="Undo" />
|
|
1674
|
-
{#if undo_stack.length > 0}
|
|
1675
|
-
<span class="history-count">{undo_stack.length}</span>
|
|
1676
|
-
{/if}
|
|
1677
|
-
</button>
|
|
1678
|
-
<button
|
|
1679
|
-
type="button"
|
|
1680
|
-
aria-label="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1681
|
-
disabled={redo_stack.length === 0}
|
|
1682
|
-
onclick={redo}
|
|
1683
|
-
title="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1684
|
-
class="undo-redo-btn"
|
|
1685
|
-
>
|
|
1686
|
-
<Icon icon="Redo" />
|
|
1687
|
-
{#if redo_stack.length > 0}
|
|
1688
|
-
<span class="history-count">{redo_stack.length}</span>
|
|
1689
|
-
{/if}
|
|
1690
|
-
</button>
|
|
1691
|
-
</div>
|
|
1605
|
+
<Icon icon="Arrow{measure_menu_open ? `Up` : `Down`}" style="margin-left: -2px" />
|
|
1606
|
+
</button>
|
|
1607
|
+
{#if show_selection_reset}
|
|
1608
|
+
<button
|
|
1609
|
+
type="button"
|
|
1610
|
+
aria-label="Reset selection and bond edits"
|
|
1611
|
+
onclick={() => {
|
|
1612
|
+
clear_selection()
|
|
1613
|
+
clear_bond_edits()
|
|
1614
|
+
}}
|
|
1615
|
+
>
|
|
1616
|
+
<Icon icon="Reset" style="margin-left: -4px" />
|
|
1617
|
+
</button>
|
|
1692
1618
|
{/if}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
{
|
|
1709
|
-
{
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
type="button"
|
|
1713
|
-
class:selected={bond_edit_mode === mode}
|
|
1714
|
-
aria-pressed={bond_edit_mode === mode}
|
|
1715
|
-
title="{title} ({label[0]})"
|
|
1716
|
-
onclick={() => (bond_edit_mode = mode)}
|
|
1717
|
-
>
|
|
1718
|
-
{label}
|
|
1719
|
-
</button>
|
|
1720
|
-
{/each}
|
|
1721
|
-
</div>
|
|
1722
|
-
</div>
|
|
1723
|
-
<div class="undo-redo-container">
|
|
1724
|
-
<button
|
|
1725
|
-
type="button"
|
|
1726
|
-
aria-label="Undo bond edit (Cmd/Ctrl+Z)"
|
|
1727
|
-
disabled={bond_undo_stack.length === 0}
|
|
1728
|
-
onclick={undo_bond_edit}
|
|
1729
|
-
title="Undo bond edit (Cmd/Ctrl+Z)"
|
|
1730
|
-
class="undo-redo-btn"
|
|
1731
|
-
>
|
|
1732
|
-
<Icon icon="Undo" />
|
|
1733
|
-
{#if bond_undo_stack.length > 0}
|
|
1734
|
-
<span class="history-count">{bond_undo_stack.length}</span>
|
|
1735
|
-
{/if}
|
|
1736
|
-
</button>
|
|
1737
|
-
<button
|
|
1738
|
-
type="button"
|
|
1739
|
-
aria-label="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1740
|
-
disabled={bond_redo_stack.length === 0}
|
|
1741
|
-
onclick={redo_bond_edit}
|
|
1742
|
-
title="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1743
|
-
class="undo-redo-btn"
|
|
1744
|
-
>
|
|
1745
|
-
<Icon icon="Redo" />
|
|
1746
|
-
{#if bond_redo_stack.length > 0}
|
|
1747
|
-
<span class="history-count">{bond_redo_stack.length}</span>
|
|
1748
|
-
{/if}
|
|
1749
|
-
</button>
|
|
1619
|
+
{#if measure_menu_open}
|
|
1620
|
+
<div class="view-mode-dropdown">
|
|
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)}
|
|
1622
|
+
<button
|
|
1623
|
+
class="view-mode-option"
|
|
1624
|
+
class:selected={measure_mode === mode}
|
|
1625
|
+
disabled={mode === `edit-bonds` && !bond_edits_enabled}
|
|
1626
|
+
title={mode === `edit-bonds` && !bond_edits_enabled
|
|
1627
|
+
? `Bond editing is only available for the original 1x1x1 cell`
|
|
1628
|
+
: label}
|
|
1629
|
+
onclick={() => {
|
|
1630
|
+
if (mode === `edit-bonds` && !bond_edits_enabled) return
|
|
1631
|
+
;[measure_mode, measure_menu_open] = [mode, false]
|
|
1632
|
+
}}
|
|
1633
|
+
>
|
|
1634
|
+
<Icon {icon} style="transform: scale({scale})" />
|
|
1635
|
+
<span>{label}</span>
|
|
1636
|
+
</button>
|
|
1637
|
+
{/each}
|
|
1750
1638
|
</div>
|
|
1751
1639
|
{/if}
|
|
1640
|
+
</div>
|
|
1752
1641
|
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1642
|
+
<!-- Undo/redo buttons (only in edit-atoms mode) -->
|
|
1643
|
+
{#if measure_mode === `edit-atoms`}
|
|
1644
|
+
<div class="undo-redo-container">
|
|
1645
|
+
<button
|
|
1646
|
+
type="button"
|
|
1647
|
+
aria-label="Undo (Cmd/Ctrl+Z)"
|
|
1648
|
+
disabled={undo_stack.length === 0}
|
|
1649
|
+
onclick={undo}
|
|
1650
|
+
title="Undo (Cmd/Ctrl+Z)"
|
|
1651
|
+
class="undo-redo-btn"
|
|
1652
|
+
>
|
|
1653
|
+
<Icon icon="Undo" />
|
|
1654
|
+
{#if undo_stack.length > 0}
|
|
1655
|
+
<span class="history-count">{undo_stack.length}</span>
|
|
1656
|
+
{/if}
|
|
1657
|
+
</button>
|
|
1658
|
+
<button
|
|
1659
|
+
type="button"
|
|
1660
|
+
aria-label="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1661
|
+
disabled={redo_stack.length === 0}
|
|
1662
|
+
onclick={redo}
|
|
1663
|
+
title="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1664
|
+
class="undo-redo-btn"
|
|
1665
|
+
>
|
|
1666
|
+
<Icon icon="Redo" />
|
|
1667
|
+
{#if redo_stack.length > 0}
|
|
1668
|
+
<span class="history-count">{redo_stack.length}</span>
|
|
1669
|
+
{/if}
|
|
1670
|
+
</button>
|
|
1671
|
+
</div>
|
|
1672
|
+
{/if}
|
|
1769
1673
|
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
<div class="add-atom-input">
|
|
1674
|
+
{#if measure_mode === `edit-bonds`}
|
|
1675
|
+
<div class="bond-edit-toolbar" aria-label="Bond editing controls">
|
|
1676
|
+
{#if bond_edit_mode === `add`}
|
|
1774
1677
|
<label>
|
|
1775
|
-
<span>
|
|
1776
|
-
<
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
style="width: 3em; text-align: center"
|
|
1782
|
-
onkeydown={(event: KeyboardEvent) => {
|
|
1783
|
-
if (event.key === `Enter`) {
|
|
1784
|
-
handle_change_element(change_element_value)
|
|
1785
|
-
} else if (event.key === `Escape`) {
|
|
1786
|
-
change_element_mode = false
|
|
1787
|
-
}
|
|
1788
|
-
event.stopPropagation()
|
|
1789
|
-
}}
|
|
1790
|
-
{@attach (node: HTMLInputElement) => {
|
|
1791
|
-
node.focus()
|
|
1792
|
-
}}
|
|
1793
|
-
/>
|
|
1678
|
+
<span>Bond order</span>
|
|
1679
|
+
<select bind:value={bond_edit_order}>
|
|
1680
|
+
{#each BOND_ORDER_OPTIONS as { order, label } (label)}
|
|
1681
|
+
<option value={order}>{label}</option>
|
|
1682
|
+
{/each}
|
|
1683
|
+
</select>
|
|
1794
1684
|
</label>
|
|
1795
|
-
|
|
1685
|
+
{/if}
|
|
1686
|
+
<div class="bond-edit-mode-toggle">
|
|
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)}
|
|
1688
|
+
<button
|
|
1689
|
+
type="button"
|
|
1690
|
+
class:selected={bond_edit_mode === mode}
|
|
1691
|
+
aria-pressed={bond_edit_mode === mode}
|
|
1692
|
+
title="{title} ({label[0]})"
|
|
1693
|
+
onclick={() => (bond_edit_mode = mode)}
|
|
1694
|
+
>
|
|
1695
|
+
{label}
|
|
1696
|
+
</button>
|
|
1697
|
+
{/each}
|
|
1796
1698
|
</div>
|
|
1797
|
-
|
|
1699
|
+
</div>
|
|
1700
|
+
<div class="undo-redo-container">
|
|
1701
|
+
<button
|
|
1702
|
+
type="button"
|
|
1703
|
+
aria-label="Undo bond edit (Cmd/Ctrl+Z)"
|
|
1704
|
+
disabled={bond_undo_stack.length === 0}
|
|
1705
|
+
onclick={undo_bond_edit}
|
|
1706
|
+
title="Undo bond edit (Cmd/Ctrl+Z)"
|
|
1707
|
+
class="undo-redo-btn"
|
|
1708
|
+
>
|
|
1709
|
+
<Icon icon="Undo" />
|
|
1710
|
+
{#if bond_undo_stack.length > 0}
|
|
1711
|
+
<span class="history-count">{bond_undo_stack.length}</span>
|
|
1712
|
+
{/if}
|
|
1713
|
+
</button>
|
|
1714
|
+
<button
|
|
1715
|
+
type="button"
|
|
1716
|
+
aria-label="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1717
|
+
disabled={bond_redo_stack.length === 0}
|
|
1718
|
+
onclick={redo_bond_edit}
|
|
1719
|
+
title="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1720
|
+
class="undo-redo-btn"
|
|
1721
|
+
>
|
|
1722
|
+
<Icon icon="Redo" />
|
|
1723
|
+
{#if bond_redo_stack.length > 0}
|
|
1724
|
+
<span class="history-count">{bond_redo_stack.length}</span>
|
|
1725
|
+
{/if}
|
|
1726
|
+
</button>
|
|
1727
|
+
</div>
|
|
1798
1728
|
{/if}
|
|
1799
1729
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
<
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1730
|
+
<!-- Add-atom element input (shown when add_atom_mode is active) -->
|
|
1731
|
+
{#if measure_mode === `edit-atoms` && add_atom_mode}
|
|
1732
|
+
<div class="add-atom-input">
|
|
1733
|
+
<label>
|
|
1734
|
+
<span>Element:</span>
|
|
1735
|
+
<input
|
|
1736
|
+
type="text"
|
|
1737
|
+
bind:value={add_element}
|
|
1738
|
+
maxlength="2"
|
|
1739
|
+
placeholder="C"
|
|
1740
|
+
style="width: 3em; text-align: center"
|
|
1741
|
+
/>
|
|
1742
|
+
</label>
|
|
1743
|
+
<span style="font-size: 0.75em; opacity: 0.7">Click to place</span>
|
|
1744
|
+
</div>
|
|
1811
1745
|
{/if}
|
|
1812
1746
|
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1747
|
+
<!-- Change-element input (shown when 'e' pressed with selection) -->
|
|
1748
|
+
{#if measure_mode === `edit-atoms` && change_element_mode && selected_sites.length > 0}
|
|
1749
|
+
<div class="add-atom-input">
|
|
1750
|
+
<label>
|
|
1751
|
+
<span>New element:</span>
|
|
1752
|
+
<input
|
|
1753
|
+
type="text"
|
|
1754
|
+
bind:value={change_element_value}
|
|
1755
|
+
maxlength="2"
|
|
1756
|
+
placeholder="Fe"
|
|
1757
|
+
style="width: 3em; text-align: center"
|
|
1758
|
+
onkeydown={(event: KeyboardEvent) => {
|
|
1759
|
+
if (event.key === `Enter`) {
|
|
1760
|
+
handle_change_element(change_element_value)
|
|
1761
|
+
} else if (event.key === `Escape`) {
|
|
1762
|
+
change_element_mode = false
|
|
1763
|
+
}
|
|
1764
|
+
event.stopPropagation()
|
|
1765
|
+
}}
|
|
1766
|
+
{@attach (node: HTMLInputElement) => {
|
|
1767
|
+
node.focus()
|
|
1768
|
+
}}
|
|
1769
|
+
/>
|
|
1770
|
+
</label>
|
|
1771
|
+
<span style="font-size: 0.75em; opacity: 0.7">Enter to apply</span>
|
|
1772
|
+
</div>
|
|
1823
1773
|
{/if}
|
|
1774
|
+
{/if}
|
|
1824
1775
|
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
bind:cell_type
|
|
1837
|
-
bind:volumetric_data
|
|
1838
|
-
bind:isosurface_settings
|
|
1839
|
-
bind:active_volume_idx
|
|
1840
|
-
{structure}
|
|
1841
|
-
{supercell_loading}
|
|
1842
|
-
{sym_data}
|
|
1843
|
-
/>
|
|
1844
|
-
{/if}
|
|
1776
|
+
{#if enable_info_pane && normalized_structure && controls_config.visible(`info-pane`)}
|
|
1777
|
+
<StructureInfoPane
|
|
1778
|
+
structure={normalized_structure}
|
|
1779
|
+
bind:pane_open={info_pane_open}
|
|
1780
|
+
bind:highlighted_sites
|
|
1781
|
+
bind:hovered_site_idx
|
|
1782
|
+
bind:selected_sites
|
|
1783
|
+
{sym_data}
|
|
1784
|
+
{@attach tooltip({ content: `Structure info pane` })}
|
|
1785
|
+
/>
|
|
1786
|
+
{/if}
|
|
1845
1787
|
|
|
1846
|
-
|
|
1788
|
+
{#if controls_config.visible(`export-pane`)}
|
|
1789
|
+
<StructureExportPane
|
|
1790
|
+
bind:export_pane_open
|
|
1791
|
+
structure={normalized_structure}
|
|
1792
|
+
{wrapper}
|
|
1793
|
+
{scene}
|
|
1794
|
+
{camera}
|
|
1795
|
+
bind:png_dpi
|
|
1796
|
+
pane_props={{ style: `max-height: calc(${height}px - 50px)` }}
|
|
1797
|
+
/>
|
|
1798
|
+
{/if}
|
|
1799
|
+
|
|
1800
|
+
{#if controls_config.visible(`controls`)}
|
|
1801
|
+
<StructureControls
|
|
1802
|
+
bind:controls_open
|
|
1803
|
+
bind:scene_props
|
|
1804
|
+
bind:lattice_props
|
|
1805
|
+
bind:show_image_atoms
|
|
1806
|
+
bind:supercell_scaling
|
|
1807
|
+
bind:background_color
|
|
1808
|
+
bind:background_opacity
|
|
1809
|
+
bind:color_scheme
|
|
1810
|
+
bind:atom_color_config
|
|
1811
|
+
bind:cell_type
|
|
1812
|
+
bind:volumetric_data
|
|
1813
|
+
bind:isosurface_settings
|
|
1814
|
+
bind:active_volume_idx
|
|
1815
|
+
{structure}
|
|
1816
|
+
{supercell_loading}
|
|
1817
|
+
{sym_data}
|
|
1818
|
+
{polyhedra_rendered_elements}
|
|
1819
|
+
/>
|
|
1847
1820
|
{/if}
|
|
1848
|
-
|
|
1821
|
+
|
|
1822
|
+
{@render top_right_controls?.()}
|
|
1823
|
+
</ViewerChrome>
|
|
1849
1824
|
|
|
1850
1825
|
<AtomLegend
|
|
1851
1826
|
bind:atom_color_config
|
|
@@ -1857,74 +1832,108 @@
|
|
|
1857
1832
|
bind:element_radius_overrides
|
|
1858
1833
|
bind:site_radius_overrides
|
|
1859
1834
|
selected_sites={atom_legend_selected_sites}
|
|
1860
|
-
structure={
|
|
1835
|
+
structure={internal_displayed_structure}
|
|
1861
1836
|
show_mode_toggle={viewer_active}
|
|
1862
1837
|
{sym_data}
|
|
1863
1838
|
>
|
|
1864
|
-
{#
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1839
|
+
{#snippet children({ mode_menu_open })}
|
|
1840
|
+
{#if structure && `lattice` in structure}
|
|
1841
|
+
<CellSelect
|
|
1842
|
+
bind:supercell_scaling
|
|
1843
|
+
bind:cell_type
|
|
1844
|
+
{sym_data}
|
|
1845
|
+
loading={supercell_loading}
|
|
1846
|
+
direction="up"
|
|
1847
|
+
suppress_hover={mode_menu_open}
|
|
1848
|
+
/>
|
|
1849
|
+
{/if}
|
|
1850
|
+
{/snippet}
|
|
1873
1851
|
</AtomLegend>
|
|
1874
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
|
+
|
|
1875
1923
|
<!-- prevent from rendering in vitest runner since WebGLRenderingContext not available -->
|
|
1876
1924
|
{#if typeof WebGLRenderingContext !== `undefined`}
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
gizmo={scene_gizmo}
|
|
1885
|
-
{lattice_props}
|
|
1886
|
-
volumetric_data={supercell_volume}
|
|
1887
|
-
{isosurface_settings}
|
|
1888
|
-
bind:camera_is_moving
|
|
1889
|
-
bind:selected_sites
|
|
1890
|
-
active_sites={active_scene_sites}
|
|
1891
|
-
bind:hovered_idx={hovered_site_idx}
|
|
1892
|
-
bind:measured_sites
|
|
1893
|
-
bind:scene
|
|
1894
|
-
bind:camera
|
|
1895
|
-
bind:orbit_controls
|
|
1896
|
-
bind:rotation_target_ref
|
|
1897
|
-
bind:initial_computed_zoom
|
|
1898
|
-
bind:hidden_elements
|
|
1899
|
-
bind:hidden_prop_vals
|
|
1900
|
-
bind:element_radius_overrides
|
|
1901
|
-
bind:site_radius_overrides
|
|
1902
|
-
bind:added_bonds
|
|
1903
|
-
bind:removed_bonds
|
|
1904
|
-
bind:bond_order_overrides
|
|
1905
|
-
{bond_edits_enabled}
|
|
1906
|
-
bind:bond_edit_mode
|
|
1907
|
-
{bond_edit_order}
|
|
1908
|
-
{measure_mode}
|
|
1909
|
-
{width}
|
|
1910
|
-
{height}
|
|
1911
|
-
{atom_color_config}
|
|
1912
|
-
{sym_data}
|
|
1913
|
-
on_sites_moved={handle_sites_moved}
|
|
1914
|
-
on_operation_start={push_undo}
|
|
1915
|
-
on_bond_edit_start={push_bond_undo}
|
|
1916
|
-
on_add_atom={handle_add_atom}
|
|
1917
|
-
bind:add_atom_mode
|
|
1918
|
-
bind:add_element
|
|
1919
|
-
bind:cursor={canvas_cursor}
|
|
1920
|
-
bind:dragging_atoms
|
|
1921
|
-
/>
|
|
1922
|
-
</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}
|
|
1923
1932
|
</div>
|
|
1924
1933
|
{/if}
|
|
1925
1934
|
|
|
1926
1935
|
<div class="bottom-left">
|
|
1927
|
-
{@render bottom_left?.({ structure:
|
|
1936
|
+
{@render bottom_left?.({ structure: internal_displayed_structure })}
|
|
1928
1937
|
</div>
|
|
1929
1938
|
|
|
1930
1939
|
{#if toast_msg}
|
|
@@ -1934,9 +1943,7 @@
|
|
|
1934
1943
|
{#if symmetry_error}
|
|
1935
1944
|
<div class="symmetry-error">
|
|
1936
1945
|
<span>{symmetry_error}</span>
|
|
1937
|
-
<button onclick={() => (symmetry_error = undefined)} aria-label="Dismiss">
|
|
1938
|
-
×
|
|
1939
|
-
</button>
|
|
1946
|
+
<button onclick={() => (symmetry_error = undefined)} aria-label="Dismiss"> × </button>
|
|
1940
1947
|
</div>
|
|
1941
1948
|
{/if}
|
|
1942
1949
|
{:else if structure}
|
|
@@ -1966,7 +1973,9 @@
|
|
|
1966
1973
|
background: var(--struct-bg-fullscreen, var(--struct-bg));
|
|
1967
1974
|
overflow: hidden;
|
|
1968
1975
|
}
|
|
1969
|
-
|
|
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) {
|
|
1970
1979
|
height: 100vh !important;
|
|
1971
1980
|
width: 100vw !important;
|
|
1972
1981
|
}
|
|
@@ -1974,6 +1983,22 @@
|
|
|
1974
1983
|
background: var(--struct-dragover-bg, var(--dragover-bg));
|
|
1975
1984
|
border: var(--struct-dragover-border, var(--dragover-border));
|
|
1976
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
|
+
}
|
|
1977
2002
|
/* Ensure canvas is transparent so the themed --struct-bg shows through */
|
|
1978
2003
|
.structure :global(canvas) {
|
|
1979
2004
|
background: transparent;
|
|
@@ -1986,7 +2011,7 @@
|
|
|
1986
2011
|
}
|
|
1987
2012
|
/* Avoid accidental text selection while interacting with the viewer */
|
|
1988
2013
|
.structure :global(canvas),
|
|
1989
|
-
.structure section.control-buttons,
|
|
2014
|
+
.structure :global(section.control-buttons),
|
|
1990
2015
|
.structure .bottom-left {
|
|
1991
2016
|
user-select: none;
|
|
1992
2017
|
}
|
|
@@ -1997,44 +2022,6 @@
|
|
|
1997
2022
|
font-size: var(--struct-bottom-left-font-size, 1.2em);
|
|
1998
2023
|
padding: var(--struct-bottom-left-padding, 1pt 5pt);
|
|
1999
2024
|
}
|
|
2000
|
-
section.control-buttons {
|
|
2001
|
-
position: absolute;
|
|
2002
|
-
display: flex;
|
|
2003
|
-
top: var(--struct-buttons-top, var(--ctrl-btn-top, 1ex));
|
|
2004
|
-
right: var(--struct-buttons-right, var(--ctrl-btn-right, 1ex));
|
|
2005
|
-
gap: 4pt;
|
|
2006
|
-
/* buttons need higher z-index than AtomLegend to make info/controls panes occlude legend */
|
|
2007
|
-
/* we also need crazy high z-index to make info/control pane occlude threlte/extras' <HTML> elements for site labels */
|
|
2008
|
-
z-index: var(
|
|
2009
|
-
--struct-buttons-z-index,
|
|
2010
|
-
var(--z-index-overlay-controls, 100000000)
|
|
2011
|
-
);
|
|
2012
|
-
opacity: 0;
|
|
2013
|
-
pointer-events: none;
|
|
2014
|
-
transition: opacity 0.2s ease;
|
|
2015
|
-
}
|
|
2016
|
-
/* Mode: always - controls always visible */
|
|
2017
|
-
section.control-buttons.always-visible {
|
|
2018
|
-
opacity: 1;
|
|
2019
|
-
pointer-events: auto;
|
|
2020
|
-
}
|
|
2021
|
-
/* Mode: hover - controls visible on component hover */
|
|
2022
|
-
.structure:hover section.control-buttons.hover-visible,
|
|
2023
|
-
.structure:focus-within section.control-buttons.hover-visible {
|
|
2024
|
-
opacity: 1;
|
|
2025
|
-
pointer-events: auto;
|
|
2026
|
-
}
|
|
2027
|
-
/* Mode: never - stays hidden (default state, no additional CSS needed) */
|
|
2028
|
-
section.control-buttons > :global(button) {
|
|
2029
|
-
background-color: transparent;
|
|
2030
|
-
display: flex;
|
|
2031
|
-
padding: 1px 6px;
|
|
2032
|
-
border-radius: var(--border-radius, 3pt);
|
|
2033
|
-
font-size: clamp(0.85em, 2cqmin, 1.3em);
|
|
2034
|
-
}
|
|
2035
|
-
section.control-buttons :global(button:hover) {
|
|
2036
|
-
background-color: color-mix(in srgb, currentColor 8%, transparent);
|
|
2037
|
-
}
|
|
2038
2025
|
/* Match Trajectory dropdown UI */
|
|
2039
2026
|
.view-mode-dropdown {
|
|
2040
2027
|
position: absolute;
|
|
@@ -2042,7 +2029,9 @@
|
|
|
2042
2029
|
right: 0;
|
|
2043
2030
|
background: var(--surface-bg);
|
|
2044
2031
|
border-radius: var(--border-radius, 3pt);
|
|
2045
|
-
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);
|
|
2046
2035
|
display: flex;
|
|
2047
2036
|
flex-direction: column;
|
|
2048
2037
|
}
|
|
@@ -2081,7 +2070,7 @@
|
|
|
2081
2070
|
.measure-mode-dropdown > button {
|
|
2082
2071
|
background: transparent;
|
|
2083
2072
|
padding: 1px 6px;
|
|
2084
|
-
font-size: clamp(0.
|
|
2073
|
+
font-size: var(--ctrl-btn-icon-size, clamp(0.7rem, 2cqmin, 0.85rem));
|
|
2085
2074
|
}
|
|
2086
2075
|
.selection-limit-text {
|
|
2087
2076
|
font-weight: bold;
|
|
@@ -2143,7 +2132,8 @@
|
|
|
2143
2132
|
opacity: 0;
|
|
2144
2133
|
}
|
|
2145
2134
|
@keyframes toast-fade {
|
|
2146
|
-
0%,
|
|
2135
|
+
0%,
|
|
2136
|
+
70% {
|
|
2147
2137
|
opacity: 1;
|
|
2148
2138
|
}
|
|
2149
2139
|
100% {
|