Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions dist/components/ThreeDEditor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@ export class ThreeDEditor extends React.Component<any, any, any> {
}[];
viewerTriggerResize: boolean;
viewerSettings: {
isViewAdjustable: boolean;
atomRadiiScale: number;
repetitionsAlongLatticeVectorA: number;
repetitionsAlongLatticeVectorB: number;
repetitionsAlongLatticeVectorC: number;
chemicalConnectivityFactor: number;
isViewAdjustable: any;
atomRadiiScale: any;
repetitionsAlongLatticeVectorA: any;
repetitionsAlongLatticeVectorB: any;
repetitionsAlongLatticeVectorC: any;
chemicalConnectivityFactor: any;
};
_initialToggleSettings: {
orthographicCamera: any;
bonds: any;
axes: any;
autoRotate: any;
elementLabels: any;
coordinateLabels: any;
};
boundaryConditions: any;
isConventionalCellShown: any;
Expand Down Expand Up @@ -60,6 +68,12 @@ export class ThreeDEditor extends React.Component<any, any, any> {
handleMessage: (event: any) => void;
handleSetMaterial(newMaterialConfig: any): void;
componentDidMount(): void;
/**
* Apply toggle-based view settings from URL params after the Wave instance is mounted.
* These settings are imperative (they toggle state on the Wave class instance),
* so they must be applied after componentDidMount when WaveComponent.wave exists.
*/
_applyInitialToggleSettings(): void;
componentWillUnmount(): void;
UNSAFE_componentWillReceiveProps(nextProps: any, nextContext: any): void;
_resetStateWaveComponent(): void;
Expand Down Expand Up @@ -237,6 +251,7 @@ export namespace ThreeDEditor {
let boundaryConditions: PropTypes.Requireable<object>;
let onUpdate: PropTypes.Requireable<(...args: any[]) => any>;
let isStandalone: PropTypes.Requireable<boolean>;
let initialViewSettings: PropTypes.Requireable<object>;
}
namespace defaultProps {
let boundaryConditions_1: {};
Expand All @@ -249,6 +264,8 @@ export namespace ThreeDEditor {
export { editable_1 as editable };
let isStandalone_1: boolean;
export { isStandalone_1 as isStandalone };
let initialViewSettings_1: {};
export { initialViewSettings_1 as initialViewSettings };
}
}
import React from "react";
Expand Down
61 changes: 52 additions & 9 deletions dist/components/ThreeDEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class ThreeDEditor extends React.Component {
* @param props Properties as explained below
*/
constructor(props) {
var _a, _b, _c, _d, _e, _f, _g;
super(props);
this.handleSetSetting = (setting) => {
const { viewerSettings } = this.state;
Expand Down Expand Up @@ -284,7 +285,7 @@ export class ThreeDEditor extends React.Component {
const { viewerSettings } = this.state;
return (_jsx(ParametersMenu, { viewerSettings: viewerSettings, handleSphereRadiusChange: this.handleSphereRadiusChange, handleCellRepetitionsChange: this.handleCellRepetitionsChange, handleChemicalConnectivityFactorChange: this.handleChemicalConnectivityFactorChange }));
};
const { boundaryConditions, isConventionalCellShown, material } = this.props;
const { boundaryConditions, isConventionalCellShown, material, initialViewSettings = {}, } = this.props;
// TODO : overloading a bunch of props and state attributes here..
this.state = {
// on/off switch for the component
Expand All @@ -296,17 +297,26 @@ export class ThreeDEditor extends React.Component {
// TODO: remove the need for `viewerTriggerResize`
// whether to trigger resize
viewerTriggerResize: false,
// Settings of the wave viewer
// Settings of the wave viewer, merged with any initial overrides from URL params
viewerSettings: {
isViewAdjustable: settings.isViewAdjustable,
atomRadiiScale: settings.atomRadiiScale,
repetitionsAlongLatticeVectorA: settings.repetitions,
repetitionsAlongLatticeVectorB: settings.repetitions,
repetitionsAlongLatticeVectorC: settings.repetitions,
chemicalConnectivityFactor: settings.chemicalConnectivityFactor,
isViewAdjustable: (_a = initialViewSettings.isViewAdjustable) !== null && _a !== void 0 ? _a : settings.isViewAdjustable,
atomRadiiScale: (_b = initialViewSettings.atomRadiiScale) !== null && _b !== void 0 ? _b : settings.atomRadiiScale,
repetitionsAlongLatticeVectorA: (_c = initialViewSettings.repetitionsAlongLatticeVectorA) !== null && _c !== void 0 ? _c : settings.repetitions,
repetitionsAlongLatticeVectorB: (_d = initialViewSettings.repetitionsAlongLatticeVectorB) !== null && _d !== void 0 ? _d : settings.repetitions,
repetitionsAlongLatticeVectorC: (_e = initialViewSettings.repetitionsAlongLatticeVectorC) !== null && _e !== void 0 ? _e : settings.repetitions,
chemicalConnectivityFactor: (_f = initialViewSettings.chemicalConnectivityFactor) !== null && _f !== void 0 ? _f : settings.chemicalConnectivityFactor,
},
// Toggle settings from URL to apply after Wave instance mounts
_initialToggleSettings: {
orthographicCamera: initialViewSettings.orthographicCamera,
bonds: initialViewSettings.bonds,
axes: initialViewSettings.axes,
autoRotate: initialViewSettings.autoRotate,
elementLabels: initialViewSettings.elementLabels,
coordinateLabels: initialViewSettings.coordinateLabels,
},
boundaryConditions,
isConventionalCellShown,
isConventionalCellShown: (_g = initialViewSettings.conventionalCell) !== null && _g !== void 0 ? _g : isConventionalCellShown,
// material that is originally passed to the component and can be modified in ThreejsEditorModal component.
originalMaterial: material,
// material that is passed to WaveComponent to be visualized and may have repetition and radius adjusted.
Expand Down Expand Up @@ -348,6 +358,36 @@ export class ThreeDEditor extends React.Component {
componentDidMount() {
this.addHotKeyListener();
window.addEventListener("message", this.handleMessage);
this._applyInitialToggleSettings();
}
/**
* Apply toggle-based view settings from URL params after the Wave instance is mounted.
* These settings are imperative (they toggle state on the Wave class instance),
* so they must be applied after componentDidMount when WaveComponent.wave exists.
*/
_applyInitialToggleSettings() {
var _a;
const { _initialToggleSettings } = this.state;
if (!_initialToggleSettings || !((_a = this.WaveComponent) === null || _a === void 0 ? void 0 : _a.wave))
return;
if (_initialToggleSettings.orthographicCamera) {
this.handleToggleOrthographicCamera();
}
if (_initialToggleSettings.bonds) {
this.handleToggleBonds();
}
if (_initialToggleSettings.axes) {
this.handleToggleAxes();
}
if (_initialToggleSettings.autoRotate) {
this.handleToggleOrbitControlsAnimation();
}
if (_initialToggleSettings.elementLabels) {
this.handleToggleElementLabels();
}
if (_initialToggleSettings.coordinateLabels) {
this.handleToggleCoordinateLabels();
}
}
componentWillUnmount() {
this.removeHotKeyListener();
Expand Down Expand Up @@ -649,11 +689,14 @@ ThreeDEditor.propTypes = {
boundaryConditions: PropTypes.object,
onUpdate: PropTypes.func,
isStandalone: PropTypes.bool,
// eslint-disable-next-line react/forbid-prop-types
initialViewSettings: PropTypes.object,
};
ThreeDEditor.defaultProps = {
boundaryConditions: {},
isConventionalCellShown: false,
onUpdate: undefined,
editable: false,
isStandalone: false,
initialViewSettings: {},
};
1 change: 1 addition & 0 deletions dist/exports.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { ThreeDEditor } from "./components/ThreeDEditor";
export { ThreejsEditorModal } from "./components/ThreejsEditorModal";
export { parseViewSettingsFromUrlParams, serializeViewSettingsToUrlParams } from "./utils/viewSettingsUrl";
1 change: 1 addition & 0 deletions dist/exports.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { ThreeDEditor } from "./components/ThreeDEditor";
export { ThreejsEditorModal } from "./components/ThreejsEditorModal";
export { parseViewSettingsFromUrlParams, serializeViewSettingsToUrlParams, } from "./utils/viewSettingsUrl";
2 changes: 1 addition & 1 deletion dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export function renderThreeDEditor(materialConfig: any, newDomElement: any): void;
export function renderThreeDEditor(materialConfig: any, newDomElement: any, options?: {}): void;
8 changes: 6 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import { Made } from "@mat3ra/made";
import React from "react";
import ReactDOM from "react-dom";
import { ThreeDEditor } from "./components/ThreeDEditor";
import { parseViewSettingsFromUrlParams } from "./utils/viewSettingsUrl";
// eslint-disable-next-line react/no-render-return-value
const renderThreeDEditor = (materialConfig, newDomElement) => {
const renderThreeDEditor = (materialConfig, newDomElement, options = {}) => {
const config = materialConfig || Made.defaultMaterialConfig;
const domElement = newDomElement || document.getElementById("root");
if (!domElement) {
console.warn("No root element found for rendering the 3D editor");
return;
}
// Read view settings from URL query params unless explicitly provided
const initialViewSettings = options.initialViewSettings ||
parseViewSettingsFromUrlParams(Object.fromEntries(new URLSearchParams(window.location.search)));
const currentMaterial = new Made.Material(config);
ReactDOM.render(_jsx(ThreeDEditor, { editable: true, isStandalone: true, material: currentMaterial }), domElement);
ReactDOM.render(_jsx(ThreeDEditor, { editable: true, isStandalone: true, material: currentMaterial, initialViewSettings: initialViewSettings }), domElement);
};
window.renderThreeDEditor = renderThreeDEditor;
export { renderThreeDEditor };
39 changes: 39 additions & 0 deletions dist/utils/viewSettingsUrl.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* View settings that can be passed via URL query parameters.
* Includes both numeric "viewerSettings" values and boolean toggle settings.
*/
export interface ViewSettingsFromUrl {
atomRadiiScale?: number;
repetitionsAlongLatticeVectorA?: number;
repetitionsAlongLatticeVectorB?: number;
repetitionsAlongLatticeVectorC?: number;
chemicalConnectivityFactor?: number;
isViewAdjustable?: boolean;
orthographicCamera?: boolean;
bonds?: boolean;
axes?: boolean;
autoRotate?: boolean;
elementLabels?: boolean;
coordinateLabels?: boolean;
conventionalCell?: boolean;
}
/**
* Parse URL query parameters into a ViewSettingsFromUrl object.
* Unknown or invalid parameters are silently ignored.
*
* The `repetitions` param supports two formats:
* - Single number (e.g. `repetitions=2`) → applied to all three axes
* - Comma-separated (e.g. `repetitions=2,3,1`) → A=2, B=3, C=1
*
* Accepts values as strings (from URLSearchParams) or pre-parsed types
* (from Iron Router's getQueryWithParsedBooleansFromRoute which converts
* "true"/"false" strings to actual booleans).
*
* @param params - key-value pairs from URL query string
*/
export declare function parseViewSettingsFromUrlParams(params: Record<string, any>): ViewSettingsFromUrl;
/**
* Serialize a ViewSettingsFromUrl object back to URL query parameter key-value pairs.
* Only includes values that differ from defaults. Useful for future two-way sync.
*/
export declare function serializeViewSettingsToUrlParams(viewSettings: ViewSettingsFromUrl): Record<string, string>;
147 changes: 147 additions & 0 deletions dist/utils/viewSettingsUrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import settings from "../settings";
/** Registry of recognized URL param names, their types, and how they map to ViewSettingsFromUrl keys. */
const PARAM_PARSERS = {
atomRadiiScale: { key: "atomRadiiScale", type: "number" },
repetitions: { key: "repetitionsAlongLatticeVectorA", type: "number" }, // handled specially
chemicalConnectivityFactor: { key: "chemicalConnectivityFactor", type: "number" },
connectivityFactor: { key: "chemicalConnectivityFactor", type: "number" }, // alias
isViewAdjustable: { key: "isViewAdjustable", type: "boolean" },
orthographicCamera: { key: "orthographicCamera", type: "boolean" },
bonds: { key: "bonds", type: "boolean" },
axes: { key: "axes", type: "boolean" },
autoRotate: { key: "autoRotate", type: "boolean" },
elementLabels: { key: "elementLabels", type: "boolean" },
coordinateLabels: { key: "coordinateLabels", type: "boolean" },
conventionalCell: { key: "conventionalCell", type: "boolean" },
};
function parseNumber(value) {
const n = Number(value);
return Number.isFinite(n) ? n : undefined;
}
function parseBoolean(value) {
if (value === "true" || value === "1")
return true;
if (value === "false" || value === "0")
return false;
return undefined;
}
/**
* Parse URL query parameters into a ViewSettingsFromUrl object.
* Unknown or invalid parameters are silently ignored.
*
* The `repetitions` param supports two formats:
* - Single number (e.g. `repetitions=2`) → applied to all three axes
* - Comma-separated (e.g. `repetitions=2,3,1`) → A=2, B=3, C=1
*
* Accepts values as strings (from URLSearchParams) or pre-parsed types
* (from Iron Router's getQueryWithParsedBooleansFromRoute which converts
* "true"/"false" strings to actual booleans).
*
* @param params - key-value pairs from URL query string
*/
export function parseViewSettingsFromUrlParams(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params) {
const result = {};
for (const [paramName, rawValue] of Object.entries(params)) {
if (rawValue === undefined || rawValue === "")
continue;
const valueStr = String(rawValue);
// Special handling for `repetitions` (comma-separated or single number)
if (paramName === "repetitions") {
const parts = valueStr.split(",").map((s) => s.trim());
if (parts.length === 1) {
const n = parseNumber(parts[0]);
if (n !== undefined && n >= 1) {
result.repetitionsAlongLatticeVectorA = n;
result.repetitionsAlongLatticeVectorB = n;
result.repetitionsAlongLatticeVectorC = n;
}
}
else if (parts.length === 3) {
const [a, b, c] = parts.map(parseNumber);
if (a !== undefined && a >= 1)
result.repetitionsAlongLatticeVectorA = a;
if (b !== undefined && b >= 1)
result.repetitionsAlongLatticeVectorB = b;
if (c !== undefined && c >= 1)
result.repetitionsAlongLatticeVectorC = c;
}
continue;
}
const parser = PARAM_PARSERS[paramName];
if (!parser)
continue;
if (parser.type === "number") {
const n = parseNumber(valueStr);
if (n !== undefined) {
result[parser.key] = n;
}
}
else if (parser.type === "boolean") {
// Accept pre-parsed booleans (from Iron Router) or string values
if (typeof rawValue === "boolean") {
result[parser.key] = rawValue;
}
else {
const b = parseBoolean(valueStr);
if (b !== undefined) {
result[parser.key] = b;
}
}
}
}
return result;
}
/**
* Serialize a ViewSettingsFromUrl object back to URL query parameter key-value pairs.
* Only includes values that differ from defaults. Useful for future two-way sync.
*/
export function serializeViewSettingsToUrlParams(viewSettings) {
const params = {};
if (viewSettings.atomRadiiScale !== undefined &&
viewSettings.atomRadiiScale !== settings.atomRadiiScale) {
params.atomRadiiScale = String(viewSettings.atomRadiiScale);
}
if (viewSettings.chemicalConnectivityFactor !== undefined &&
viewSettings.chemicalConnectivityFactor !== settings.chemicalConnectivityFactor) {
params.chemicalConnectivityFactor = String(viewSettings.chemicalConnectivityFactor);
}
// Serialize repetitions as comma-separated if any differ from default
const repA = viewSettings.repetitionsAlongLatticeVectorA;
const repB = viewSettings.repetitionsAlongLatticeVectorB;
const repC = viewSettings.repetitionsAlongLatticeVectorC;
if (repA !== undefined || repB !== undefined || repC !== undefined) {
const a = repA !== null && repA !== void 0 ? repA : settings.repetitions;
const b = repB !== null && repB !== void 0 ? repB : settings.repetitions;
const c = repC !== null && repC !== void 0 ? repC : settings.repetitions;
if (a !== settings.repetitions || b !== settings.repetitions || c !== settings.repetitions) {
if (a === b && b === c) {
params.repetitions = String(a);
}
else {
params.repetitions = `${a},${b},${c}`;
}
}
}
// Boolean toggle settings — only include when true (since defaults are false)
const booleanParams = [
{ key: "orthographicCamera", urlKey: "orthographicCamera" },
{ key: "bonds", urlKey: "bonds" },
{ key: "axes", urlKey: "axes" },
{ key: "autoRotate", urlKey: "autoRotate" },
{ key: "elementLabels", urlKey: "elementLabels" },
{ key: "coordinateLabels", urlKey: "coordinateLabels" },
{ key: "conventionalCell", urlKey: "conventionalCell" },
];
for (const { key, urlKey } of booleanParams) {
if (viewSettings[key] !== undefined) {
params[urlKey] = String(viewSettings[key]);
}
}
if (viewSettings.isViewAdjustable !== undefined &&
viewSettings.isViewAdjustable !== settings.isViewAdjustable) {
params.isViewAdjustable = String(viewSettings.isViewAdjustable);
}
return params;
}
Loading
Loading