matterviz 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EmptyState.svelte +10 -4
- package/dist/FilePicker.svelte +48 -60
- package/dist/Icon.svelte +7 -4
- package/dist/MillerIndexInput.svelte +2 -2
- package/dist/api/optimade.js +10 -7
- package/dist/app.css +68 -0
- package/dist/brillouin/BrillouinZone.svelte +90 -91
- package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
- package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
- package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
- package/dist/brillouin/BrillouinZoneScene.svelte +46 -68
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/brillouin/compute.js +10 -16
- package/dist/brillouin/geometry.js +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +48 -50
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +61 -169
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +559 -755
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/async-compute.svelte.js +13 -14
- package/dist/chempot-diagram/color.d.ts +21 -1
- package/dist/chempot-diagram/color.js +70 -1
- package/dist/chempot-diagram/compute.d.ts +7 -0
- package/dist/chempot-diagram/compute.js +85 -26
- package/dist/chempot-diagram/controls-state.svelte.js +1 -1
- package/dist/chempot-diagram/export.js +1 -6
- package/dist/chempot-diagram/temperature.js +9 -10
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +19 -20
- package/dist/composition/BarChart.svelte +30 -26
- package/dist/composition/BubbleChart.svelte +17 -17
- package/dist/composition/Composition.svelte +10 -19
- package/dist/composition/Formula.svelte +20 -27
- package/dist/composition/FormulaFilter.svelte +115 -184
- package/dist/composition/PieChart.svelte +55 -41
- package/dist/composition/format.js +7 -9
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.js +14 -0
- package/dist/composition/parse.d.ts +2 -2
- package/dist/composition/parse.js +61 -115
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +17 -26
- package/dist/convex-hull/ConvexHull.svelte +12 -15
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +93 -94
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +127 -186
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +131 -180
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
- package/dist/convex-hull/ConvexHullControls.svelte +46 -48
- package/dist/convex-hull/ConvexHullStats.svelte +193 -195
- package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
- package/dist/convex-hull/GasPressureControls.svelte +5 -12
- package/dist/convex-hull/StructurePopup.svelte +18 -22
- package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
- package/dist/convex-hull/TemperatureSlider.svelte +4 -10
- package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
- package/dist/convex-hull/gas-thermodynamics.js +21 -53
- package/dist/convex-hull/helpers.d.ts +8 -10
- package/dist/convex-hull/helpers.js +37 -43
- package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
- package/dist/convex-hull/thermodynamics.js +37 -44
- package/dist/coordination/CoordinationBarPlot.svelte +49 -53
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
- package/dist/coordination/calc-coordination.d.ts +1 -1
- package/dist/coordination/calc-coordination.js +28 -25
- package/dist/element/BohrAtom.svelte +5 -9
- 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 +44 -67
- package/dist/element/Nucleus.svelte +4 -2
- package/dist/element/data.d.ts +3 -2
- package/dist/element/data.js +5 -1
- package/dist/element/helpers.d.ts +4 -0
- package/dist/element/helpers.js +18 -0
- package/dist/element/index.d.ts +2 -5
- package/dist/element/index.js +4 -19
- package/dist/feedback/DragOverlay.svelte +5 -5
- package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
- package/dist/feedback/StatusMessage.svelte +7 -4
- package/dist/fermi-surface/FermiSlice.svelte +21 -18
- package/dist/fermi-surface/FermiSurface.svelte +35 -44
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +7 -13
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +64 -136
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +25 -58
- package/dist/fermi-surface/parse.js +54 -88
- package/dist/file-viewer/JsonBrowser.svelte +1043 -0
- package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
- package/dist/file-viewer/PlotPanel.svelte +350 -0
- package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
- package/dist/file-viewer/detect.d.ts +11 -0
- package/dist/file-viewer/detect.js +381 -0
- package/dist/file-viewer/eligibility.d.ts +4 -0
- package/dist/file-viewer/eligibility.js +53 -0
- package/dist/file-viewer/host-protocol.d.ts +50 -0
- package/dist/file-viewer/host-transfer.d.ts +25 -0
- package/dist/file-viewer/host-transfer.js +42 -0
- package/dist/file-viewer/index.d.ts +5 -0
- package/dist/file-viewer/index.js +5 -0
- package/dist/file-viewer/main.d.ts +44 -0
- package/dist/file-viewer/main.js +535 -0
- package/dist/file-viewer/parse.d.ts +9 -0
- package/dist/file-viewer/parse.js +170 -0
- package/dist/file-viewer/plot-utils.d.ts +24 -0
- package/dist/file-viewer/plot-utils.js +191 -0
- package/dist/file-viewer/types.d.ts +4 -0
- package/dist/file-viewer/types.js +5 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +153 -219
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/heatmap-matrix/index.d.ts +5 -0
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/io/ExportPane.svelte +11 -7
- package/dist/io/decompress.d.ts +4 -1
- package/dist/io/decompress.js +24 -36
- package/dist/io/export.d.ts +11 -4
- package/dist/io/export.js +75 -19
- package/dist/io/fetch.d.ts +0 -3
- package/dist/io/fetch.js +0 -11
- package/dist/io/file-drop.js +60 -35
- package/dist/io/index.d.ts +0 -1
- package/dist/io/index.js +2 -9
- package/dist/io/is-binary.d.ts +9 -0
- package/dist/io/is-binary.js +45 -1
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +40 -40
- package/dist/isosurface/Isosurface.svelte +700 -215
- package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +590 -116
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
- package/dist/isosurface/VolumeSlice.svelte +224 -0
- package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
- package/dist/isosurface/coloring.d.ts +20 -0
- package/dist/isosurface/coloring.js +118 -0
- package/dist/isosurface/geometry-worker-types.d.ts +38 -0
- package/dist/isosurface/geometry.worker.d.ts +1 -0
- package/dist/isosurface/geometry.worker.js +59 -0
- package/dist/isosurface/grid.d.ts +10 -0
- package/dist/isosurface/grid.js +31 -0
- package/dist/isosurface/index.d.ts +4 -0
- package/dist/isosurface/index.js +4 -0
- package/dist/isosurface/parse-vaspwave.d.ts +3 -0
- package/dist/isosurface/parse-vaspwave.js +138 -0
- package/dist/isosurface/parse.js +6 -6
- package/dist/isosurface/profile.d.ts +12 -0
- package/dist/isosurface/profile.js +19 -0
- package/dist/isosurface/sampling.d.ts +34 -0
- package/dist/isosurface/sampling.js +408 -0
- package/dist/isosurface/slice-rendering.d.ts +13 -0
- package/dist/isosurface/slice-rendering.js +79 -0
- package/dist/isosurface/slice.d.ts +22 -2
- package/dist/isosurface/slice.js +200 -116
- package/dist/isosurface/types.d.ts +24 -1
- package/dist/isosurface/types.js +120 -8
- package/dist/json-path.d.ts +4 -0
- package/dist/json-path.js +89 -0
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +44 -14
- package/dist/layout/FullscreenButton.svelte +2 -1
- package/dist/layout/FullscreenToggle.svelte +10 -4
- package/dist/layout/InfoCard.svelte +8 -21
- 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 +57 -62
- package/dist/layout/SettingsSection.svelte.d.ts +6 -7
- package/dist/layout/SubpageGrid.svelte +3 -1
- package/dist/layout/ViewerChrome.svelte +16 -11
- package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
- package/dist/layout/fullscreen.js +3 -5
- package/dist/layout/fullscreen.svelte.js +1 -2
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +22 -59
- package/dist/layout/json-tree/JsonTree.svelte +101 -172
- package/dist/layout/json-tree/JsonValue.svelte +9 -32
- package/dist/layout/json-tree/utils.d.ts +0 -3
- package/dist/layout/json-tree/utils.js +50 -188
- package/dist/marching-cubes.d.ts +8 -1
- package/dist/marching-cubes.js +115 -48
- package/dist/math.d.ts +5 -2
- package/dist/math.js +48 -38
- package/dist/overlays/ContextMenu.svelte +19 -34
- package/dist/overlays/CopyButton.svelte +11 -5
- package/dist/overlays/DragControlTab.svelte +6 -1
- package/dist/overlays/DraggablePane.svelte +110 -74
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -1
- package/dist/overlays/GlassChip.svelte +32 -0
- package/dist/overlays/GlassChip.svelte.d.ts +8 -0
- package/dist/overlays/InfoPaneCards.svelte +11 -23
- package/dist/overlays/index.d.ts +7 -0
- package/dist/overlays/index.js +26 -0
- package/dist/overlays/portal.d.ts +2 -0
- package/dist/overlays/portal.js +14 -0
- package/dist/periodic-table/PeriodicTable.svelte +113 -145
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
- package/dist/periodic-table/PeriodicTableControls.svelte +37 -115
- package/dist/periodic-table/TableInset.svelte +9 -4
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +68 -105
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
- package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
- package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
- package/dist/phase-diagram/build-diagram.js +4 -9
- package/dist/phase-diagram/diagram-input.d.ts +1 -2
- package/dist/phase-diagram/parse.js +15 -15
- package/dist/phase-diagram/svg-to-diagram.js +28 -27
- package/dist/phase-diagram/utils.js +2 -3
- package/dist/plot/bar/BarPlot.svelte +461 -426
- package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
- package/dist/plot/bar/BarPlotControls.svelte +1 -3
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
- package/dist/plot/bar/SpacegroupBarPlot.svelte +46 -77
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +322 -226
- package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
- package/dist/plot/box/Violin.svelte.d.ts +1 -0
- package/dist/plot/box/box-plot.js +1 -1
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +2 -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 +101 -106
- package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
- package/dist/plot/core/components/FillArea.svelte +10 -17
- package/dist/plot/core/components/HierarchyControls.svelte +249 -0
- package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
- package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
- package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/core/components/Line.svelte +18 -9
- package/dist/plot/core/components/Line.svelte.d.ts +2 -0
- package/dist/plot/core/components/PlotAxis.svelte +40 -17
- package/dist/plot/core/components/PlotControls.svelte +65 -41
- package/dist/plot/core/components/PlotLegend.svelte +31 -35
- package/dist/plot/core/components/PlotMarginals.svelte +616 -0
- package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
- package/dist/plot/core/components/PlotTooltip.svelte +1 -1
- package/dist/plot/core/components/PortalSelect.svelte +4 -8
- package/dist/plot/core/components/ReferenceLine.svelte +27 -24
- package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
- package/dist/plot/core/components/ReferencePlane.svelte +7 -8
- package/dist/plot/core/components/ZeroLines.svelte +4 -8
- package/dist/plot/core/components/ZoomRect.svelte +1 -2
- package/dist/plot/core/components/index.d.ts +1 -0
- package/dist/plot/core/components/index.js +1 -0
- package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
- package/dist/plot/core/data-cleaning-signal.js +467 -0
- package/dist/plot/core/data-cleaning.d.ts +2 -42
- package/dist/plot/core/data-cleaning.js +27 -474
- package/dist/plot/core/data-transform.d.ts +1 -2
- package/dist/plot/core/data-transform.js +9 -15
- package/dist/plot/core/fill-utils.d.ts +3 -1
- package/dist/plot/core/fill-utils.js +28 -6
- package/dist/plot/core/index.d.ts +1 -0
- package/dist/plot/core/index.js +1 -0
- package/dist/plot/core/interactions.js +0 -2
- package/dist/plot/core/layout.d.ts +15 -6
- package/dist/plot/core/layout.js +57 -38
- 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 +2 -4
- package/dist/plot/core/pan-zoom.svelte.js +34 -17
- package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -21
- package/dist/plot/core/reference-line.js +1 -1
- package/dist/plot/core/scales.d.ts +23 -0
- package/dist/plot/core/scales.js +39 -13
- package/dist/plot/core/svg.js +0 -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 +29 -324
- package/dist/plot/core/types.js +23 -20
- package/dist/plot/core/utils/hierarchy-chart.d.ts +52 -0
- package/dist/plot/core/utils/hierarchy-chart.js +191 -0
- package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
- package/dist/plot/core/utils/hierarchy-labels.js +100 -0
- package/dist/plot/core/utils/label-placement.js +2 -2
- package/dist/plot/histogram/Histogram.svelte +314 -340
- package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
- package/dist/plot/histogram/HistogramControls.svelte +30 -28
- package/dist/plot/histogram/histogram.d.ts +31 -0
- package/dist/plot/histogram/histogram.js +70 -0
- package/dist/plot/histogram/index.d.ts +1 -0
- package/dist/plot/histogram/index.js +1 -0
- package/dist/plot/index.d.ts +1 -0
- package/dist/plot/index.js +1 -0
- package/dist/plot/sankey/Sankey.svelte +78 -69
- package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
- package/dist/plot/sankey/SankeyControls.svelte +22 -16
- package/dist/plot/sankey/sankey.js +13 -9
- package/dist/plot/scatter/BinnedScatterPlot.svelte +311 -141
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +517 -410
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
- package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
- package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/scatter/ScatterPoint.svelte +16 -9
- package/dist/plot/scatter/adaptive-density.d.ts +1 -1
- package/dist/plot/scatter/adaptive-density.js +36 -20
- package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
- package/dist/plot/scatter/binned-scatter-types.js +6 -1
- package/dist/plot/scatter/scatter-data.d.ts +1 -1
- package/dist/plot/scatter/scatter-data.js +8 -7
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
- package/dist/plot/sunburst/Sunburst.svelte +269 -347
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
- package/dist/plot/sunburst/index.d.ts +1 -1
- package/dist/plot/sunburst/index.js +2 -1
- package/dist/plot/sunburst/render.d.ts +2 -2
- package/dist/plot/sunburst/render.js +47 -22
- package/dist/plot/sunburst/sunburst.d.ts +5 -1
- package/dist/plot/sunburst/sunburst.js +10 -3
- package/dist/plot/treemap/Treemap.svelte +1022 -0
- package/dist/plot/treemap/Treemap.svelte.d.ts +90 -0
- package/dist/plot/treemap/index.d.ts +4 -0
- package/dist/plot/treemap/index.js +4 -0
- package/dist/plot/treemap/labels.d.ts +38 -0
- package/dist/plot/treemap/labels.js +80 -0
- package/dist/plot/treemap/treemap.d.ts +28 -0
- package/dist/plot/treemap/treemap.js +102 -0
- package/dist/rdf/RdfPlot.svelte +37 -59
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/calc-rdf.js +63 -52
- package/dist/rdf/index.d.ts +0 -1
- package/dist/sanitize.js +85 -27
- package/dist/scene/SceneCamera.svelte +1 -1
- package/dist/scene/SceneCamera.svelte.d.ts +1 -1
- package/dist/scene/bind-renderer.svelte.js +14 -6
- package/dist/scene/index.d.ts +1 -2
- package/dist/scene/index.js +1 -1
- package/dist/scene/{props.js → props.svelte.js} +15 -1
- package/dist/settings.d.ts +19 -9
- package/dist/settings.js +229 -525
- package/dist/spectral/Bands.svelte +207 -221
- package/dist/spectral/BandsAndDos.svelte +11 -9
- package/dist/spectral/BrillouinBandsDos.svelte +19 -42
- package/dist/spectral/Dos.svelte +92 -93
- package/dist/spectral/helpers.d.ts +3 -3
- package/dist/spectral/helpers.js +65 -140
- package/dist/state.svelte.js +5 -18
- package/dist/structure/Arrow.svelte +5 -18
- package/dist/structure/ArrowInstances.svelte +160 -0
- package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
- package/dist/structure/AtomLegend.svelte +120 -85
- package/dist/structure/AtomLegend.svelte.d.ts +2 -3
- package/dist/structure/Bond.svelte +31 -23
- package/dist/structure/CanvasTooltip.svelte +15 -3
- package/dist/structure/CellSelect.svelte +45 -35
- 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 +636 -504
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureCarousel.svelte +637 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +165 -425
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureExportPane.svelte +17 -21
- package/dist/structure/StructureInfoPane.svelte +128 -133
- package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
- package/dist/structure/StructureScene.svelte +535 -578
- package/dist/structure/StructureScene.svelte.d.ts +6 -2
- package/dist/structure/StructureViewport.svelte +451 -0
- package/dist/structure/StructureViewport.svelte.d.ts +65 -0
- package/dist/structure/atom-label-placement.d.ts +16 -12
- package/dist/structure/atom-label-placement.js +70 -37
- package/dist/structure/atom-properties.d.ts +2 -0
- package/dist/structure/atom-properties.js +105 -38
- package/dist/structure/bond-order-perception.js +3 -8
- package/dist/structure/bonding.d.ts +9 -3
- package/dist/structure/bonding.js +95 -46
- package/dist/structure/edit-history.d.ts +6 -0
- package/dist/structure/edit-history.js +27 -0
- package/dist/structure/export.d.ts +1 -0
- package/dist/structure/export.js +89 -105
- package/dist/structure/format-detect.d.ts +1 -0
- package/dist/structure/format-detect.js +20 -0
- package/dist/structure/geometry.d.ts +9 -0
- package/dist/structure/geometry.js +27 -0
- package/dist/structure/index.d.ts +16 -1
- package/dist/structure/index.js +15 -10
- package/dist/structure/measure.d.ts +1 -1
- package/dist/structure/measure.js +6 -12
- package/dist/structure/parse.d.ts +2 -1
- package/dist/structure/parse.js +272 -313
- package/dist/structure/partial-occupancy.js +10 -5
- package/dist/structure/pbc.js +85 -107
- package/dist/structure/polyhedra.d.ts +0 -1
- package/dist/structure/polyhedra.js +7 -21
- package/dist/structure/prop-groups.d.ts +85 -0
- package/dist/structure/prop-groups.js +18 -0
- package/dist/structure/supercell.d.ts +1 -2
- package/dist/structure/supercell.js +7 -13
- package/dist/symmetry/SymmetryElements.svelte +11 -15
- package/dist/symmetry/SymmetryStats.svelte +55 -71
- package/dist/symmetry/WyckoffTable.svelte +10 -28
- package/dist/symmetry/cell-transform.js +6 -14
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +13 -10
- package/dist/symmetry/spacegroups.js +3 -2
- package/dist/table/HeatmapTable.svelte +1142 -447
- package/dist/table/HeatmapTable.svelte.d.ts +12 -2
- package/dist/table/ToggleMenu.svelte +168 -53
- package/dist/table/ToggleMenu.svelte.d.ts +1 -1
- 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/embedded.d.ts +18 -0
- package/dist/theme/embedded.js +206 -0
- package/dist/theme/index.d.ts +5 -5
- package/dist/theme/index.js +5 -12
- package/dist/theme/themes.mjs +44 -66
- package/dist/tooltip/KCoords.svelte +4 -1
- package/dist/tooltip/TooltipContent.svelte +3 -6
- package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
- package/dist/tooltip/index.d.ts +8 -1
- package/dist/trajectory/Trajectory.svelte +613 -386
- package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
- package/dist/trajectory/TrajectoryError.svelte +12 -74
- package/dist/trajectory/TrajectoryExportPane.svelte +14 -32
- package/dist/trajectory/TrajectoryInfoPane.svelte +127 -164
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
- package/dist/trajectory/extract.js +61 -49
- package/dist/trajectory/format-detect.d.ts +2 -0
- package/dist/trajectory/format-detect.js +17 -13
- package/dist/trajectory/frame-reader.d.ts +2 -0
- package/dist/trajectory/frame-reader.js +35 -32
- package/dist/trajectory/helpers.d.ts +2 -0
- package/dist/trajectory/helpers.js +34 -56
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +5 -8
- package/dist/trajectory/parse/ase.js +1 -1
- 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 +6 -6
- package/dist/trajectory/parse/index.js +25 -33
- package/dist/trajectory/parse/lammps.js +8 -12
- package/dist/trajectory/parse/vasp.js +10 -7
- package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
- package/dist/trajectory/parse/vaspout-electronic.js +207 -0
- package/dist/trajectory/parse/vaspout-h5.d.ts +4 -0
- package/dist/trajectory/parse/vaspout-h5.js +281 -0
- package/dist/trajectory/parse/xyz.js +59 -9
- package/dist/trajectory/plotting.d.ts +8 -9
- package/dist/trajectory/plotting.js +113 -64
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +7 -0
- package/dist/xrd/XrdPlot.svelte +79 -118
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +24 -62
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +69 -95
- package/package.json +94 -34
- package/readme.md +6 -5
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
- package/dist/scene/types.d.ts +0 -26
- package/dist/tooltip/types.d.ts +0 -8
- package/dist/trajectory/constants.d.ts +0 -6
- package/dist/trajectory/constants.js +0 -11
- /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
- /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
|
@@ -6,14 +6,17 @@
|
|
|
6
6
|
import { StatusMessage } from '../feedback'
|
|
7
7
|
import Spinner from '../feedback/Spinner.svelte'
|
|
8
8
|
import Icon from '../Icon.svelte'
|
|
9
|
-
import
|
|
9
|
+
import * as io from '../io'
|
|
10
10
|
import { forward_window_keydown, handle_and_prevent } from '../keyboard'
|
|
11
11
|
import { parse_volumetric_file } from '../isosurface/parse'
|
|
12
12
|
import type { IsosurfaceSettings, VolumetricData } from '../isosurface/types'
|
|
13
13
|
import {
|
|
14
14
|
auto_isosurface_settings,
|
|
15
15
|
DEFAULT_ISOSURFACE_SETTINGS,
|
|
16
|
-
|
|
16
|
+
label_file_volumes,
|
|
17
|
+
lattices_match,
|
|
18
|
+
materialize_layers,
|
|
19
|
+
merge_imported_volumes,
|
|
17
20
|
} from '../isosurface/types'
|
|
18
21
|
import { type FullscreenToggleProp, toggle_fullscreen, ViewerChrome } from '../layout'
|
|
19
22
|
import { sync_fullscreen } from '../layout/fullscreen.svelte'
|
|
@@ -21,6 +24,7 @@
|
|
|
21
24
|
import { create_cart_to_frac, create_frac_to_cart } from '../math'
|
|
22
25
|
import { DEFAULTS } from '../settings'
|
|
23
26
|
import { colors } from '../state.svelte'
|
|
27
|
+
import StructureViewport from './StructureViewport.svelte'
|
|
24
28
|
import type {
|
|
25
29
|
AnyStructure,
|
|
26
30
|
BondEditMode,
|
|
@@ -28,13 +32,16 @@
|
|
|
28
32
|
Crystal,
|
|
29
33
|
MeasureMode,
|
|
30
34
|
StructureBond,
|
|
35
|
+
StructureView,
|
|
31
36
|
} from './'
|
|
32
37
|
import {
|
|
38
|
+
DEFAULT_STRUCTURE_VIEWS,
|
|
33
39
|
default_vector_configs,
|
|
34
40
|
get_element_counts,
|
|
35
41
|
get_pbc_image_sites,
|
|
36
42
|
get_structure_vector_keys,
|
|
37
43
|
} from './'
|
|
44
|
+
import { push_edit, step_history } from './edit-history'
|
|
38
45
|
import { wrap_to_unit_cell } from './pbc'
|
|
39
46
|
import {
|
|
40
47
|
is_valid_supercell_input,
|
|
@@ -45,13 +52,12 @@
|
|
|
45
52
|
import * as symmetry from '../symmetry'
|
|
46
53
|
import { transform_cell } from '../symmetry'
|
|
47
54
|
import type { MoyoDataset } from '@spglib/moyo-wasm'
|
|
48
|
-
import { Canvas } from '@threlte/core'
|
|
49
55
|
import type { ComponentProps, Snippet } from 'svelte'
|
|
50
56
|
import { untrack } from 'svelte'
|
|
51
57
|
import { click_outside, tooltip } from 'svelte-multiselect/attachments'
|
|
52
58
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
53
59
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity'
|
|
54
|
-
import type { Camera,
|
|
60
|
+
import type { Camera, Scene } from 'three'
|
|
55
61
|
import type { AtomColorConfig } from './atom-properties'
|
|
56
62
|
import { get_property_colors } from './atom-properties'
|
|
57
63
|
import AtomLegend from './AtomLegend.svelte'
|
|
@@ -59,7 +65,11 @@
|
|
|
59
65
|
import { BOND_ORDER_OPTIONS, merge_bond_edits, remap_bonds_after_deletion } from './bonding'
|
|
60
66
|
import type { StructureHandlerData } from './index'
|
|
61
67
|
import { MAX_SELECTED_SITES } from './measure'
|
|
62
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
ensure_lattice_params,
|
|
70
|
+
normalize_fractional_coords,
|
|
71
|
+
parse_any_structure,
|
|
72
|
+
} from './parse'
|
|
63
73
|
import StructureControls from './StructureControls.svelte'
|
|
64
74
|
import StructureExportPane from './StructureExportPane.svelte'
|
|
65
75
|
import StructureInfoPane from './StructureInfoPane.svelte'
|
|
@@ -107,6 +117,8 @@
|
|
|
107
117
|
lattice_props: lattice_props_in = $bindable(),
|
|
108
118
|
controls_open = $bindable(false),
|
|
109
119
|
info_pane_open = $bindable(false),
|
|
120
|
+
multi_view = $bindable(false),
|
|
121
|
+
views = DEFAULT_STRUCTURE_VIEWS,
|
|
110
122
|
enable_measure_mode = $bindable(true),
|
|
111
123
|
measure_mode = $bindable<MeasureMode>(`distance`),
|
|
112
124
|
bond_edit_mode = $bindable<BondEditMode>(`add`),
|
|
@@ -183,95 +195,101 @@
|
|
|
183
195
|
on_camera_reset,
|
|
184
196
|
on_bonds_change,
|
|
185
197
|
...rest
|
|
186
|
-
}:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
198
|
+
}: {
|
|
199
|
+
structure?: AnyStructure
|
|
200
|
+
bonds?: StructureBond[]
|
|
201
|
+
scene_props?: ComponentProps<typeof StructureScene>
|
|
202
|
+
// Controls visibility configuration.
|
|
203
|
+
// - 'always': controls always visible
|
|
204
|
+
// - 'hover': controls visible on component hover (default)
|
|
205
|
+
// - 'never': controls never visible
|
|
206
|
+
// - object: { mode, hidden, style } for fine-grained control
|
|
207
|
+
//
|
|
208
|
+
// Control names: 'reset-camera', 'fullscreen', 'measure-mode', 'info-pane', 'export-pane', 'controls'
|
|
209
|
+
show_controls?: ShowControlsProp
|
|
210
|
+
fullscreen?: boolean
|
|
211
|
+
// bindable width of the canvas
|
|
212
|
+
width?: number
|
|
213
|
+
// bindable height of the canvas
|
|
214
|
+
height?: number
|
|
215
|
+
// Canvas wrapper element (for export pane)
|
|
216
|
+
wrapper?: HTMLDivElement
|
|
217
|
+
// PNG export DPI setting
|
|
218
|
+
png_dpi?: number
|
|
219
|
+
reset_text?: string
|
|
220
|
+
hovered?: boolean
|
|
221
|
+
dragover?: boolean
|
|
222
|
+
allow_file_drop?: boolean
|
|
223
|
+
enable_info_pane?: boolean
|
|
224
|
+
enable_measure_mode?: boolean
|
|
225
|
+
measure_mode?: MeasureMode
|
|
226
|
+
bond_edit_mode?: BondEditMode
|
|
227
|
+
bond_edit_order?: BondOrder
|
|
228
|
+
info_pane_open?: boolean
|
|
229
|
+
// When true, split the canvas into a 2x2 grid showing the structure from
|
|
230
|
+
// different angles (Ovito-style). Each pane has independent orbit controls.
|
|
231
|
+
multi_view?: boolean
|
|
232
|
+
// The 4 (or more) view definitions used by multi_view. Defaults to an
|
|
233
|
+
// Ovito-like set: one perspective + three orthographic axis views.
|
|
234
|
+
views?: StructureView[]
|
|
235
|
+
fullscreen_toggle?: FullscreenToggleProp
|
|
236
|
+
bottom_left?: Snippet<[{ structure?: AnyStructure }]>
|
|
237
|
+
top_right_controls?: Snippet // Additional controls to render at the end of the control buttons row
|
|
238
|
+
data_url?: string // URL to load structure from (alternative to providing structure directly)
|
|
239
|
+
// Generic callback for when files are dropped - receives raw content and filename
|
|
240
|
+
on_file_drop?: (content: string | ArrayBuffer, filename: string) => void
|
|
241
|
+
// spinner props (passed to Spinner component)
|
|
242
|
+
spinner_props?: ComponentProps<typeof Spinner>
|
|
243
|
+
loading?: boolean
|
|
244
|
+
error_msg?: string
|
|
245
|
+
// Performance mode: 'quality' (default) or 'speed' for large structures
|
|
246
|
+
performance_mode?: `quality` | `speed`
|
|
247
|
+
// allow parent components to control highlighted/selected site indices
|
|
248
|
+
selected_sites?: number[]
|
|
249
|
+
highlighted_sites?: number[]
|
|
250
|
+
hovered_site_idx?: number | null
|
|
251
|
+
// explicit measured sites for distance/angle overlays
|
|
252
|
+
measured_sites?: number[]
|
|
253
|
+
// expose the displayed structure (with image atoms and/or supercell) for external
|
|
254
|
+
// use. Output-only: the scene renders from an internal raw copy for performance,
|
|
255
|
+
// so external writes to this prop are ignored (overwritten on the next pipeline run)
|
|
256
|
+
displayed_structure?: AnyStructure
|
|
257
|
+
// Track which elements are hidden (bindable across frames in trajectories)
|
|
258
|
+
hidden_elements?: Set<ElementSymbol>
|
|
259
|
+
// Track which property values are hidden (e.g. Wyckoff positions, coordination numbers)
|
|
260
|
+
hidden_prop_vals?: Set<number | string>
|
|
261
|
+
// Per-element radius overrides (absolute values in Angstroms)
|
|
262
|
+
element_radius_overrides?: Partial<Record<ElementSymbol, number>>
|
|
263
|
+
// Per-site radius overrides (absolute values in Angstroms)
|
|
264
|
+
// Accepts Map or SvelteMap for flexibility with external callers
|
|
265
|
+
site_radius_overrides?: Map<number, number> | SvelteMap<number, number>
|
|
266
|
+
// Symmetry analysis data (bindable for external access)
|
|
267
|
+
sym_data?: MoyoDataset | null
|
|
268
|
+
// Symmetry analysis settings (bindable for external control)
|
|
269
|
+
symmetry_settings?: Partial<SymmetrySettings>
|
|
270
|
+
// Map element symbols to different elements (e.g. {'H': 'Na', 'He': 'Cl'})
|
|
271
|
+
element_mapping?: Partial<Record<ElementSymbol, ElementSymbol>>
|
|
272
|
+
// Cell type: original, conventional, or primitive (requires symmetry analysis)
|
|
273
|
+
cell_type?: CellType
|
|
274
|
+
// Volumetric data for isosurface rendering (parsed from CHGCAR or .cube files)
|
|
275
|
+
volumetric_data?: VolumetricData[]
|
|
276
|
+
// Isosurface rendering settings
|
|
277
|
+
isosurface_settings?: IsosurfaceSettings
|
|
278
|
+
// Active volume index when multiple volumes are present
|
|
279
|
+
active_volume_idx?: number
|
|
280
|
+
// structure content as string (alternative to providing structure directly or via data_url)
|
|
281
|
+
structure_string?: string
|
|
282
|
+
// Atom coloring configuration
|
|
283
|
+
atom_color_config?: Partial<AtomColorConfig>
|
|
284
|
+
children?: Snippet<[{ structure?: AnyStructure; fullscreen: boolean }]>
|
|
285
|
+
on_file_load?: EventHandler
|
|
286
|
+
on_error?: EventHandler
|
|
287
|
+
on_fullscreen_change?: EventHandler
|
|
288
|
+
on_camera_move?: EventHandler
|
|
289
|
+
on_camera_reset?: EventHandler
|
|
290
|
+
on_bonds_change?: (bonds: StructureBond[] | undefined) => void
|
|
291
|
+
} & Omit<ComponentProps<typeof StructureControls>, `children` | `onclose`> &
|
|
292
|
+
Omit<HTMLAttributes<HTMLDivElement>, `children`> = $props()
|
|
275
293
|
|
|
276
294
|
// Initialize models from incoming props; mutations come from UI controls; we mirror into local dicts (NOTE only doing shallow merge)
|
|
277
295
|
$effect.pre(() => {
|
|
@@ -283,41 +301,70 @@
|
|
|
283
301
|
}
|
|
284
302
|
})
|
|
285
303
|
|
|
286
|
-
// Load structure from URL when data_url is provided
|
|
304
|
+
// Load structure from URL when data_url is provided. A monotonic load_id ignores stale
|
|
305
|
+
// completions so a newer data_url (or an externally-supplied structure, via the cleanup)
|
|
306
|
+
// can't be clobbered by a slow earlier fetch.
|
|
307
|
+
let data_url_load_id = 0
|
|
308
|
+
let loaded_data_url: string | undefined
|
|
309
|
+
let url_owned_structure: AnyStructure | undefined
|
|
310
|
+
|
|
287
311
|
$effect(() => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
312
|
+
const requested_url = data_url
|
|
313
|
+
const current_structure = structure
|
|
314
|
+
// Host on_file_drop owns the structure; don't treat it as caller-owned cancel.
|
|
315
|
+
const caller_owns_structure = Boolean(
|
|
316
|
+
!on_file_drop && current_structure && current_structure !== url_owned_structure,
|
|
317
|
+
)
|
|
318
|
+
if (!requested_url || caller_owns_structure) {
|
|
319
|
+
loaded_data_url = undefined
|
|
320
|
+
url_owned_structure = undefined
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
if (loaded_data_url === requested_url) return
|
|
324
|
+
|
|
325
|
+
const load_id = ++data_url_load_id
|
|
326
|
+
const is_current = () => load_id === data_url_load_id
|
|
327
|
+
loading = true
|
|
328
|
+
error_msg = undefined
|
|
329
|
+
|
|
330
|
+
io.load_from_url(requested_url, (content, filename) => {
|
|
331
|
+
if (!is_current()) return // stale response
|
|
332
|
+
if (on_file_drop) {
|
|
333
|
+
on_file_drop(content, filename)
|
|
334
|
+
loaded_data_url = requested_url
|
|
335
|
+
} else {
|
|
336
|
+
// Parse structure internally when no handler provided
|
|
337
|
+
try {
|
|
338
|
+
parse_and_emit_file(content, filename)
|
|
339
|
+
url_owned_structure = structure
|
|
340
|
+
loaded_data_url = requested_url
|
|
341
|
+
} catch (error) {
|
|
342
|
+
error_msg = `Failed to parse structure: ${to_error(error).message}`
|
|
343
|
+
on_error?.({ error_msg, filename })
|
|
308
344
|
}
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
.catch((error: Error) => {
|
|
348
|
+
if (!is_current()) return
|
|
349
|
+
console.error(`Failed to load structure from URL:`, error)
|
|
350
|
+
error_msg = `Failed to load structure: ${error.message}`
|
|
351
|
+
on_error?.({ error_msg, filename: io.basename_from_url(requested_url) })
|
|
352
|
+
})
|
|
353
|
+
.finally(() => {
|
|
354
|
+
if (is_current()) loading = false
|
|
309
355
|
})
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
356
|
+
|
|
357
|
+
return () => {
|
|
358
|
+
// invalidate in-flight load on data_url change / structure arrival / unmount
|
|
359
|
+
if (is_current()) {
|
|
360
|
+
data_url_load_id += 1
|
|
361
|
+
loading = false
|
|
362
|
+
}
|
|
317
363
|
}
|
|
318
364
|
})
|
|
319
365
|
|
|
320
|
-
$effect(() => {
|
|
366
|
+
$effect(() => {
|
|
367
|
+
// Parse structure from string when structure_string is provided
|
|
321
368
|
if (!structure_string || data_url) return
|
|
322
369
|
loading = true
|
|
323
370
|
error_msg = undefined
|
|
@@ -331,9 +378,7 @@
|
|
|
331
378
|
throw new Error(`Failed to parse structure from string`)
|
|
332
379
|
}
|
|
333
380
|
} catch (err) {
|
|
334
|
-
error_msg = `Failed to parse structure from string: ${
|
|
335
|
-
to_error(err).message
|
|
336
|
-
}`
|
|
381
|
+
error_msg = `Failed to parse structure from string: ${to_error(err).message}`
|
|
337
382
|
untrack(() => on_error?.({ error_msg, filename: `string` }))
|
|
338
383
|
} finally {
|
|
339
384
|
loading = false
|
|
@@ -389,12 +434,7 @@
|
|
|
389
434
|
|
|
390
435
|
// Compute property-based colors for legend display
|
|
391
436
|
let property_colors = $derived(
|
|
392
|
-
get_property_colors(
|
|
393
|
-
structure,
|
|
394
|
-
atom_color_config,
|
|
395
|
-
scene_props.bonding_strategy,
|
|
396
|
-
sym_data,
|
|
397
|
-
),
|
|
437
|
+
get_property_colors(structure, atom_color_config, scene_props.bonding_strategy, sym_data),
|
|
398
438
|
)
|
|
399
439
|
|
|
400
440
|
let symmetry_run_id = 0
|
|
@@ -426,7 +466,7 @@
|
|
|
426
466
|
} else {
|
|
427
467
|
// Keep previous symmetry data while recomputing so bound consumers
|
|
428
468
|
// (e.g. SymmetryStats inputs) do not unmount and lose focus.
|
|
429
|
-
untrack(() => symmetry_error = undefined)
|
|
469
|
+
untrack(() => (symmetry_error = undefined))
|
|
430
470
|
}
|
|
431
471
|
const run_id = ++symmetry_run_id
|
|
432
472
|
// Destructure symmetry_settings to ensure Svelte tracks changes to symprec and algo
|
|
@@ -436,20 +476,21 @@
|
|
|
436
476
|
// Skip symmetry auto-analysis in unit tests; happy-dom can't fetch WASM assets
|
|
437
477
|
if (typeof process !== `undefined` && process.env?.VITEST) return
|
|
438
478
|
|
|
439
|
-
symmetry
|
|
479
|
+
symmetry
|
|
480
|
+
.ensure_moyo_wasm_ready()
|
|
440
481
|
.then(() =>
|
|
441
482
|
run_id === symmetry_run_id
|
|
442
483
|
? symmetry.analyze_structure_symmetry(current_structure, current_settings)
|
|
443
|
-
: null
|
|
484
|
+
: null,
|
|
444
485
|
)
|
|
445
486
|
.then((data) => {
|
|
446
487
|
if (data && run_id === symmetry_run_id) {
|
|
447
|
-
untrack(() => sym_data = data)
|
|
488
|
+
untrack(() => (sym_data = data))
|
|
448
489
|
}
|
|
449
490
|
})
|
|
450
491
|
.catch((err) => {
|
|
451
492
|
if (run_id === symmetry_run_id) {
|
|
452
|
-
untrack(() => sym_data = null)
|
|
493
|
+
untrack(() => (sym_data = null))
|
|
453
494
|
symmetry_error = `Symmetry analysis failed: ${err?.message || err}`
|
|
454
495
|
console.error(`Symmetry analysis failed:`, err)
|
|
455
496
|
}
|
|
@@ -474,8 +515,7 @@
|
|
|
474
515
|
let last_emitted_bond_signature = $state<string>()
|
|
475
516
|
let bond_edit_snapshot = $state.raw<BondEditSnapshot>()
|
|
476
517
|
let has_bond_edits = $derived(
|
|
477
|
-
added_bonds.length > 0 || removed_bonds.length > 0 ||
|
|
478
|
-
bond_order_overrides.length > 0,
|
|
518
|
+
added_bonds.length > 0 || removed_bonds.length > 0 || bond_order_overrides.length > 0,
|
|
479
519
|
)
|
|
480
520
|
|
|
481
521
|
const clone_bonds = (edit_bonds: StructureBond[]): StructureBond[] =>
|
|
@@ -556,10 +596,10 @@
|
|
|
556
596
|
const current_source_bond_signature = (): string => {
|
|
557
597
|
const raw_signature = bond_signature(current_source_bonds())
|
|
558
598
|
if (raw_signature !== last_emitted_bond_signature) return raw_signature
|
|
559
|
-
return
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
599
|
+
return (
|
|
600
|
+
bond_history_context?.source_bond_signature ??
|
|
601
|
+
(bond_edit_snapshot ? bond_signature(bond_edit_snapshot.bonds) : raw_signature)
|
|
602
|
+
)
|
|
563
603
|
}
|
|
564
604
|
|
|
565
605
|
const current_bond_edit_context = (): BondEditContext => ({
|
|
@@ -646,16 +686,30 @@
|
|
|
646
686
|
|
|
647
687
|
// === Edit-atoms mode state ===
|
|
648
688
|
let dragging_atoms = $state(false)
|
|
649
|
-
|
|
650
|
-
|
|
689
|
+
// History entries snapshot the bindable `bonds` prop alongside the structure:
|
|
690
|
+
// atom deletion remaps `bonds` to the shifted site indices, so restoring the
|
|
691
|
+
// structure alone would leave explicit bonds pointing at wrong sites.
|
|
692
|
+
type EditSnapshot = { structure: AnyStructure; bonds: StructureBond[] | undefined }
|
|
693
|
+
// $state.raw: stacks hold full structure snapshots — deep-proxying them would make
|
|
694
|
+
// every restored structure (and all downstream site reads) go through proxy traps.
|
|
695
|
+
// Mutations happen via reassignment so .length stays reactive in the template.
|
|
696
|
+
let undo_stack = $state.raw<EditSnapshot[]>([])
|
|
697
|
+
let redo_stack = $state.raw<EditSnapshot[]>([])
|
|
651
698
|
const MAX_HISTORY = 20
|
|
652
699
|
// Flag set before internal edits (undo/redo/delete/add/move) to distinguish
|
|
653
700
|
// them from external structure changes (file load, trajectory step, etc.)
|
|
654
701
|
let is_internal_edit = false
|
|
702
|
+
// Claim URL ownership before regular effects so internal edits aren't treated as caller-owned.
|
|
703
|
+
$effect.pre(() => {
|
|
704
|
+
void structure
|
|
705
|
+
if (is_internal_edit && loaded_data_url && loaded_data_url === data_url && structure) {
|
|
706
|
+
url_owned_structure = structure
|
|
707
|
+
}
|
|
708
|
+
})
|
|
709
|
+
|
|
655
710
|
// Add-atom sub-mode state (bound to StructureScene)
|
|
656
711
|
let add_atom_mode = $state(false)
|
|
657
712
|
let add_element = $state<ElementSymbol>(`C` as ElementSymbol)
|
|
658
|
-
let canvas_cursor = $state(`default`)
|
|
659
713
|
let is_measure_selection_mode = $derived(
|
|
660
714
|
measure_mode === `distance` || measure_mode === `angle`,
|
|
661
715
|
)
|
|
@@ -693,28 +747,40 @@
|
|
|
693
747
|
dragging_atoms = false
|
|
694
748
|
}
|
|
695
749
|
|
|
750
|
+
const snapshot_edit_state = (current: AnyStructure): EditSnapshot => ({
|
|
751
|
+
structure: $state.snapshot(current) as AnyStructure,
|
|
752
|
+
bonds: bonds && ($state.snapshot(bonds) as StructureBond[]),
|
|
753
|
+
})
|
|
754
|
+
|
|
696
755
|
function push_undo() {
|
|
697
756
|
if (!structure) return
|
|
698
|
-
|
|
699
|
-
undo_stack
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
757
|
+
;[undo_stack, redo_stack] = push_edit(
|
|
758
|
+
[undo_stack, redo_stack],
|
|
759
|
+
snapshot_edit_state(structure),
|
|
760
|
+
MAX_HISTORY,
|
|
761
|
+
)
|
|
703
762
|
}
|
|
704
763
|
|
|
705
|
-
// Shared undo/redo:
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (!
|
|
764
|
+
// Shared undo/redo: restore the top of that direction's stack, pushing the
|
|
765
|
+
// current state onto the opposite one ($state.raw stacks update by reassignment)
|
|
766
|
+
function apply_history(direction: `undo` | `redo`) {
|
|
767
|
+
// Check the stack before snapshotting: $state.snapshot deep-clones the structure
|
|
768
|
+
if (!structure || (direction === `undo` ? undo_stack : redo_stack).length === 0) return
|
|
769
|
+
const result = step_history(
|
|
770
|
+
[undo_stack, redo_stack],
|
|
771
|
+
direction,
|
|
772
|
+
snapshot_edit_state(structure),
|
|
773
|
+
)
|
|
774
|
+
if (!result) return
|
|
710
775
|
is_internal_edit = true
|
|
711
|
-
|
|
712
|
-
structure = restored
|
|
776
|
+
;[undo_stack, redo_stack] = result.stacks
|
|
777
|
+
structure = result.restored.structure
|
|
778
|
+
bonds = result.restored.bonds
|
|
713
779
|
clear_selection()
|
|
714
780
|
}
|
|
715
781
|
|
|
716
|
-
const undo = () => apply_history(
|
|
717
|
-
const redo = () => apply_history(
|
|
782
|
+
const undo = () => apply_history(`undo`)
|
|
783
|
+
const redo = () => apply_history(`redo`)
|
|
718
784
|
|
|
719
785
|
// Clear undo/redo stacks when structure changes externally (file load, etc.)
|
|
720
786
|
// Internal edits set is_internal_edit=true before modifying structure.
|
|
@@ -757,7 +823,10 @@
|
|
|
757
823
|
$effect(() => {
|
|
758
824
|
void bond_edit_mode
|
|
759
825
|
untrack(() => {
|
|
760
|
-
if (
|
|
826
|
+
if (
|
|
827
|
+
measure_mode === `edit-bonds` &&
|
|
828
|
+
(selected_sites.length > 0 || measured_sites.length > 0)
|
|
829
|
+
) {
|
|
761
830
|
clear_selection()
|
|
762
831
|
}
|
|
763
832
|
})
|
|
@@ -788,16 +857,21 @@
|
|
|
788
857
|
$effect(() => {
|
|
789
858
|
viewer_active = hovered || focused
|
|
790
859
|
})
|
|
791
|
-
|
|
860
|
+
// Keep the gizmo mounted whenever enabled — toggling via `{#if gizmo}` on hover remounts
|
|
861
|
+
// OrbitControls/Gizmo and resets camera rotation. Visibility is CSS-gated by `gizmo-visible`.
|
|
862
|
+
let scene_gizmo = $derived(scene_props.gizmo ?? scene_props.show_gizmo)
|
|
792
863
|
let active_scene_sites = $derived([
|
|
793
864
|
...new SvelteSet([...(scene_props.active_sites ?? []), ...highlighted_sites]),
|
|
794
865
|
])
|
|
795
866
|
|
|
796
867
|
// 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
|
|
868
|
+
// This ensures atoms are rendered inside the unit cell regardless of data source.
|
|
869
|
+
// ensure_lattice_params covers structures that bypass parse_any_structure (direct
|
|
870
|
+
// props, trajectory JSON frames) with matrix-only pymatgen lattices, which would
|
|
871
|
+
// otherwise NaN the camera auto-fit and render blank.
|
|
798
872
|
let normalized_structure = $derived.by(() => {
|
|
799
873
|
if (!structure || !(`lattice` in structure)) return structure
|
|
800
|
-
return normalize_fractional_coords(structure) as AnyStructure
|
|
874
|
+
return ensure_lattice_params(normalize_fractional_coords(structure)) as AnyStructure
|
|
801
875
|
})
|
|
802
876
|
|
|
803
877
|
let structure_with_bonds = $derived.by(() => {
|
|
@@ -812,7 +886,8 @@
|
|
|
812
886
|
// This must happen BEFORE supercell transformation
|
|
813
887
|
let cell_transformed_structure = $derived.by(() => {
|
|
814
888
|
if (
|
|
815
|
-
!structure_with_bonds ||
|
|
889
|
+
!structure_with_bonds ||
|
|
890
|
+
!(`lattice` in structure_with_bonds) ||
|
|
816
891
|
cell_type === `original`
|
|
817
892
|
) {
|
|
818
893
|
return structure_with_bonds
|
|
@@ -829,8 +904,11 @@
|
|
|
829
904
|
}
|
|
830
905
|
})
|
|
831
906
|
|
|
832
|
-
// Create supercell if needed (uses cell_transformed_structure as base)
|
|
833
|
-
|
|
907
|
+
// Create supercell if needed (uses cell_transformed_structure as base).
|
|
908
|
+
// $state.raw: supercells can hold thousands of sites — deep-proxying them makes
|
|
909
|
+
// every downstream read (bonding, PBC images, instancing) traverse proxy traps,
|
|
910
|
+
// measured ~15x slower for compute_bonds on a 704-site supercell.
|
|
911
|
+
let supercell_structure = $state.raw(structure)
|
|
834
912
|
let supercell_loading = $state(false)
|
|
835
913
|
let has_supercell = $derived(
|
|
836
914
|
Boolean(supercell_scaling) && ![``, `1x1x1`, `1`].includes(supercell_scaling),
|
|
@@ -849,35 +927,48 @@
|
|
|
849
927
|
})
|
|
850
928
|
})
|
|
851
929
|
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
if (!
|
|
930
|
+
// Supercell tiling factors for isosurface geometry (tiling itself happens in
|
|
931
|
+
// the Isosurface component so color sampling can use the untiled full-res
|
|
932
|
+
// volumes). Gate on !supercell_loading so tiled surfaces and the supercell
|
|
933
|
+
// structure update in the same frame (large supercells defer via setTimeout).
|
|
934
|
+
let volume_scaling = $derived.by((): Vec3 => {
|
|
935
|
+
if (!has_supercell || supercell_loading) return [1, 1, 1]
|
|
858
936
|
try {
|
|
859
|
-
return
|
|
937
|
+
return parse_supercell_scaling(supercell_scaling)
|
|
860
938
|
} catch {
|
|
861
|
-
return
|
|
939
|
+
return [1, 1, 1]
|
|
862
940
|
}
|
|
863
941
|
})
|
|
864
942
|
|
|
865
943
|
let supercell_timeout: ReturnType<typeof setTimeout> | undefined
|
|
944
|
+
// Compute the supercell, falling back to the unscaled structure on error
|
|
945
|
+
const make_supercell_safe = (base: Crystal): Crystal => {
|
|
946
|
+
try {
|
|
947
|
+
return make_supercell(base, supercell_scaling)
|
|
948
|
+
} catch (error) {
|
|
949
|
+
console.error(`Failed to create supercell:`, error)
|
|
950
|
+
show_toast(`Failed to create supercell: ${to_error(error).message}`)
|
|
951
|
+
return base
|
|
952
|
+
}
|
|
953
|
+
}
|
|
866
954
|
$effect(() => {
|
|
867
955
|
const base_structure = cell_transformed_structure
|
|
868
956
|
clearTimeout(supercell_timeout)
|
|
869
|
-
if (
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
957
|
+
if (
|
|
958
|
+
!base_structure ||
|
|
959
|
+
!(`lattice` in base_structure) ||
|
|
960
|
+
!has_supercell ||
|
|
961
|
+
!is_valid_supercell_input(supercell_scaling)
|
|
962
|
+
) {
|
|
873
963
|
supercell_structure = base_structure
|
|
874
964
|
supercell_loading = false
|
|
875
965
|
} else {
|
|
876
966
|
// For large supercells, show loading state and use async generation
|
|
877
967
|
const sites_count = base_structure.sites?.length || 0
|
|
878
|
-
|
|
879
|
-
const scaling_mult =
|
|
880
|
-
(
|
|
968
|
+
// lenient parse just for a size estimate (invalid factors count as 1)
|
|
969
|
+
const scaling_mult = supercell_scaling
|
|
970
|
+
.split(/[x×]/)
|
|
971
|
+
.reduce((product, factor) => product * (Number(factor) || 1), 1)
|
|
881
972
|
const estimated_sites = sites_count * scaling_mult
|
|
882
973
|
|
|
883
974
|
// Show spinner for supercells with >1000 estimated sites or scaling >8
|
|
@@ -887,27 +978,11 @@
|
|
|
887
978
|
supercell_loading = true
|
|
888
979
|
// Use setTimeout to allow UI to update before heavy computation
|
|
889
980
|
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
|
-
}
|
|
981
|
+
supercell_structure = make_supercell_safe(base_structure as Crystal)
|
|
982
|
+
supercell_loading = false
|
|
903
983
|
}, 10)
|
|
904
984
|
} else {
|
|
905
|
-
|
|
906
|
-
supercell_structure = make_supercell(
|
|
907
|
-
base_structure as Crystal,
|
|
908
|
-
supercell_scaling,
|
|
909
|
-
)
|
|
910
|
-
}
|
|
985
|
+
supercell_structure = make_supercell_safe(base_structure as Crystal)
|
|
911
986
|
supercell_loading = false
|
|
912
987
|
}
|
|
913
988
|
}
|
|
@@ -934,9 +1009,12 @@
|
|
|
934
1009
|
})
|
|
935
1010
|
})
|
|
936
1011
|
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
1012
|
+
// Element-map + PBC image atoms. Skipped during drags (doubled site count drops frames);
|
|
1013
|
+
// images return on release.
|
|
1014
|
+
// The render path reads the internal $state.raw copy, NOT the bindable prop:
|
|
1015
|
+
// if a consumer binds displayed_structure to a deep $state, reading the prop
|
|
1016
|
+
// back would re-proxy every site and slow all scene computations ~10x.
|
|
1017
|
+
let internal_displayed_structure = $state.raw<AnyStructure | undefined>(undefined)
|
|
940
1018
|
$effect(() => {
|
|
941
1019
|
let struct = supercell_structure
|
|
942
1020
|
if (struct && element_mapping && Object.keys(element_mapping).length > 0) {
|
|
@@ -953,124 +1031,92 @@
|
|
|
953
1031
|
})),
|
|
954
1032
|
}
|
|
955
1033
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1034
|
+
// Local var, NOT a read-back of the states written below: reading our own
|
|
1035
|
+
// fresh-object write inside this effect would self-retrigger it forever.
|
|
1036
|
+
const next =
|
|
1037
|
+
!dragging_atoms && show_image_atoms && struct && `lattice` in struct && struct.lattice
|
|
959
1038
|
? get_pbc_image_sites(struct)
|
|
960
1039
|
: struct
|
|
1040
|
+
internal_displayed_structure = next
|
|
1041
|
+
displayed_structure = next
|
|
961
1042
|
})
|
|
962
1043
|
|
|
963
|
-
//
|
|
964
|
-
|
|
965
|
-
let camera_is_moving = $state(false)
|
|
1044
|
+
// scene + camera of the primary pane, bound out for the export pane. All other camera
|
|
1045
|
+
// handling (move tracking, reset, re-framing) lives in StructureViewport.
|
|
966
1046
|
let scene = $state<Scene | undefined>(undefined)
|
|
967
1047
|
let camera = $state<Camera | undefined>(undefined)
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
let
|
|
1048
|
+
|
|
1049
|
+
// Multi-side view state: index of the pane the pointer is over (gets edit interactions),
|
|
1050
|
+
// a token bumped to reset every pane, and the set of panes whose camera has moved (so
|
|
1051
|
+
// the reset button stays visible until every moved pane is reset). Pane 0 = primary.
|
|
1052
|
+
let active_pane_idx = $state(0)
|
|
1053
|
+
let reset_token = $state(0)
|
|
1054
|
+
// SvelteSet is already reactive; do NOT wrap in $state (double-proxying breaks it)
|
|
1055
|
+
const moved_panes = new SvelteSet<number>()
|
|
1056
|
+
let any_camera_moved = $derived(moved_panes.size > 0)
|
|
1057
|
+
|
|
1058
|
+
// Inputs shared by every StructureViewport (single + all multi-view panes). Camera,
|
|
1059
|
+
// selection bindings, and per-pane chrome differ and stay on each snippet below.
|
|
1060
|
+
let shared_viewport_props = $derived({
|
|
1061
|
+
structure: internal_displayed_structure,
|
|
1062
|
+
base_structure: cell_transformed_structure,
|
|
1063
|
+
scene_props,
|
|
1064
|
+
gizmo: scene_gizmo,
|
|
1065
|
+
lattice_props,
|
|
1066
|
+
volumetric_data,
|
|
1067
|
+
volume_scaling,
|
|
1068
|
+
active_volume_idx,
|
|
1069
|
+
isosurface_settings,
|
|
1070
|
+
bond_edits_enabled,
|
|
1071
|
+
bond_edit_order,
|
|
1072
|
+
measure_mode,
|
|
1073
|
+
atom_color_config,
|
|
1074
|
+
sym_data,
|
|
1075
|
+
active_sites: active_scene_sites,
|
|
1076
|
+
on_sites_moved: handle_sites_moved,
|
|
1077
|
+
on_operation_start: push_undo,
|
|
1078
|
+
on_bond_edit_start: push_bond_undo,
|
|
1079
|
+
on_add_atom: handle_add_atom,
|
|
1080
|
+
})
|
|
973
1081
|
|
|
974
1082
|
// Mutual exclusion: opening one pane closes others
|
|
975
1083
|
$effect(() => {
|
|
976
1084
|
if (info_pane_open) {
|
|
977
|
-
untrack(() => [controls_open, export_pane_open] = [false, false])
|
|
1085
|
+
untrack(() => ([controls_open, export_pane_open] = [false, false]))
|
|
978
1086
|
}
|
|
979
1087
|
})
|
|
980
1088
|
$effect(() => {
|
|
981
1089
|
if (controls_open) {
|
|
982
|
-
untrack(() => [info_pane_open, export_pane_open] = [false, false])
|
|
1090
|
+
untrack(() => ([info_pane_open, export_pane_open] = [false, false]))
|
|
983
1091
|
}
|
|
984
1092
|
})
|
|
985
1093
|
$effect(() => {
|
|
986
1094
|
if (export_pane_open) {
|
|
987
|
-
untrack(() => [info_pane_open, controls_open] = [false, false])
|
|
1095
|
+
untrack(() => ([info_pane_open, controls_open] = [false, false]))
|
|
988
1096
|
}
|
|
989
1097
|
})
|
|
990
1098
|
|
|
991
|
-
// Reset tracking when structure changes
|
|
1099
|
+
// Reset moved-pane tracking when structure changes
|
|
992
1100
|
$effect(() => {
|
|
993
|
-
|
|
1101
|
+
// untrack: clearing must not add moved_panes as a dependency, else a pane move
|
|
1102
|
+
// (which adds to moved_panes) would immediately re-run this and clear it again.
|
|
1103
|
+
if (structure) untrack(() => moved_panes.clear())
|
|
994
1104
|
})
|
|
995
1105
|
|
|
996
1106
|
// Clear stale camera target and position so StructureScene uses the new
|
|
997
1107
|
// structure's rotation_target (unit cell center) and auto-positions the camera.
|
|
998
1108
|
function clear_camera_state() {
|
|
1109
|
+
// Reset to a fresh [0,0,0] so the primary viewport re-frames the new structure.
|
|
1110
|
+
// Side panes reset their local camera state in StructureViewport's structure effect.
|
|
999
1111
|
scene_props.camera_target = undefined
|
|
1000
1112
|
scene_props.camera_position = [0, 0, 0]
|
|
1001
1113
|
}
|
|
1002
1114
|
|
|
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 })
|
|
1115
|
+
// Reset every pane's camera (each StructureViewport resets on a reset_token bump and,
|
|
1116
|
+
// for the primary, emits on_camera_reset).
|
|
1117
|
+
function reset_all_cameras() {
|
|
1118
|
+
reset_token += 1
|
|
1119
|
+
moved_panes.clear()
|
|
1074
1120
|
}
|
|
1075
1121
|
|
|
1076
1122
|
const emit_file_load_event = (
|
|
@@ -1081,27 +1127,69 @@
|
|
|
1081
1127
|
on_file_load?.({
|
|
1082
1128
|
structure: loaded_structure,
|
|
1083
1129
|
filename,
|
|
1084
|
-
file_size: typeof content === `string`
|
|
1085
|
-
? new Blob([content]).size
|
|
1086
|
-
: content.byteLength,
|
|
1130
|
+
file_size: typeof content === `string` ? new Blob([content]).size : content.byteLength,
|
|
1087
1131
|
total_atoms: loaded_structure.sites?.length || 0,
|
|
1088
1132
|
})
|
|
1089
1133
|
|
|
1134
|
+
// The currently loaded structure's lattice matrix (undefined for molecules/none)
|
|
1135
|
+
const current_lattice_matrix = () =>
|
|
1136
|
+
structure && `lattice` in structure ? structure.lattice.matrix : undefined
|
|
1137
|
+
const shares_current_lattice = (matrix: Parameters<typeof lattices_match>[1]): boolean =>
|
|
1138
|
+
lattices_match(current_lattice_matrix(), matrix)
|
|
1139
|
+
|
|
1090
1140
|
// Try to parse content as a volumetric file, setting both structure and volumetric data.
|
|
1091
1141
|
// Delegates format detection entirely to parse_volumetric_file (filename + content sniffing).
|
|
1142
|
+
// When the file describes the same cell as already-loaded volumes, its volumes are
|
|
1143
|
+
// APPENDED (or replaced in place on re-import of the same source file) so multiple
|
|
1144
|
+
// fields — e.g. density + ESP — coexist and can cross-color each other's isosurfaces.
|
|
1145
|
+
// A file with a different lattice replaces the current structure and volumes.
|
|
1092
1146
|
// 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 {
|
|
1147
|
+
function try_parse_volumetric(text_content: string, filename: string): AnyStructure | null {
|
|
1097
1148
|
const vol_result = parse_volumetric_file(text_content, filename)
|
|
1098
1149
|
if (!vol_result) return null
|
|
1150
|
+
|
|
1151
|
+
const incoming = label_file_volumes(vol_result.volumes, filename)
|
|
1152
|
+
const same_cell = shares_current_lattice(vol_result.structure.lattice?.matrix)
|
|
1153
|
+
const added_toast = (count: number) =>
|
|
1154
|
+
`Added ${count} volume${count > 1 ? `s` : ``} from ${filename}`
|
|
1155
|
+
|
|
1156
|
+
if (same_cell && structure) {
|
|
1157
|
+
// Same cell: keep the structure and camera, only update volumes
|
|
1158
|
+
if (volumetric_data?.length) {
|
|
1159
|
+
// Materialize the implicit single surface into explicit layers so existing
|
|
1160
|
+
// surfaces survive the transition to multi-volume mode
|
|
1161
|
+
const merged = merge_imported_volumes(
|
|
1162
|
+
volumetric_data,
|
|
1163
|
+
materialize_layers(isosurface_settings, active_volume_idx),
|
|
1164
|
+
incoming,
|
|
1165
|
+
active_volume_idx,
|
|
1166
|
+
)
|
|
1167
|
+
volumetric_data = merged.volumes
|
|
1168
|
+
isosurface_settings = { ...isosurface_settings, layers: merged.layers }
|
|
1169
|
+
active_volume_idx = merged.first_touched_idx
|
|
1170
|
+
show_toast(
|
|
1171
|
+
merged.n_added > 0
|
|
1172
|
+
? added_toast(merged.n_added)
|
|
1173
|
+
: `Reloaded volumes from ${filename}`,
|
|
1174
|
+
)
|
|
1175
|
+
} else if (incoming[0]) {
|
|
1176
|
+
// First volumetric file for this structure
|
|
1177
|
+
volumetric_data = incoming
|
|
1178
|
+
isosurface_settings = auto_isosurface_settings(incoming[0].data_range)
|
|
1179
|
+
active_volume_idx = 0
|
|
1180
|
+
show_toast(added_toast(incoming.length))
|
|
1181
|
+
}
|
|
1182
|
+
return structure
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// Replace: new system (or nothing loaded yet)
|
|
1186
|
+
clear_camera_state()
|
|
1099
1187
|
// parse_volumetric_file extracts structure from file header;
|
|
1100
1188
|
// parsers set pbc so the lattice conforms to Crystal's LatticeType
|
|
1101
1189
|
structure = vol_result.structure as AnyStructure
|
|
1102
|
-
volumetric_data =
|
|
1190
|
+
volumetric_data = incoming
|
|
1103
1191
|
// Auto-compute reasonable isosurface settings from data range
|
|
1104
|
-
const vol =
|
|
1192
|
+
const vol = incoming[0]
|
|
1105
1193
|
if (vol) {
|
|
1106
1194
|
isosurface_settings = auto_isosurface_settings(vol.data_range)
|
|
1107
1195
|
active_volume_idx = 0
|
|
@@ -1112,33 +1200,36 @@
|
|
|
1112
1200
|
// Parse file content, trying volumetric format first then falling back to plain structure.
|
|
1113
1201
|
// Returns the parsed structure on success, throws on failure.
|
|
1114
1202
|
function parse_file_content(text_content: string, filename: string): AnyStructure {
|
|
1115
|
-
clear_camera_state()
|
|
1116
1203
|
const vol_struct = try_parse_volumetric(text_content, filename)
|
|
1117
1204
|
if (vol_struct) return vol_struct
|
|
1118
|
-
// Clear stale volumetric data when loading a non-volumetric file
|
|
1119
|
-
volumetric_data = []
|
|
1120
1205
|
const parsed = parse_any_structure(text_content, filename)
|
|
1121
1206
|
if (!parsed) throw new Error(`Failed to parse structure from ${filename}`)
|
|
1207
|
+
// Keep loaded volumes and camera when the new structure describes the same
|
|
1208
|
+
// cell (e.g. a mixed batch drop of CHGCAR + POSCAR, in either order);
|
|
1209
|
+
// clear both for a genuinely new system
|
|
1210
|
+
const same_cell = shares_current_lattice(
|
|
1211
|
+
`lattice` in parsed ? parsed.lattice?.matrix : undefined,
|
|
1212
|
+
)
|
|
1213
|
+
if (!same_cell) {
|
|
1214
|
+
clear_camera_state()
|
|
1215
|
+
volumetric_data = []
|
|
1216
|
+
}
|
|
1122
1217
|
structure = parsed
|
|
1123
1218
|
return parsed
|
|
1124
1219
|
}
|
|
1125
1220
|
|
|
1126
|
-
|
|
1221
|
+
function parse_and_emit_file(content: string | ArrayBuffer, filename: string): void {
|
|
1222
|
+
const text = content instanceof ArrayBuffer ? new TextDecoder().decode(content) : content
|
|
1223
|
+
emit_file_load_event(parse_file_content(text, filename), filename, content)
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const handle_file_drop = io.create_file_drop_handler({
|
|
1127
1227
|
allow: () => allow_file_drop,
|
|
1228
|
+
// Parse errors propagate so multi-file batches aggregate all failures into
|
|
1229
|
+
// one message instead of the last error overwriting earlier ones
|
|
1128
1230
|
on_drop: (content, filename) => {
|
|
1129
1231
|
if (on_file_drop) return on_file_drop(content, filename)
|
|
1130
|
-
|
|
1131
|
-
const text_content = content instanceof ArrayBuffer
|
|
1132
|
-
? new TextDecoder().decode(content)
|
|
1133
|
-
: content
|
|
1134
|
-
const parsed = parse_file_content(text_content, filename)
|
|
1135
|
-
emit_file_load_event(parsed, filename, content)
|
|
1136
|
-
} catch (err) {
|
|
1137
|
-
error_msg = `Failed to parse structure: ${
|
|
1138
|
-
to_error(err).message
|
|
1139
|
-
}`
|
|
1140
|
-
on_error?.({ error_msg, filename })
|
|
1141
|
-
}
|
|
1232
|
+
parse_and_emit_file(content, filename)
|
|
1142
1233
|
},
|
|
1143
1234
|
on_error: (msg) => {
|
|
1144
1235
|
error_msg = msg
|
|
@@ -1263,8 +1354,10 @@
|
|
|
1263
1354
|
}
|
|
1264
1355
|
// Duplicate selected atoms at a small offset
|
|
1265
1356
|
if (
|
|
1266
|
-
key === `d` &&
|
|
1267
|
-
|
|
1357
|
+
key === `d` &&
|
|
1358
|
+
(event.ctrlKey || event.metaKey) &&
|
|
1359
|
+
selected_sites.length > 0 &&
|
|
1360
|
+
structure?.sites
|
|
1268
1361
|
) {
|
|
1269
1362
|
is_internal_edit = true
|
|
1270
1363
|
push_undo()
|
|
@@ -1273,11 +1366,7 @@
|
|
|
1273
1366
|
const new_sites = structure.sites
|
|
1274
1367
|
.filter((_, idx) => orig_indices.has(idx))
|
|
1275
1368
|
.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
|
-
]
|
|
1369
|
+
const new_xyz: Vec3 = [site.xyz[0] + 0.5, site.xyz[1] + 0.5, site.xyz[2] + 0.5]
|
|
1281
1370
|
return {
|
|
1282
1371
|
...site,
|
|
1283
1372
|
xyz: new_xyz,
|
|
@@ -1293,9 +1382,7 @@
|
|
|
1293
1382
|
// Select the newly duplicated atoms
|
|
1294
1383
|
selected_sites = new_sites.map((_, idx) => base_idx + idx)
|
|
1295
1384
|
measured_sites = [...selected_sites]
|
|
1296
|
-
show_toast(
|
|
1297
|
-
`Duplicated ${new_sites.length} site${new_sites.length > 1 ? `s` : ``}`,
|
|
1298
|
-
)
|
|
1385
|
+
show_toast(`Duplicated ${new_sites.length} site${new_sites.length > 1 ? `s` : ``}`)
|
|
1299
1386
|
return true
|
|
1300
1387
|
}
|
|
1301
1388
|
|
|
@@ -1320,6 +1407,9 @@
|
|
|
1320
1407
|
} else if (event.key === `i` && has_modifier && enable_info_pane) {
|
|
1321
1408
|
info_pane_open = !info_pane_open
|
|
1322
1409
|
return true
|
|
1410
|
+
} else if (event.key === `g` && has_modifier && controls_config.visible(`multi-view`)) {
|
|
1411
|
+
multi_view = !multi_view
|
|
1412
|
+
return true
|
|
1323
1413
|
} else if (event.key === `Escape`) {
|
|
1324
1414
|
// Prioritize closing panes, then exit edit modes, then exit fullscreen
|
|
1325
1415
|
if (info_pane_open) info_pane_open = false
|
|
@@ -1351,7 +1441,7 @@
|
|
|
1351
1441
|
): SvelteSet<number> {
|
|
1352
1442
|
const result = new SvelteSet<number>()
|
|
1353
1443
|
for (const scene_idx of scene_indices) {
|
|
1354
|
-
const displayed_site =
|
|
1444
|
+
const displayed_site = internal_displayed_structure?.sites?.[scene_idx]
|
|
1355
1445
|
if (!displayed_site) continue
|
|
1356
1446
|
if (skip_image_atoms && displayed_site.properties?.orig_site_idx != null) {
|
|
1357
1447
|
continue
|
|
@@ -1389,10 +1479,9 @@
|
|
|
1389
1479
|
const orig_indices = scene_to_structure_indices(scene_indices)
|
|
1390
1480
|
// For crystals, wrap to [0,1) inline so normalize_fractional_coords fast-paths.
|
|
1391
1481
|
// 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
|
|
1482
|
+
const lattice = `lattice` in structure ? (structure as Crystal).lattice.matrix : null
|
|
1483
|
+
// get_cart_to_frac guards matrix_inverse_3x3, which throws on singular lattices
|
|
1484
|
+
const cart_to_frac = get_cart_to_frac()
|
|
1396
1485
|
const frac_to_cart = lattice ? create_frac_to_cart(lattice) : null
|
|
1397
1486
|
structure = {
|
|
1398
1487
|
...structure,
|
|
@@ -1434,9 +1523,7 @@
|
|
|
1434
1523
|
change_element_mode = false
|
|
1435
1524
|
change_element_value = ``
|
|
1436
1525
|
show_toast(
|
|
1437
|
-
`Changed ${orig_indices.size} site${
|
|
1438
|
-
orig_indices.size > 1 ? `s` : ``
|
|
1439
|
-
} to ${elem}`,
|
|
1526
|
+
`Changed ${orig_indices.size} site${orig_indices.size > 1 ? `s` : ``} to ${elem}`,
|
|
1440
1527
|
)
|
|
1441
1528
|
}
|
|
1442
1529
|
|
|
@@ -1451,29 +1538,30 @@
|
|
|
1451
1538
|
push_undo()
|
|
1452
1539
|
structure = {
|
|
1453
1540
|
...structure,
|
|
1454
|
-
sites: [
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1541
|
+
sites: [
|
|
1542
|
+
...structure.sites,
|
|
1543
|
+
{
|
|
1544
|
+
species: [{ element: elem, occu: 1, oxidation_state: 0 }],
|
|
1545
|
+
xyz,
|
|
1546
|
+
abc: get_cart_to_frac()?.(xyz) ?? xyz,
|
|
1547
|
+
label: elem,
|
|
1548
|
+
properties: {},
|
|
1549
|
+
},
|
|
1550
|
+
],
|
|
1461
1551
|
}
|
|
1462
1552
|
show_toast(`Added ${elem} at (${xyz.map((coord) => coord.toFixed(2)).join(`, `)})`)
|
|
1463
1553
|
}
|
|
1464
1554
|
|
|
1465
1555
|
// Only set background override when background_color is explicitly provided
|
|
1466
1556
|
$effect(() => {
|
|
1467
|
-
if (
|
|
1557
|
+
if (!wrapper) return
|
|
1558
|
+
if (background_color) {
|
|
1468
1559
|
// Convert opacity (0-1) to hex alpha value (00-FF)
|
|
1469
1560
|
const alpha_hex = Math.round(background_opacity * 255)
|
|
1470
1561
|
.toString(16)
|
|
1471
1562
|
.padStart(2, `0`)
|
|
1472
|
-
wrapper.style.setProperty(
|
|
1473
|
-
|
|
1474
|
-
`${background_color}${alpha_hex}`,
|
|
1475
|
-
)
|
|
1476
|
-
} else if (typeof window !== `undefined` && wrapper) {
|
|
1563
|
+
wrapper.style.setProperty(`--struct-bg-override`, `${background_color}${alpha_hex}`)
|
|
1564
|
+
} else {
|
|
1477
1565
|
// Remove override to use theme system
|
|
1478
1566
|
wrapper.style.removeProperty(`--struct-bg-override`)
|
|
1479
1567
|
}
|
|
@@ -1497,10 +1585,10 @@
|
|
|
1497
1585
|
<div
|
|
1498
1586
|
class:dragover
|
|
1499
1587
|
class:active={info_pane_open || controls_open || export_pane_open}
|
|
1500
|
-
class:gizmo-visible={Boolean(scene_gizmo)}
|
|
1588
|
+
class:gizmo-visible={viewer_active && Boolean(scene_gizmo)}
|
|
1589
|
+
class:multi-view={multi_view}
|
|
1501
1590
|
role="application"
|
|
1502
1591
|
tabindex="0"
|
|
1503
|
-
style:--canvas-cursor={canvas_cursor}
|
|
1504
1592
|
aria-label="Structure viewer"
|
|
1505
1593
|
bind:this={wrapper}
|
|
1506
1594
|
bind:clientWidth={width}
|
|
@@ -1513,22 +1601,14 @@
|
|
|
1513
1601
|
focused = false
|
|
1514
1602
|
}
|
|
1515
1603
|
}}
|
|
1516
|
-
ondblclick={(event) => {
|
|
1517
|
-
const target = event.target
|
|
1518
|
-
if (!(target instanceof HTMLElement)) return
|
|
1519
|
-
// Don't reset if double-click was on UI controls/panes/legend
|
|
1520
|
-
if (
|
|
1521
|
-
[`.control-buttons`, `.structure-legend`, `.atom-legend`, `.info-pane`, `.export-pane`, `.controls-pane`].some((selector) => target.closest(selector))
|
|
1522
|
-
|| target.tagName === `BUTTON` || target.tagName === `INPUT` || target.tagName === `SELECT`
|
|
1523
|
-
) return
|
|
1524
|
-
// Reset camera for double-clicks on the 3D scene
|
|
1525
|
-
reset_camera()
|
|
1526
|
-
}}
|
|
1527
1604
|
ondrop={handle_file_drop}
|
|
1528
|
-
{...drag_over_handlers({
|
|
1605
|
+
{...io.drag_over_handlers({
|
|
1606
|
+
allow: () => allow_file_drop,
|
|
1607
|
+
set_dragover: (over) => (dragover = over),
|
|
1608
|
+
})}
|
|
1529
1609
|
onkeydown={handle_and_prevent(handle_keydown)}
|
|
1530
1610
|
{...rest}
|
|
1531
|
-
class=
|
|
1611
|
+
class={[`structure`, rest.class]}
|
|
1532
1612
|
>
|
|
1533
1613
|
{@render children?.({ structure, fullscreen })}
|
|
1534
1614
|
{#if loading}
|
|
@@ -1541,8 +1621,13 @@
|
|
|
1541
1621
|
<StatusMessage bind:message={error_msg} type="error" dismissible />
|
|
1542
1622
|
{:else if (structure?.sites?.length ?? 0) > 0}
|
|
1543
1623
|
{#snippet reset_camera_btn()}
|
|
1544
|
-
{#if
|
|
1545
|
-
<button
|
|
1624
|
+
{#if any_camera_moved && controls_config.visible(`reset-camera`)}
|
|
1625
|
+
<button
|
|
1626
|
+
class="reset-camera"
|
|
1627
|
+
onclick={reset_all_cameras}
|
|
1628
|
+
title={reset_text}
|
|
1629
|
+
aria-label={reset_text}
|
|
1630
|
+
>
|
|
1546
1631
|
<!-- Target/Focus icon for reset camera -->
|
|
1547
1632
|
<Icon icon="Reset" />
|
|
1548
1633
|
</button>
|
|
@@ -1555,20 +1640,36 @@
|
|
|
1555
1640
|
fullscreen_btn_style="padding: 0 3px"
|
|
1556
1641
|
{wrapper}
|
|
1557
1642
|
before={reset_camera_btn}
|
|
1558
|
-
style="--viewer-buttons-gap: 4pt; --viewer-buttons-btn-padding: 1px 6px; --viewer-buttons-align: stretch"
|
|
1643
|
+
style="--viewer-buttons-gap: 4pt; --viewer-buttons-btn-padding: 1px 6px; --viewer-buttons-align: stretch; --viewer-buttons-hover-bg: transparent; --viewer-buttons-hover-color: light-dark(#000, #fff)"
|
|
1559
1644
|
>
|
|
1645
|
+
{#if controls_config.visible(`multi-view`)}
|
|
1646
|
+
<button
|
|
1647
|
+
class="multi-view-toggle"
|
|
1648
|
+
class:active={multi_view}
|
|
1649
|
+
onclick={() => (multi_view = !multi_view)}
|
|
1650
|
+
title="Toggle multi-side view 2×2 grid (Cmd/Ctrl+G)"
|
|
1651
|
+
aria-label="Toggle multi-side view"
|
|
1652
|
+
aria-pressed={multi_view}
|
|
1653
|
+
{@attach tooltip()}
|
|
1654
|
+
>
|
|
1655
|
+
<Icon icon="Grid2x2" />
|
|
1656
|
+
</button>
|
|
1657
|
+
{/if}
|
|
1658
|
+
|
|
1560
1659
|
{#if enable_measure_mode && controls_config.visible(`measure-mode`)}
|
|
1561
1660
|
<div
|
|
1562
1661
|
class="measure-mode-dropdown"
|
|
1563
|
-
{@attach click_outside({ callback: () => measure_menu_open = false })}
|
|
1662
|
+
{@attach click_outside({ callback: () => (measure_menu_open = false) })}
|
|
1564
1663
|
>
|
|
1565
1664
|
<button
|
|
1566
1665
|
onclick={() => (measure_menu_open = !measure_menu_open)}
|
|
1567
1666
|
title="Measure / Edit"
|
|
1667
|
+
aria-label="Measure / Edit"
|
|
1568
1668
|
class="view-mode-button"
|
|
1569
1669
|
class:active={measure_menu_open}
|
|
1570
1670
|
aria-expanded={measure_menu_open}
|
|
1571
1671
|
style="transform: scale(1.2)"
|
|
1672
|
+
{@attach tooltip()}
|
|
1572
1673
|
>
|
|
1573
1674
|
{#if show_measure_selection_limit}
|
|
1574
1675
|
<span class="selection-limit-text">
|
|
@@ -1576,18 +1677,17 @@
|
|
|
1576
1677
|
</span>
|
|
1577
1678
|
{:else}
|
|
1578
1679
|
<Icon
|
|
1579
|
-
icon={(
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1680
|
+
icon={(
|
|
1681
|
+
{
|
|
1682
|
+
distance: `Ruler`,
|
|
1683
|
+
angle: `Angle`,
|
|
1684
|
+
'edit-bonds': `Link`,
|
|
1685
|
+
'edit-atoms': `Edit`,
|
|
1686
|
+
} as const
|
|
1687
|
+
)[measure_mode]}
|
|
1585
1688
|
/>
|
|
1586
1689
|
{/if}
|
|
1587
|
-
<Icon
|
|
1588
|
-
icon="Arrow{measure_menu_open ? `Up` : `Down`}"
|
|
1589
|
-
style="margin-left: -2px"
|
|
1590
|
-
/>
|
|
1690
|
+
<Icon icon="Arrow{measure_menu_open ? `Up` : `Down`}" style="margin-left: -2px" />
|
|
1591
1691
|
</button>
|
|
1592
1692
|
{#if show_selection_reset}
|
|
1593
1693
|
<button
|
|
@@ -1603,12 +1703,7 @@
|
|
|
1603
1703
|
{/if}
|
|
1604
1704
|
{#if measure_menu_open}
|
|
1605
1705
|
<div class="view-mode-dropdown">
|
|
1606
|
-
{#each [
|
|
1607
|
-
{ mode: `distance`, icon: `Ruler`, label: `Distance`, scale: 1.1 },
|
|
1608
|
-
{ mode: `angle`, icon: `Angle`, label: `Angle`, scale: 1.3 },
|
|
1609
|
-
{ mode: `edit-atoms`, icon: `Edit`, label: `Edit Atoms`, scale: 1.0 },
|
|
1610
|
-
{ mode: `edit-bonds`, icon: `Link`, label: `Edit Bonds`, scale: 1.0 },
|
|
1611
|
-
] as const as { mode, icon, label, scale } (mode)}
|
|
1706
|
+
{#each [{ mode: `distance`, icon: `Ruler`, label: `Distance`, scale: 1.1 }, { mode: `angle`, icon: `Angle`, label: `Angle`, scale: 1.3 }, { mode: `edit-atoms`, icon: `Edit`, label: `Edit Atoms`, scale: 1.0 }, { mode: `edit-bonds`, icon: `Link`, label: `Edit Bonds`, scale: 1.0 }] as const as { mode, icon, label, scale } (mode)}
|
|
1612
1707
|
<button
|
|
1613
1708
|
class="view-mode-option"
|
|
1614
1709
|
class:selected={measure_mode === mode}
|
|
@@ -1629,36 +1724,43 @@
|
|
|
1629
1724
|
{/if}
|
|
1630
1725
|
</div>
|
|
1631
1726
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1727
|
+
{#snippet undo_redo_snippet(
|
|
1728
|
+
buttons: {
|
|
1729
|
+
icon: `Undo` | `Redo`
|
|
1730
|
+
title: string
|
|
1731
|
+
stack: unknown[]
|
|
1732
|
+
action: () => void
|
|
1733
|
+
}[],
|
|
1734
|
+
)}
|
|
1634
1735
|
<div class="undo-redo-container">
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
aria-label="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1651
|
-
disabled={redo_stack.length === 0}
|
|
1652
|
-
onclick={redo}
|
|
1653
|
-
title="Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1654
|
-
class="undo-redo-btn"
|
|
1655
|
-
>
|
|
1656
|
-
<Icon icon="Redo" />
|
|
1657
|
-
{#if redo_stack.length > 0}
|
|
1658
|
-
<span class="history-count">{redo_stack.length}</span>
|
|
1659
|
-
{/if}
|
|
1660
|
-
</button>
|
|
1736
|
+
{#each buttons as { icon, title, stack, action } (icon)}
|
|
1737
|
+
<button
|
|
1738
|
+
type="button"
|
|
1739
|
+
aria-label={title}
|
|
1740
|
+
disabled={stack.length === 0}
|
|
1741
|
+
onclick={action}
|
|
1742
|
+
{title}
|
|
1743
|
+
class="undo-redo-btn"
|
|
1744
|
+
>
|
|
1745
|
+
<Icon {icon} />
|
|
1746
|
+
{#if stack.length > 0}
|
|
1747
|
+
<span class="history-count">{stack.length}</span>
|
|
1748
|
+
{/if}
|
|
1749
|
+
</button>
|
|
1750
|
+
{/each}
|
|
1661
1751
|
</div>
|
|
1752
|
+
{/snippet}
|
|
1753
|
+
|
|
1754
|
+
{#if measure_mode === `edit-atoms`}
|
|
1755
|
+
{@render undo_redo_snippet([
|
|
1756
|
+
{ icon: `Undo`, title: `Undo (Cmd/Ctrl+Z)`, stack: undo_stack, action: undo },
|
|
1757
|
+
{
|
|
1758
|
+
icon: `Redo`,
|
|
1759
|
+
title: `Redo (Cmd/Ctrl+Y or Cmd+Shift+Z)`,
|
|
1760
|
+
stack: redo_stack,
|
|
1761
|
+
action: redo,
|
|
1762
|
+
},
|
|
1763
|
+
])}
|
|
1662
1764
|
{/if}
|
|
1663
1765
|
|
|
1664
1766
|
{#if measure_mode === `edit-bonds`}
|
|
@@ -1674,10 +1776,7 @@
|
|
|
1674
1776
|
</label>
|
|
1675
1777
|
{/if}
|
|
1676
1778
|
<div class="bond-edit-mode-toggle">
|
|
1677
|
-
{#each [
|
|
1678
|
-
{ mode: `add`, label: `Add`, title: `Add: click two atoms` },
|
|
1679
|
-
{ mode: `delete`, label: `Delete`, title: `Delete: click a bond` },
|
|
1680
|
-
] as const as { mode, label, title } (mode)}
|
|
1779
|
+
{#each [{ mode: `add`, label: `Add`, title: `Add: click two atoms` }, { mode: `delete`, label: `Delete`, title: `Delete: click a bond` }] as const as { mode, label, title } (mode)}
|
|
1681
1780
|
<button
|
|
1682
1781
|
type="button"
|
|
1683
1782
|
class:selected={bond_edit_mode === mode}
|
|
@@ -1690,34 +1789,20 @@
|
|
|
1690
1789
|
{/each}
|
|
1691
1790
|
</div>
|
|
1692
1791
|
</div>
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
<button
|
|
1708
|
-
type="button"
|
|
1709
|
-
aria-label="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1710
|
-
disabled={bond_redo_stack.length === 0}
|
|
1711
|
-
onclick={redo_bond_edit}
|
|
1712
|
-
title="Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)"
|
|
1713
|
-
class="undo-redo-btn"
|
|
1714
|
-
>
|
|
1715
|
-
<Icon icon="Redo" />
|
|
1716
|
-
{#if bond_redo_stack.length > 0}
|
|
1717
|
-
<span class="history-count">{bond_redo_stack.length}</span>
|
|
1718
|
-
{/if}
|
|
1719
|
-
</button>
|
|
1720
|
-
</div>
|
|
1792
|
+
{@render undo_redo_snippet([
|
|
1793
|
+
{
|
|
1794
|
+
icon: `Undo`,
|
|
1795
|
+
title: `Undo bond edit (Cmd/Ctrl+Z)`,
|
|
1796
|
+
stack: bond_undo_stack,
|
|
1797
|
+
action: undo_bond_edit,
|
|
1798
|
+
},
|
|
1799
|
+
{
|
|
1800
|
+
icon: `Redo`,
|
|
1801
|
+
title: `Redo bond edit (Cmd/Ctrl+Y or Cmd+Shift+Z)`,
|
|
1802
|
+
stack: bond_redo_stack,
|
|
1803
|
+
action: redo_bond_edit,
|
|
1804
|
+
},
|
|
1805
|
+
])}
|
|
1721
1806
|
{/if}
|
|
1722
1807
|
|
|
1723
1808
|
<!-- Add-atom element input (shown when add_atom_mode is active) -->
|
|
@@ -1738,8 +1823,7 @@
|
|
|
1738
1823
|
{/if}
|
|
1739
1824
|
|
|
1740
1825
|
<!-- Change-element input (shown when 'e' pressed with selection) -->
|
|
1741
|
-
{#if measure_mode === `edit-atoms` && change_element_mode &&
|
|
1742
|
-
selected_sites.length > 0}
|
|
1826
|
+
{#if measure_mode === `edit-atoms` && change_element_mode && selected_sites.length > 0}
|
|
1743
1827
|
<div class="add-atom-input">
|
|
1744
1828
|
<label>
|
|
1745
1829
|
<span>New element:</span>
|
|
@@ -1767,8 +1851,7 @@
|
|
|
1767
1851
|
{/if}
|
|
1768
1852
|
{/if}
|
|
1769
1853
|
|
|
1770
|
-
{#if enable_info_pane && normalized_structure &&
|
|
1771
|
-
controls_config.visible(`info-pane`)}
|
|
1854
|
+
{#if enable_info_pane && normalized_structure && controls_config.visible(`info-pane`)}
|
|
1772
1855
|
<StructureInfoPane
|
|
1773
1856
|
structure={normalized_structure}
|
|
1774
1857
|
bind:pane_open={info_pane_open}
|
|
@@ -1827,7 +1910,7 @@
|
|
|
1827
1910
|
bind:element_radius_overrides
|
|
1828
1911
|
bind:site_radius_overrides
|
|
1829
1912
|
selected_sites={atom_legend_selected_sites}
|
|
1830
|
-
structure={
|
|
1913
|
+
structure={internal_displayed_structure}
|
|
1831
1914
|
show_mode_toggle={viewer_active}
|
|
1832
1915
|
{sym_data}
|
|
1833
1916
|
>
|
|
@@ -1845,60 +1928,90 @@
|
|
|
1845
1928
|
{/snippet}
|
|
1846
1929
|
</AtomLegend>
|
|
1847
1930
|
|
|
1931
|
+
<!-- One StructureViewport renders the single view; four render the 2x2 multi-view.
|
|
1932
|
+
The primary pane (index 0) carries the external camera API: scene/camera are bound
|
|
1933
|
+
out for export and camera_position/target persist into scene_props, and it emits
|
|
1934
|
+
on_camera_move/on_camera_reset. All camera handling itself lives in StructureViewport. -->
|
|
1935
|
+
{#snippet primary_viewport(view: StructureView)}
|
|
1936
|
+
<StructureViewport
|
|
1937
|
+
in_grid={multi_view}
|
|
1938
|
+
label={multi_view ? view.label : undefined}
|
|
1939
|
+
active={multi_view && active_pane_idx === 0}
|
|
1940
|
+
interactive={!multi_view || active_pane_idx === 0}
|
|
1941
|
+
onactivate={() => (active_pane_idx = 0)}
|
|
1942
|
+
{reset_token}
|
|
1943
|
+
report_moved={(moved) => (moved ? moved_panes.add(0) : moved_panes.delete(0))}
|
|
1944
|
+
{on_camera_move}
|
|
1945
|
+
{on_camera_reset}
|
|
1946
|
+
{...shared_viewport_props}
|
|
1947
|
+
camera_direction={view.direction}
|
|
1948
|
+
camera_projection={view.projection ?? scene_props.camera_projection}
|
|
1949
|
+
bind:camera_position={scene_props.camera_position}
|
|
1950
|
+
bind:camera_target={scene_props.camera_target}
|
|
1951
|
+
bind:scene
|
|
1952
|
+
bind:camera
|
|
1953
|
+
bind:selected_sites
|
|
1954
|
+
bind:measured_sites
|
|
1955
|
+
bind:hovered_site_idx
|
|
1956
|
+
bind:hidden_elements
|
|
1957
|
+
bind:hidden_prop_vals
|
|
1958
|
+
bind:element_radius_overrides
|
|
1959
|
+
bind:site_radius_overrides
|
|
1960
|
+
bind:added_bonds
|
|
1961
|
+
bind:removed_bonds
|
|
1962
|
+
bind:bond_order_overrides
|
|
1963
|
+
bind:bond_edit_mode
|
|
1964
|
+
bind:add_atom_mode
|
|
1965
|
+
bind:add_element
|
|
1966
|
+
bind:dragging_atoms
|
|
1967
|
+
bind:polyhedra_rendered_elements
|
|
1968
|
+
/>
|
|
1969
|
+
{/snippet}
|
|
1970
|
+
|
|
1971
|
+
{#snippet extra_viewport(view: StructureView, pane_idx: number)}
|
|
1972
|
+
<StructureViewport
|
|
1973
|
+
in_grid
|
|
1974
|
+
label={view.label}
|
|
1975
|
+
active={active_pane_idx === pane_idx}
|
|
1976
|
+
interactive={active_pane_idx === pane_idx}
|
|
1977
|
+
onactivate={() => (active_pane_idx = pane_idx)}
|
|
1978
|
+
{reset_token}
|
|
1979
|
+
report_moved={(moved) =>
|
|
1980
|
+
moved ? moved_panes.add(pane_idx) : moved_panes.delete(pane_idx)}
|
|
1981
|
+
{...shared_viewport_props}
|
|
1982
|
+
camera_direction={view.direction}
|
|
1983
|
+
camera_projection={view.projection ?? scene_props.camera_projection}
|
|
1984
|
+
bind:selected_sites
|
|
1985
|
+
bind:measured_sites
|
|
1986
|
+
bind:hovered_site_idx
|
|
1987
|
+
bind:hidden_elements
|
|
1988
|
+
bind:hidden_prop_vals
|
|
1989
|
+
bind:element_radius_overrides
|
|
1990
|
+
bind:site_radius_overrides
|
|
1991
|
+
bind:added_bonds
|
|
1992
|
+
bind:removed_bonds
|
|
1993
|
+
bind:bond_order_overrides
|
|
1994
|
+
bind:bond_edit_mode
|
|
1995
|
+
bind:add_atom_mode
|
|
1996
|
+
bind:add_element
|
|
1997
|
+
bind:dragging_atoms
|
|
1998
|
+
/>
|
|
1999
|
+
{/snippet}
|
|
2000
|
+
|
|
1848
2001
|
<!-- prevent from rendering in vitest runner since WebGLRenderingContext not available -->
|
|
1849
2002
|
{#if typeof WebGLRenderingContext !== `undefined`}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
gizmo={scene_gizmo}
|
|
1858
|
-
{lattice_props}
|
|
1859
|
-
volumetric_data={supercell_volume}
|
|
1860
|
-
{isosurface_settings}
|
|
1861
|
-
bind:camera_is_moving
|
|
1862
|
-
bind:selected_sites
|
|
1863
|
-
active_sites={active_scene_sites}
|
|
1864
|
-
bind:hovered_idx={hovered_site_idx}
|
|
1865
|
-
bind:measured_sites
|
|
1866
|
-
bind:scene
|
|
1867
|
-
bind:camera
|
|
1868
|
-
bind:orbit_controls
|
|
1869
|
-
bind:rotation_target_ref
|
|
1870
|
-
bind:initial_computed_zoom
|
|
1871
|
-
bind:hidden_elements
|
|
1872
|
-
bind:hidden_prop_vals
|
|
1873
|
-
bind:element_radius_overrides
|
|
1874
|
-
bind:site_radius_overrides
|
|
1875
|
-
bind:added_bonds
|
|
1876
|
-
bind:removed_bonds
|
|
1877
|
-
bind:bond_order_overrides
|
|
1878
|
-
{bond_edits_enabled}
|
|
1879
|
-
bind:bond_edit_mode
|
|
1880
|
-
{bond_edit_order}
|
|
1881
|
-
{measure_mode}
|
|
1882
|
-
{width}
|
|
1883
|
-
{height}
|
|
1884
|
-
{atom_color_config}
|
|
1885
|
-
{sym_data}
|
|
1886
|
-
on_sites_moved={handle_sites_moved}
|
|
1887
|
-
on_operation_start={push_undo}
|
|
1888
|
-
on_bond_edit_start={push_bond_undo}
|
|
1889
|
-
on_add_atom={handle_add_atom}
|
|
1890
|
-
bind:add_atom_mode
|
|
1891
|
-
bind:add_element
|
|
1892
|
-
bind:cursor={canvas_cursor}
|
|
1893
|
-
bind:dragging_atoms
|
|
1894
|
-
bind:polyhedra_rendered_elements
|
|
1895
|
-
/>
|
|
1896
|
-
</Canvas>
|
|
2003
|
+
<div class:multi={multi_view} class="viewport-stage">
|
|
2004
|
+
{@render primary_viewport(multi_view ? (views[0] ?? {}) : {})}
|
|
2005
|
+
{#if multi_view}
|
|
2006
|
+
{#each views.slice(1) as view, idx (idx)}
|
|
2007
|
+
{@render extra_viewport(view, idx + 1)}
|
|
2008
|
+
{/each}
|
|
2009
|
+
{/if}
|
|
1897
2010
|
</div>
|
|
1898
2011
|
{/if}
|
|
1899
2012
|
|
|
1900
2013
|
<div class="bottom-left">
|
|
1901
|
-
{@render bottom_left?.({ structure:
|
|
2014
|
+
{@render bottom_left?.({ structure: internal_displayed_structure })}
|
|
1902
2015
|
</div>
|
|
1903
2016
|
|
|
1904
2017
|
{#if toast_msg}
|
|
@@ -1908,9 +2021,7 @@
|
|
|
1908
2021
|
{#if symmetry_error}
|
|
1909
2022
|
<div class="symmetry-error">
|
|
1910
2023
|
<span>{symmetry_error}</span>
|
|
1911
|
-
<button onclick={() => (symmetry_error = undefined)} aria-label="Dismiss">
|
|
1912
|
-
×
|
|
1913
|
-
</button>
|
|
2024
|
+
<button onclick={() => (symmetry_error = undefined)} aria-label="Dismiss"> × </button>
|
|
1914
2025
|
</div>
|
|
1915
2026
|
{/if}
|
|
1916
2027
|
{:else if structure}
|
|
@@ -1940,7 +2051,9 @@
|
|
|
1940
2051
|
background: var(--struct-bg-fullscreen, var(--struct-bg));
|
|
1941
2052
|
overflow: hidden;
|
|
1942
2053
|
}
|
|
1943
|
-
|
|
2054
|
+
/* Single view: stretch the lone canvas to the full screen in fullscreen mode.
|
|
2055
|
+
In multi-view the grid fills the screen and each canvas fills its 1fr cell. */
|
|
2056
|
+
.structure:fullscreen:not(.multi-view) :global(canvas) {
|
|
1944
2057
|
height: 100vh !important;
|
|
1945
2058
|
width: 100vw !important;
|
|
1946
2059
|
}
|
|
@@ -1948,6 +2061,22 @@
|
|
|
1948
2061
|
background: var(--struct-dragover-bg, var(--dragover-bg));
|
|
1949
2062
|
border: var(--struct-dragover-border, var(--dragover-border));
|
|
1950
2063
|
}
|
|
2064
|
+
.viewport-stage {
|
|
2065
|
+
height: 100%;
|
|
2066
|
+
width: 100%;
|
|
2067
|
+
}
|
|
2068
|
+
/* 2x2 multi-side view grid: four equal subcanvases. grid-auto-rows keeps rows
|
|
2069
|
+
equal-height if a custom `views` array supplies more than four entries. */
|
|
2070
|
+
.viewport-stage.multi {
|
|
2071
|
+
display: grid;
|
|
2072
|
+
grid-template-columns: 1fr 1fr;
|
|
2073
|
+
grid-template-rows: 1fr 1fr;
|
|
2074
|
+
grid-auto-rows: 1fr;
|
|
2075
|
+
gap: var(--struct-viewport-gap, 2px);
|
|
2076
|
+
}
|
|
2077
|
+
.multi-view-toggle.active {
|
|
2078
|
+
color: var(--accent-color, #4a9eff);
|
|
2079
|
+
}
|
|
1951
2080
|
/* Ensure canvas is transparent so the themed --struct-bg shows through */
|
|
1952
2081
|
.structure :global(canvas) {
|
|
1953
2082
|
background: transparent;
|
|
@@ -1978,7 +2107,9 @@
|
|
|
1978
2107
|
right: 0;
|
|
1979
2108
|
background: var(--surface-bg);
|
|
1980
2109
|
border-radius: var(--border-radius, 3pt);
|
|
1981
|
-
box-shadow:
|
|
2110
|
+
box-shadow:
|
|
2111
|
+
0 8px 16px -4px rgba(0, 0, 0, 0.3),
|
|
2112
|
+
0 4px 8px -2px rgba(0, 0, 0, 0.1);
|
|
1982
2113
|
display: flex;
|
|
1983
2114
|
flex-direction: column;
|
|
1984
2115
|
}
|
|
@@ -2017,7 +2148,7 @@
|
|
|
2017
2148
|
.measure-mode-dropdown > button {
|
|
2018
2149
|
background: transparent;
|
|
2019
2150
|
padding: 1px 6px;
|
|
2020
|
-
font-size: clamp(0.
|
|
2151
|
+
font-size: var(--ctrl-btn-icon-size, clamp(0.7rem, 2cqmin, 0.85rem));
|
|
2021
2152
|
}
|
|
2022
2153
|
.selection-limit-text {
|
|
2023
2154
|
font-weight: bold;
|
|
@@ -2045,7 +2176,7 @@
|
|
|
2045
2176
|
gap: 1rem;
|
|
2046
2177
|
max-width: min(90%, 400px);
|
|
2047
2178
|
font-size: 0.9rem;
|
|
2048
|
-
z-index: 1000;
|
|
2179
|
+
z-index: var(--z-index-viewer-tooltip, 1000);
|
|
2049
2180
|
}
|
|
2050
2181
|
.symmetry-error span {
|
|
2051
2182
|
flex: 1;
|
|
@@ -2072,14 +2203,15 @@
|
|
|
2072
2203
|
padding: 0.4rem 0.8rem;
|
|
2073
2204
|
border-radius: var(--border-radius, 3pt);
|
|
2074
2205
|
font-size: 0.8rem;
|
|
2075
|
-
z-index: 100;
|
|
2206
|
+
z-index: var(--z-index-viewer-dropdown, 100);
|
|
2076
2207
|
pointer-events: none;
|
|
2077
2208
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
|
2078
2209
|
animation: toast-fade 2s ease-in-out;
|
|
2079
2210
|
opacity: 0;
|
|
2080
2211
|
}
|
|
2081
2212
|
@keyframes toast-fade {
|
|
2082
|
-
0%,
|
|
2213
|
+
0%,
|
|
2214
|
+
70% {
|
|
2083
2215
|
opacity: 1;
|
|
2084
2216
|
}
|
|
2085
2217
|
100% {
|