From 585a989550deab84b1617e134cc0d8de2511b0d0 Mon Sep 17 00:00:00 2001
From: "Aaron (Qilong)" <173288704@qq.com>
Date: Tue, 31 Mar 2026 15:45:09 -0400
Subject: [PATCH 1/6] Tech Debt clean up
---
.eslintrc.json | 9 +-
.github/workflows/npm-publish.yml | 4 +-
package.json | 23 +-
src/App.js | 170 +++++-----
src/Dynamic.css | 21 +-
src/Dynamic.js | 76 ++---
src/Static.js | 500 ++++++++++++++----------------
src/Toast.js | 56 ++--
tests/App.test.js | 37 ++-
tests/Dynamic.test.js | 36 +++
tests/Static.test.js | 67 ++++
tests/e2e.test.js | 8 +-
12 files changed, 530 insertions(+), 477 deletions(-)
create mode 100644 tests/Dynamic.test.js
create mode 100644 tests/Static.test.js
diff --git a/.eslintrc.json b/.eslintrc.json
index b2093c6..65b3f79 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -3,8 +3,7 @@
"node": true,
"browser": true,
"es2021": true,
- "jest": true,
- "webextensions": true
+ "jest": true
},
"extends": [
"eslint:recommended",
@@ -25,11 +24,15 @@
"version": "detect"
}
},
+ "globals": {
+ "chrome": "readonly"
+ },
"ignorePatterns": ["dist"],
"rules": {
"indent": ["error", 2],
"quotes": ["error", "single"],
"semi": ["error", "always"],
- "jsx-quotes": ["error", "prefer-single"]
+ "jsx-quotes": ["error", "prefer-single"],
+ "no-console": "warn"
}
}
diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml
index e25077f..85aa06f 100644
--- a/.github/workflows/npm-publish.yml
+++ b/.github/workflows/npm-publish.yml
@@ -19,7 +19,7 @@ jobs:
with:
node-version: 22
- name: Install dependencies
- run: npm ci --force
+ run: npm ci
- name: Build for development
run: npm run build --if-present
- name: Run Test
@@ -36,7 +36,7 @@ jobs:
node-version: 22
registry-url: https://registry.npmjs.org/
- name: Install dependencies
- run: npm ci --force
+ run: npm ci
- name: Build for production
run: npm run production --if-present
- name: Run Test
diff --git a/package.json b/package.json
index 205e4de..57c4943 100644
--- a/package.json
+++ b/package.json
@@ -15,10 +15,10 @@
"main": "index.bundle.js",
"dependencies": {
"bootstrap": "^5.2.1",
+ "prop-types": "^15.8.1",
"react": "^18.2.0",
"react-bootstrap": "^2.5.0",
- "react-dom": "^18.2.0",
- "react-scripts": "^5.0.1"
+ "react-dom": "^18.2.0"
},
"scripts": {
"lint:check": "eslint src/ tests/",
@@ -33,15 +33,7 @@
"lic_direct": "npx @adsk/adsk-npm-license-puller --path . --app-name 'splash-screen' --verbose --about-box ./license_output/about-box_direct.html --about-box-type desktop --year $(date \"+%Y\") --paos ./license_output/paos_direct.csv",
"lic_transitive": "npx @adsk/adsk-npm-license-puller --path . --app-name 'splash-screen' --verbose --about-box ./license_output/about-box_transitive.html --about-box-type desktop --transitive --year $(date \"+%Y\") --paos ./license_output/paos_transitive.csv",
"generate_license": "npm run lic_direct && npm run lic_transitive",
- "test:unit": "NODE_ENV=test jest tests/App.test.js tests/Toast.test.js",
- "test:e2e": "playwright test tests/e2e.test.js",
- "eject": "react-scripts eject"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
+ "test:unit": "NODE_ENV=test jest tests/App.test.js tests/Toast.test.js tests/Dynamic.test.js tests/Static.test.js"
},
"browserslist": {
"production": [
@@ -56,9 +48,16 @@
]
},
"devDependencies": {
+ "@babel/core": "^7.23.0",
+ "@babel/preset-env": "^7.23.0",
+ "@babel/preset-react": "^7.22.0",
"@playwright/test": "^1.49.0",
"@testing-library/react": "^13.4.0",
- "html-webpack-plugin": "^4.5.2",
+ "babel-loader": "^9.1.3",
+ "css-loader": "^6.8.1",
+ "html-webpack-plugin": "^5.6.0",
+ "style-loader": "^3.3.3",
+ "terser-webpack-plugin": "^5.3.10",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},
diff --git a/src/App.js b/src/App.js
index d712fd8..c265013 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
@@ -6,116 +6,86 @@ import Col from 'react-bootstrap/Col';
import Dynamic from './Dynamic';
import Static from './Static';
import Toast from './Toast';
-import { base64DynamoLogo, base64DynamoBackground } from './encodedImages';
+import { base64DynamoLogo, base64DynamoBackground as importedBackground } from './encodedImages';
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
-class App extends React.Component {
- constructor() {
- super();
- this.setBackgroundImage();
- const noWebView = typeof chrome === 'undefined' || typeof chrome.webview === 'undefined';
- this.isDebugMode = noWebView && new URLSearchParams(window.location.search).has('debug');
- this.state = {
- isChecked: false,
- welcomeToDynamoTitle: 'Welcome to Dynamo!',
- loadingDone: false,
- signInStatus: false
- };
-
- //This is a reference to the DOM of the project that will be called in Dynamo to set the title of the splash screen (Defined by 'Welcome to Dynamo!' by default)
- window.setLabels = this.setLabels.bind(this);
- window.setLoadingDone = this.setLoadingDone.bind(this);
- window.setSignInStatus = this.setSignInStatus.bind(this);
- this.handleCheckedChange = this.handleCheckedChange.bind(this);
- }
+// '#base64BackgroundImage' is a build-time placeholder replaced by Dynamo's build system.
+// If the placeholder was replaced (no longer contains '#'), use that value; otherwise
+// fall back to the image imported from encodedImages.
+const buildTimePlaceholder = '#base64BackgroundImage';
+const backgroundImage = buildTimePlaceholder.includes('#') ? importedBackground : buildTimePlaceholder;
- handleCheckedChange = (checked) => {
- this.setState({isChecked: checked});
- };
+const isDebugMode = (() => {
+ const noWebView = typeof chrome === 'undefined' || typeof chrome.webview === 'undefined';
+ return noWebView && new URLSearchParams(window.location.search).has('debug');
+})();
- setBackgroundImage() {
- let backgroundImage = '#base64BackgroundImage';
- if (!backgroundImage.includes('#'))
- // eslint-disable-next-line no-import-assign
- base64DynamoBackground = backgroundImage;
- }
+function App() {
+ const [welcomeToDynamoTitle, setWelcomeToDynamoTitle] = useState('Welcome to Dynamo!');
+ const [loadingDone, setLoadingDone] = useState(false);
+ const [signInStatus, setSignInStatus] = useState(false);
+ const [labels, setLabels] = useState(undefined);
- componentDidMount() {
- document.addEventListener('keydown', this.handleKeyDown);
+ useEffect(() => {
+ window.setLabels = (newLabels) => {
+ setWelcomeToDynamoTitle(newLabels.welcomeToDynamoTitle);
+ setLabels(newLabels);
+ };
+ window.setLoadingDone = () => setLoadingDone(true);
+ window.setSignInStatus = (val) => setSignInStatus(val.signInStatus === 'True');
- // Debug mode: auto-show Static when running outside Dynamo (no WebView2)
- if (this.isDebugMode) {
- console.log('[SplashScreen] Debug mode: no WebView2 detected, auto-loading Static');
- this.setLoadingDone();
+ if (isDebugMode) {
+ setLoadingDone(true);
}
- }
-
- render() {
- return (
-
-
-
-
-
-
-
-

-
-
-
-
- {this.state.welcomeToDynamoTitle}
-
-
-
-
-
-
- {
- this.state.loadingDone ?
- :
- }
-
-
-
-
- {this.state.loadingDone && }
-
-
-
-
-
- );
- }
-
- //This method sets the labels of the splash screen as an option of localization
- setLabels(labels) {
- this.setState({
- welcomeToDynamoTitle: labels.welcomeToDynamoTitle,
- labels: labels
- });
- }
-
- //Set the login status from Dynamo
- setSignInStatus(val) {
- this.setState({
- signInStatus: val.signInStatus === 'True'
- });
- }
+ return () => {
+ delete window.setLabels;
+ delete window.setLoadingDone;
+ delete window.setSignInStatus;
+ };
+ }, []);
- //This method is called when the loading is done from Dynamo side
- setLoadingDone = async () => {
- this.setState({
- loadingDone: true
- });
- };
+ return (
+
+
+
+
+
+
+
+

+
+
+
+
+ {welcomeToDynamoTitle}
+
+
+
+
+
+
+ {loadingDone
+ ? {}}
+ />
+ :
+ }
+
+
+
+
+ {loadingDone && }
+
+
+
+
+
+ );
}
export default App;
diff --git a/src/Dynamic.css b/src/Dynamic.css
index fb29dc0..7954a16 100644
--- a/src/Dynamic.css
+++ b/src/Dynamic.css
@@ -25,23 +25,6 @@
background-image: linear-gradient(to right, rgba(255, 255, 255, 0.1), #38ABDF, #38ABDF);
}
-.progress-bar-indicator {
- height: 100%;
- border-radius: 25px;
- -webkit-mask: linear-gradient(#fff 0 0);
- mask: linear-gradient(#fff 0 0);
-}
-
-.progress-bar-indicator::before {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-image: linear-gradient(to right, rgba(255, 255, 255, 0.1), #38ABDF, #38ABDF);
-}
-
.dynamicOptions {
margin-top: 50px;
}
@@ -50,7 +33,7 @@
font-size: 9px;
}
-.loadingDescription{
+.loadingDescription{
height: 3.6em;
line-height: 1.8em;
-}
\ No newline at end of file
+}
diff --git a/src/Dynamic.js b/src/Dynamic.js
index bf38705..2d95cf7 100644
--- a/src/Dynamic.js
+++ b/src/Dynamic.js
@@ -1,53 +1,43 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import './Dynamic.css';
-class Dynamic extends React.Component {
- constructor() {
- super();
- this.state = {
- barSize: '0%',
- dynamoVersion: '',
- loadDescription: '',
- loadingTime: '',
- };
+function Dynamic() {
+ const [barSize, setBarSize] = useState('0%');
+ const [dynamoVersion, setDynamoVersion] = useState('');
+ const [loadDescription, setLoadDescription] = useState('');
+ const [loadingTime, setLoadingTime] = useState('');
- //This is a reference to the DOM of the project that will be called in Dynamo to fill the loading properties
- window.setBarProperties = this.setBarProperties.bind(this);
- }
+ useEffect(() => {
+ window.setBarProperties = (version, description, size, time) => {
+ setDynamoVersion(version);
+ setLoadDescription(description);
+ setBarSize(size);
+ setLoadingTime(time);
+ };
+ return () => { delete window.setBarProperties; };
+ }, []);
- render() {
- return (
-
-
- Dynamo Core {this.state.dynamoVersion}
-
-
-
- {this.state.loadDescription}
-
-
-
-
- {this.state.loadingTime}
+ return (
+
+
+ Dynamo Core {dynamoVersion}
+
+
- );
- }
-
- setBarProperties(dynamoVersion, loadDescription, barSize, loadingTime) {
- this.setState({
- barSize: barSize,
- dynamoVersion: dynamoVersion,
- loadDescription: loadDescription,
- loadingTime: loadingTime
- });
- document.getElementsByClassName('progress-bar-indicator')[0].style.display = 'flex';
- }
+
+ {loadDescription}
+
+
+
+
+ {loadingTime}
+
+
+ );
}
export default Dynamic;
diff --git a/src/Static.js b/src/Static.js
index 9d239a8..c5630ce 100644
--- a/src/Static.js
+++ b/src/Static.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
@@ -15,290 +15,250 @@ const importStatusEnum = {
success: 3,
};
-let checked = false;
-
-class Static extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- importStatus: importStatusEnum.none,
- errorDescription: 'Something went wrong when importing your custom setting file. Please try again or proceed with default settings.',
- signInTitle: this.props.signInStatus ? this.props.labels.signOutTitle : this.props.labels.signInTitle,
- signInTooltip: this.props.signInStatus ? this.props.labels.signOutTooltip : this.props.labels.signInTooltip,
- signInStatus: this.props.signInStatus,
- loadingTime: 'Total loading time: ',
- importSettingsTitle: this.props.labels.importSettingsTitle,
- showRestartMessage: false
+function Static({ signInStatus: initialSignInStatus, labels, onCheckedChange }) {
+ const [importStatus, setImportStatus] = useState(importStatusEnum.none);
+ const [errorDescription, setErrorDescription] = useState(
+ 'Something went wrong when importing your custom setting file. Please try again or proceed with default settings.'
+ );
+ const [signInTitle, setSignInTitle] = useState(
+ initialSignInStatus ? labels.signOutTitle : labels.signInTitle
+ );
+ const [signInTooltip, setSignInTooltip] = useState(
+ initialSignInStatus ? labels.signOutTooltip : labels.signInTooltip
+ );
+ const [signInStatus, setSignInStatus] = useState(initialSignInStatus);
+ const [signInDisabled, setSignInDisabled] = useState(false);
+ const [loadingTime, setLoadingTime] = useState('Total loading time: ');
+ const [showRestartMessage, setShowRestartMessage] = useState(false);
+
+ // checkedRef holds the checkbox value passed to LaunchDynamo — a ref avoids
+ // stale closure issues in the launchDynamo handler.
+ const checkedRef = useRef(false);
+ // labelsRef gives window methods stable access to the latest labels prop
+ // without needing to re-register those methods when labels changes.
+ const labelsRef = useRef(labels);
+ labelsRef.current = labels;
+
+ const importSettingsLabelRef = useRef(null);
+ const importSettingsInputRef = useRef(null);
+
+ useEffect(() => {
+ window.setImportStatus = (status) => {
+ setImportStatus(status.status);
+ setErrorDescription(status.errorDescription || '');
+ if (status.status === importStatusEnum.success) {
+ setShowRestartMessage(true);
+ if (window.showToast) {
+ const msg = labelsRef.current?.restartMessage ?? Static.defaultProps.labels.restartMessage;
+ window.showToast(msg, 'success');
+ }
+ } else if (status.status === importStatusEnum.none) {
+ setShowRestartMessage(false);
+ window.hideToast?.();
+ }
+ };
+ window.setTotalLoadingTime = (time) => setLoadingTime(time);
+ window.setEnableSignInButton = ({ enable }) => setSignInDisabled(enable !== 'True');
+ window.handleSignInStateChange = (auth) => {
+ const isSignedIn = auth.status === 'True';
+ setSignInStatus(isSignedIn);
+ setSignInTitle(isSignedIn ? labelsRef.current.signOutTitle : labelsRef.current.signInTitle);
+ setSignInTooltip(isSignedIn ? labelsRef.current.signOutTooltip : labelsRef.current.signInTooltip);
+ };
+ window.showRestartMessage = () => {
+ setShowRestartMessage(true);
+ if (window.showToast) {
+ const msg = labelsRef.current?.restartMessage ?? Static.defaultProps.labels.restartMessage;
+ window.showToast(msg, 'success');
+ }
+ };
+ window.hideRestartMessage = () => {
+ setShowRestartMessage(false);
+ window.hideToast?.();
};
- window.setImportStatus = this.setImportStatus.bind(this);
- window.setTotalLoadingTime = this.setTotalLoadingTime.bind(this);
- window.setEnableSignInButton = this.setEnableSignInButton.bind(this);
- window.handleSignInStateChange = this.handleSignInStateChange.bind(this);
- window.showRestartMessage = this.showRestartMessage.bind(this);
- window.hideRestartMessage = this.hideRestartMessage.bind(this);
- this.handleChange = this.handleChange.bind(this);
- }
-
- componentDidMount() {
- document.addEventListener('keydown', this.handleKeyDown);
- }
-
- //Every time the checkbox is clicked, this method is called
- handleChange() {
- checked = !checked;
- this.props.onCheckedChange(checked);
- }
-
- render() {
- return (
-
-
-
-
-
-
- {this.state.signInTooltip}
- }>
-
-
-
-
-
-
- {this.state.importStatus === importStatusEnum.error ? this.state.errorDescription : this.props.labels.importSettingsTooltipDescription}
-
- }>
-
-
- {this.state.showRestartMessage && (
-
- )}
-
-
-
-
-
-
-
-
- {this.state.loadingTime}
-
-
-
- );
- }
-
- //Opens a page to signin
- signIn = async () => {
- if (typeof chrome !== 'undefined' && chrome.webview !== undefined) {
- if (this.state.signInStatus) {
- let status = await chrome.webview.hostObjects.scriptObject.SignOut();
- this.setState({
- signInStatus: !status,
- signInTitle: this.props.labels.signInTitle,
- signInTooltip: this.props.labels.signInTooltip
- });
+ return () => {
+ delete window.setImportStatus;
+ delete window.setTotalLoadingTime;
+ delete window.setEnableSignInButton;
+ delete window.handleSignInStateChange;
+ delete window.showRestartMessage;
+ delete window.hideRestartMessage;
+ };
+ }, []);
+
+ useEffect(() => {
+ // The import settings control is a