feat: add support for React@18 in backward compatible way (#9435)

Any React version matching this semver is supported: >= 16.8 < 19

Refs #8126
Refs #8414
This commit is contained in:
Vladimír Gorej
2023-12-20 16:50:22 +01:00
committed by GitHub
parent 08fe66b8fd
commit 98b53090cb
11 changed files with 2153 additions and 24278 deletions

26092
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -90,10 +90,10 @@
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"randexp": "^0.5.3", "randexp": "^0.5.3",
"randombytes": "^2.1.0", "randombytes": "^2.1.0",
"react": "=17.0.2", "react": ">=16.8.0 <19",
"react-copy-to-clipboard": "5.1.0", "react-copy-to-clipboard": "5.1.0",
"react-debounce-input": "=3.3.0", "react-debounce-input": "=3.3.0",
"react-dom": "=17.0.2", "react-dom": ">=16.8.0 <19",
"react-immutable-proptypes": "2.2.0", "react-immutable-proptypes": "2.2.0",
"react-immutable-pure-component": "^2.2.0", "react-immutable-pure-component": "^2.2.0",
"react-inspector": "^6.0.1", "react-inspector": "^6.0.1",
@@ -125,7 +125,7 @@
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@release-it/conventional-changelog": "=5.1.0", "@release-it/conventional-changelog": "=5.1.0",
"@svgr/webpack": "=8.1.0", "@svgr/webpack": "=8.1.0",
"@wojtekmaj/enzyme-adapter-react-17": "=0.8.0", "@cfaester/enzyme-adapter-react-18": "=0.7.1",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"babel-plugin-lodash": "=3.3.4", "babel-plugin-lodash": "=3.3.4",
@@ -177,7 +177,7 @@
"prettier": "^3.1.0", "prettier": "^3.1.0",
"process": "^0.11.10", "process": "^0.11.10",
"react-refresh": "^0.14.0", "react-refresh": "^0.14.0",
"react-test-renderer": "=17.0.2", "react-test-renderer": "^18.2.0",
"release-it": "=15.4.2", "release-it": "=15.4.2",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"sass": "^1.69.5", "sass": "^1.69.5",

View File

@@ -24,6 +24,7 @@ import SpecPlugin from "./plugins/spec"
import SwaggerClientPlugin from "./plugins/swagger-client" import SwaggerClientPlugin from "./plugins/swagger-client"
import UtilPlugin from "./plugins/util" import UtilPlugin from "./plugins/util"
import ViewPlugin from "./plugins/view" import ViewPlugin from "./plugins/view"
import ViewLegacyPlugin from "core/plugins/view-legacy"
import DownloadUrlPlugin from "./plugins/download-url" import DownloadUrlPlugin from "./plugins/download-url"
import SafeRenderPlugin from "./plugins/safe-render" import SafeRenderPlugin from "./plugins/safe-render"
@@ -268,6 +269,7 @@ SwaggerUI.plugins = {
SwaggerClient: SwaggerClientPlugin, SwaggerClient: SwaggerClientPlugin,
Util: UtilPlugin, Util: UtilPlugin,
View: ViewPlugin, View: ViewPlugin,
ViewLegacy: ViewLegacyPlugin,
DownloadUrl: DownloadUrlPlugin, DownloadUrl: DownloadUrlPlugin,
SafeRender: SafeRenderPlugin, SafeRender: SafeRenderPlugin,
} }

View File

@@ -30,4 +30,4 @@ export default class ServersContainer extends React.Component {
/> />
</div> ) : null </div> ) : null
} }
} }

View File

@@ -1,176 +1,149 @@
import React from "react" /**
* @prettier
*/
import React, { useCallback, useEffect } from "react"
import { OrderedMap } from "immutable" import { OrderedMap } from "immutable"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes" import ImPropTypes from "react-immutable-proptypes"
export default class Servers extends React.Component { const Servers = ({
servers,
currentServer,
setSelectedServer,
setServerVariableValue,
getServerVariable,
getEffectiveServerValue,
}) => {
const currentServerDefinition =
servers.find((s) => s.get("url") === currentServer) || OrderedMap()
const currentServerVariableDefs =
currentServerDefinition.get("variables") || OrderedMap()
const shouldShowVariableUI = currentServerVariableDefs.size !== 0
static propTypes = { useEffect(() => {
servers: ImPropTypes.list.isRequired, if (currentServer) return
currentServer: PropTypes.string.isRequired,
setSelectedServer: PropTypes.func.isRequired,
setServerVariableValue: PropTypes.func.isRequired,
getServerVariable: PropTypes.func.isRequired,
getEffectiveServerValue: PropTypes.func.isRequired
}
componentDidMount() { // fire 'change' event to set default 'value' of select
let { servers, currentServer } = this.props setSelectedServer(servers.first()?.get("url"))
}, [])
if(currentServer) { useEffect(() => {
// server has changed, we may need to set default values
const currentServerDefinition = servers.find(
(server) => server.get("url") === currentServer
)
if (!currentServerDefinition) {
setSelectedServer(servers.first().get("url"))
return return
} }
// fire 'change' event to set default 'value' of select const currentServerVariableDefs =
this.setServer(servers.first()?.get("url")) currentServerDefinition.get("variables") || OrderedMap()
} currentServerVariableDefs.map((val, key) => {
setServerVariableValue({
UNSAFE_componentWillReceiveProps(nextProps) { server: currentServer,
let { key,
servers, val: val.get("default") || "",
setServerVariableValue,
getServerVariable
} = nextProps
if (this.props.currentServer !== nextProps.currentServer || this.props.servers !== nextProps.servers) {
// Server has changed, we may need to set default values
let currentServerDefinition = servers
.find(v => v.get("url") === nextProps.currentServer)
let prevServerDefinition = this.props.servers
.find(v => v.get("url") === this.props.currentServer) || OrderedMap()
if(!currentServerDefinition) {
return this.setServer(servers.first().get("url"))
}
let prevServerVariableDefs = prevServerDefinition.get("variables") || OrderedMap()
let prevServerVariableDefaultKey = prevServerVariableDefs.find(v => v.get("default")) || OrderedMap()
let prevServerVariableDefaultValue = prevServerVariableDefaultKey.get("default")
let currentServerVariableDefs = currentServerDefinition.get("variables") || OrderedMap()
let currentServerVariableDefaultKey = currentServerVariableDefs.find(v => v.get("default")) || OrderedMap()
let currentServerVariableDefaultValue = currentServerVariableDefaultKey.get("default")
currentServerVariableDefs.map((val, key) => {
let currentValue = getServerVariable(nextProps.currentServer, key)
// note: it is possible for both key/val to be the same across definitions,
// but we will try to detect a change in default values between definitions
// only set the default value if the user hasn't set one yet
// or if the definition appears to have changed
if (!currentValue || prevServerVariableDefaultValue !== currentServerVariableDefaultValue) {
setServerVariableValue({
server: nextProps.currentServer,
key,
val: val.get("default") || ""
})
}
}) })
} })
} }, [currentServer, servers])
onServerChange =( e ) => { const handleServerChange = useCallback(
this.setServer( e.target.value ) (e) => {
setSelectedServer(e.target.value)
},
[setSelectedServer]
)
// set default variable values const handleServerVariableChange = useCallback(
} (e) => {
const variableName = e.target.getAttribute("data-variable")
const newVariableValue = e.target.value
onServerVariableValueChange = ( e ) => {
let {
setServerVariableValue,
currentServer
} = this.props
let variableName = e.target.getAttribute("data-variable")
let newVariableValue = e.target.value
if(typeof setServerVariableValue === "function") {
setServerVariableValue({ setServerVariableValue({
server: currentServer, server: currentServer,
key: variableName, key: variableName,
val: newVariableValue val: newVariableValue,
}) })
} },
} [setServerVariableValue, currentServer]
)
setServer = ( value ) => { return (
let { setSelectedServer } = this.props <div className="servers">
<label htmlFor="servers">
setSelectedServer(value) <select onChange={handleServerChange} value={currentServer}>
} {servers
.valueSeq()
render() { .map((server) => (
let { servers, <option value={server.get("url")} key={server.get("url")}>
currentServer, {server.get("url")}
getServerVariable, {server.get("description") && ` - ${server.get("description")}`}
getEffectiveServerValue
} = this.props
let currentServerDefinition = servers.find(s => s.get("url") === currentServer) || OrderedMap()
let currentServerVariableDefs = currentServerDefinition.get("variables") || OrderedMap()
let shouldShowVariableUI = currentServerVariableDefs.size !== 0
return (
<div className="servers">
<label htmlFor="servers">
<select onChange={ this.onServerChange } value={currentServer}>
{ servers.valueSeq().map(
( server ) =>
<option
value={ server.get("url") }
key={ server.get("url") }>
{ server.get("url") }
{ server.get("description") && ` - ${server.get("description")}` }
</option> </option>
).toArray()} ))
</select> .toArray()}
</label> </select>
{ shouldShowVariableUI ? </label>
<div> {shouldShowVariableUI && (
<div>
<div className={"computed-url"}> <div className={"computed-url"}>
Computed URL: Computed URL:
<code> <code>{getEffectiveServerValue(currentServer)}</code>
{getEffectiveServerValue(currentServer)} </div>
</code> <h4>Server variables</h4>
</div> <table>
<h4>Server variables</h4> <tbody>
<table> {currentServerVariableDefs.entrySeq().map(([name, val]) => {
<tbody> return (
{ <tr key={name}>
currentServerVariableDefs.entrySeq().map(([name, val]) => { <td>{name}</td>
return <tr key={name}> <td>
<td>{name}</td> {val.get("enum") ? (
<td> <select
{ val.get("enum") ? data-variable={name}
<select data-variable={name} onChange={this.onServerVariableValueChange}> onChange={handleServerVariableChange}
{val.get("enum").map(enumValue => { >
return <option {val.get("enum").map((enumValue) => {
selected={enumValue === getServerVariable(currentServer, name)} return (
<option
selected={
enumValue ===
getServerVariable(currentServer, name)
}
key={enumValue} key={enumValue}
value={enumValue}> value={enumValue}
>
{enumValue} {enumValue}
</option> </option>
})} )
</select> : })}
<input </select>
type={"text"} ) : (
value={getServerVariable(currentServer, name) || ""} <input
onChange={this.onServerVariableValueChange} type={"text"}
data-variable={name} value={getServerVariable(currentServer, name) || ""}
></input> onChange={handleServerVariableChange}
} data-variable={name}
</td> ></input>
</tr> )}
}) </td>
} </tr>
</tbody> )
</table> })}
</div>: null </tbody>
} </table>
</div> </div>
) )}
} </div>
)
} }
Servers.propTypes = {
servers: ImPropTypes.list.isRequired,
currentServer: PropTypes.string.isRequired,
setSelectedServer: PropTypes.func.isRequired,
setServerVariableValue: PropTypes.func.isRequired,
getServerVariable: PropTypes.func.isRequired,
getEffectiveServerValue: PropTypes.func.isRequired,
}
export default Servers

View File

@@ -0,0 +1,25 @@
/**
* @prettier
*/
import { getComponent } from "core/plugins/view/root-injects"
import { render } from "./root-injects"
const ViewLegacyPlugin = ({ React, getSystem, getStore, getComponents }) => {
const rootInjects = {}
const reactMajorVersion = parseInt(React?.version, 10)
if (reactMajorVersion >= 16 && reactMajorVersion < 18) {
rootInjects.render = render(
getSystem,
getStore,
getComponent,
getComponents
)
}
return {
rootInjects,
}
}
export default ViewLegacyPlugin

View File

@@ -0,0 +1,12 @@
/**
* @prettier
*/
import React from "react"
import ReactDOM from "react-dom"
export const render =
(getSystem, getStore, getComponent, getComponents) => (domNode) => {
const App = getComponent(getSystem, getStore, getComponents)("App", "root")
ReactDOM.render(<App />, domNode)
}

View File

@@ -82,7 +82,10 @@ export const withMappedContainer = (getSystem, getStore, memGetComponent) => (co
export const render = (getSystem, getStore, getComponent, getComponents) => (domNode) => { export const render = (getSystem, getStore, getComponent, getComponents) => (domNode) => {
const App = getComponent(getSystem, getStore, getComponents)("App", "root") const App = getComponent(getSystem, getStore, getComponents)("App", "root")
ReactDOM.render(<App/>, domNode) const { createRoot } = ReactDOM
const root = createRoot(domNode)
root.render(<App/>)
} }
export const getComponent = (getSystem, getStore, getComponents) => (componentName, container, config = {}) => { export const getComponent = (getSystem, getStore, getComponents) => (componentName, container, config = {}) => {

View File

@@ -16,6 +16,7 @@ import SpecPlugin from "core/plugins/spec"
import SwaggerClientPlugin from "core/plugins/swagger-client" import SwaggerClientPlugin from "core/plugins/swagger-client"
import UtilPlugin from "core/plugins/util" import UtilPlugin from "core/plugins/util"
import ViewPlugin from "core/plugins/view" import ViewPlugin from "core/plugins/view"
import ViewLegacyPlugin from "core/plugins/view-legacy"
import DownloadUrlPlugin from "core/plugins/download-url" import DownloadUrlPlugin from "core/plugins/download-url"
import SafeRenderPlugin from "core/plugins/safe-render" import SafeRenderPlugin from "core/plugins/safe-render"
// ad-hoc plugins // ad-hoc plugins
@@ -28,6 +29,7 @@ const BasePreset = () => [
UtilPlugin, UtilPlugin,
LogsPlugin, LogsPlugin,
ViewPlugin, ViewPlugin,
ViewLegacyPlugin,
SpecPlugin, SpecPlugin,
ErrPlugin, ErrPlugin,
IconsPlugin, IconsPlugin,

View File

@@ -1,6 +1,6 @@
const { JSDOM } = require("jsdom") const { JSDOM } = require("jsdom")
const Enzyme = require("enzyme") const Enzyme = require("enzyme")
const Adapter = require("@wojtekmaj/enzyme-adapter-react-17") const { default: Adapter } = require("@cfaester/enzyme-adapter-react-18")
const win = require("../../src/core/window") const win = require("../../src/core/window")

View File

@@ -1,6 +1,6 @@
import { JSDOM } from "jsdom" import { JSDOM } from "jsdom"
import Enzyme from "enzyme" import Enzyme from "enzyme"
import Adapter from "@wojtekmaj/enzyme-adapter-react-17" const { default: Adapter } = require("@cfaester/enzyme-adapter-react-18")
import win from "../../src/core/window" import win from "../../src/core/window"
@@ -29,7 +29,7 @@ function setUpDomEnvironment() {
} }
copyProps(win, window) // use UI's built-in window wrapper copyProps(win, window) // use UI's built-in window wrapper
copyProps(window, global) copyProps(window, global)
// https://github.com/jsdom/jsdom/issues/1721 // https://github.com/jsdom/jsdom/issues/1721
if (typeof global.window.URL.createObjectURL === "undefined") { if (typeof global.window.URL.createObjectURL === "undefined") {
Object.defineProperty(global.window.URL, "createObjectURL", { value: () => "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==" }) Object.defineProperty(global.window.URL, "createObjectURL", { value: () => "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==" })