some improvements on component renders

This commit is contained in:
SrGooglo 2025-03-13 23:39:01 +00:00
parent 08ba20b275
commit 1c5c213b96
2 changed files with 308 additions and 293 deletions

View File

@ -6,191 +6,194 @@ import { Icons } from "@components/Icons"
import WindowContext from "./context"
export default class DefaultWindowRender extends React.Component {
static contextType = WindowContext
static contextType = WindowContext
ref = React.createRef()
ref = React.createRef()
state = {
renderError: false,
title: null,
actions: [],
dimensions: {
height: this.props.height ?? 600,
width: this.props.width ?? 400,
},
position: this.props.defaultPosition,
visible: false,
}
state = {
renderError: false,
title: null,
actions: [],
dimensions: {
height: this.props.height ?? 600,
width: this.props.width ?? 400,
},
position: this.props.defaultPosition,
visible: false,
}
componentDidMount = () => {
this.setDefaultActions()
componentDidMount = () => {
this.setDefaultActions()
if (typeof this.props.actions !== "undefined") {
if (Array.isArray(this.props.actions)) {
const actions = this.state.actions ?? []
if (typeof this.props.actions !== "undefined") {
if (Array.isArray(this.props.actions)) {
const actions = this.state.actions ?? []
this.props.actions.forEach((action) => {
actions.push(action)
})
this.props.actions.forEach((action) => {
actions.push(action)
})
this.setState({ actions })
}
}
this.setState({ actions })
}
}
if (!this.state.position) {
this.setState({ position: this.getCenterPosition() })
}
if (!this.state.position) {
this.setState({ position: this.getCenterPosition() })
}
this.toggleVisibility(true)
}
this.toggleVisibility(true)
}
componentDidCatch = (error) => {
console.error(error)
componentDidCatch = (error) => {
console.error(error)
this.setState({
renderError: error,
})
}
this.setState({
renderError: error,
})
}
updateTitle = (title) => {
this.setState({ title })
}
updateTitle = (title) => {
this.setState({ title })
}
updateDimensions = (dimensions = {
width: 0,
height: 0,
}) => {
this.ref.current?.updateSize({
width: dimensions.width,
height: dimensions.height,
})
}
updateDimensions = (
dimensions = {
width: 0,
height: 0,
},
) => {
this.ref.current?.updateSize({
width: dimensions.width,
height: dimensions.height,
})
}
updatePosition = (position = {
x: 0,
y: 0,
}) => {
this.ref.current?.updatePosition({
x: position.x,
y: position.y,
})
}
updatePosition = (
position = {
x: 0,
y: 0,
},
) => {
this.ref.current?.updatePosition({
x: position.x,
y: position.y,
})
}
toggleVisibility = (to) => {
this.setState({ visible: to ?? !this.state.visible })
}
toggleVisibility = (to) => {
this.setState({ visible: to ?? !this.state.visible })
}
getCenterPosition = () => {
const dimensions = this.state?.dimensions ?? {}
getCenterPosition = () => {
const dimensions = this.state?.dimensions ?? {}
const windowHeight = dimensions.height ?? 600
const windowWidth = dimensions.width ?? 400
const windowHeight = dimensions.height ?? 600
const windowWidth = dimensions.width ?? 400
return {
x: window.innerWidth / 2 - windowWidth / 2,
y: window.innerHeight / 2 - windowHeight / 2,
}
}
const y = window.innerHeight / 2 - windowHeight / 2
const x = window.innerWidth / 2 - windowWidth / 2
setDefaultActions = () => {
const { actions } = this.state
return {
x: x,
y: y,
}
}
actions.push({
key: "close",
render: () => <Icons.FiXCircle style={{ margin: 0, padding: 0 }} />,
onClick: () => {
this.props.close()
},
})
setDefaultActions = () => {
const { actions } = this.state
this.setState({ actions })
}
actions.push({
key: "close",
render: () => <Icons.FiXCircle style={{ margin: 0, padding: 0 }} />,
onClick: () => {
this.props.close()
},
})
renderActions = () => {
const actions = this.state.actions
this.setState({ actions })
}
if (Array.isArray(actions)) {
return actions.map((action) => {
return (
<div key={action.key} onClick={action.onClick} {...action.props}>
{React.isValidElement(action.render) ? action.render : React.createElement(action.render)}
</div>
)
})
}
renderActions = () => {
const actions = this.state.actions
return null
}
if (Array.isArray(actions)) {
return actions.map((action) => {
return (
<div
key={action.key}
onClick={action.onClick}
{...action.props}
>
{React.isValidElement(action.render)
? action.render
: React.createElement(action.render)}
</div>
)
})
}
getComponentRender = () => {
const ctx = {
...this.props,
updateTitle: this.updateTitle,
updateDimensions: this.updateDimensions,
updatePosition: this.updatePosition,
close: this.props.close,
position: this.state.position,
dimensions: this.state.dimensions,
}
return null
}
return <WindowContext.Provider value={ctx}>
{
React.isValidElement(this.props.children)
? React.cloneElement(this.props.children, ctx)
: React.createElement(this.props.children, ctx)
}
</WindowContext.Provider>
}
getComponentRender = () => {
const ctx = {
...this.props,
updateTitle: this.updateTitle,
updateDimensions: this.updateDimensions,
updatePosition: this.updatePosition,
close: this.props.close,
position: this.state.position,
dimensions: this.state.dimensions,
}
render() {
const { position, dimensions, visible } = this.state
return (
<WindowContext.Provider value={ctx}>
{React.isValidElement(this.props.children)
? React.cloneElement(this.props.children, ctx)
: React.createElement(this.props.children, ctx)}
</WindowContext.Provider>
)
}
if (!visible) {
return null
}
render() {
const { position, dimensions, visible } = this.state
return <Rnd
ref={this.ref}
default={{
...position,
...dimensions,
}}
minWidth={
this.props.minWidth
}
minHeight={
this.props.minHeight
}
enableResizing={
this.props.enableResizing ?? true
}
disableDragging={
this.props.disableDragging ?? false
}
dragHandleClassName={
this.props.dragHandleClassName ?? "window_topbar"
}
bounds={
this.props.bounds ?? "#root"
}
className="window_wrapper"
>
<div className="window_topbar">
<div className="title">{this.state.title}</div>
<div className="actions">{this.renderActions()}</div>
</div>
if (!visible) {
return null
}
<div className="window_body">
{
this.state.renderError && <div className="render_error">
<h1>Render Error</h1>
<code>{this.state.renderError.message}</code>
</div>
}
{
!this.state.renderError && this.getComponentRender()
}
</div>
</Rnd>
}
}
return (
<Rnd
ref={this.ref}
default={{
...position,
...dimensions,
}}
minWidth={this.props.minWidth}
minHeight={this.props.minHeight}
enableResizing={this.props.enableResizing ?? true}
disableDragging={this.props.disableDragging ?? false}
dragHandleClassName={
this.props.dragHandleClassName ?? "window_topbar"
}
//bounds={this.props.bounds ?? "#root"}
className="window_wrapper"
>
<div className="window_topbar">
<div className="title">{this.state.title}</div>
<div className="actions">{this.renderActions()}</div>
</div>
<div className="window_body">
{this.state.renderError && (
<div className="render_error">
<h1>Render Error</h1>
<code>{this.state.renderError.message}</code>
</div>
)}
{!this.state.renderError && this.getComponentRender()}
</div>
</Rnd>
)
}
}

View File

@ -8,168 +8,180 @@ import DefaultWindow from "./components/defaultWindow"
import "./index.less"
export default class WindowManager extends Core {
static namespace = "window_mng"
static namespace = "window_mng"
static idMount = "windows"
static idMount = "windows"
root = null
windows = []
root = null
windows = []
public = {
close: this.close.bind(this),
render: this.render.bind(this),
}
public = {
close: this.close.bind(this),
render: this.render.bind(this),
}
async onInitialize() {
this.root = document.createElement("div")
async onInitialize() {
this.root = document.createElement("div")
this.root.setAttribute("id", this.constructor.idMount)
this.root.setAttribute("id", this.constructor.idMount)
document.body.append(this.root)
}
document.body.append(this.root)
}
handleWrapperClick = (id, event) => {
const element = this.root.querySelector(`#${id}`)
handleWrapperClick = (id, event) => {
const element = this.root.querySelector(`#${id}`)
if (element) {
if (!element.contains(event.target)) {
this.close(id)
}
}
}
if (element) {
if (!element.contains(event.target)) {
this.close(id)
}
}
}
/**
* Creates a new element with the specified id and appends it to the root element.
* If the element already exists and createOrUpdate option is false, it will throw an error.
* If the element already exists and createOrUpdate option is true, it will update the element.
* If useFrame option is true, it wraps the fragment with a DefaultWindow component before rendering.
*
* @param {string} id - The id of the element to create or update.
* @param {ReactElement} fragment - The React element to render inside the created element.
* @param {Object} options - The options for creating or updating the element.
* @param {boolean} options.useFrame - Specifies whether to wrap the fragment with a DefaultWindow component.
* @param {boolean} options.createOrUpdate - Specifies whether to create a new element or update an existing one.
* @return {HTMLElement} The created or updated element.
*/
async render(
id,
fragment,
{
useFrame = false,
onClose = null,
createOrUpdate = false,
closeOnClickOutside = false,
} = {}
) {
let element = document.createElement("div")
let node = null
let win = null
/**
* Creates a new element with the specified id and appends it to the root element.
* If the element already exists and createOrUpdate option is false, it will throw an error.
* If the element already exists and createOrUpdate option is true, it will update the element.
* If useFrame option is true, it wraps the fragment with a DefaultWindow component before rendering.
*
* @param {string} id - The id of the element to create or update.
* @param {ReactElement} fragment - The React element to render inside the created element.
* @param {Object} options - The options for creating or updating the element.
* @param {boolean} options.useFrame - Specifies whether to wrap the fragment with a DefaultWindow component.
* @param {boolean} options.createOrUpdate - Specifies whether to create a new element or update an existing one.
* @return {HTMLElement} The created or updated element.
*/
async render(
id,
fragment,
{
useFrame = false,
onClose = null,
createOrUpdate = false,
closeOnClickOutside = false,
} = {},
) {
let element = document.createElement("div")
let node = null
let win = null
// check if window already exist
// if exist, try to automatically generate a new id
if (this.root.querySelector(`#${id}`) && !createOrUpdate) {
const newId = `${id}_${Date.now()}`
// check if window already exist
// if exist, try to automatically generate a new id
if (this.root.querySelector(`#${id}`) && !createOrUpdate) {
const newId = `${id}_${Date.now()}`
this.console.warn(`Window ${id} already exist, overwritting id to ${newId}.\nYou can use {createOrUpdate = true} option to force refresh render of window`)
this.console.warn(
`Window ${id} already exist, overwritting id to ${newId}.\nYou can use {createOrUpdate = true} option to force refresh render of window`,
)
id = newId
}
id = newId
}
// check if window already exist, if exist and createOrUpdate is true, update the element
// if not exist, create a new element
if (this.root.querySelector(`#${id}`) && createOrUpdate) {
element = document.getElementById(id)
// check if window already exist, if exist and createOrUpdate is true, update the element
// if not exist, create a new element
if (this.root.querySelector(`#${id}`) && createOrUpdate) {
element = document.getElementById(id)
win = this.windows.find((_node) => {
return _node.id === id
})
win = this.windows.find((_node) => {
return _node.id === id
})
if (win) {
node = win.node
}
} else {
element.setAttribute("id", id)
if (win) {
node = win.node
}
} else {
element.setAttribute("id", id)
this.root.appendChild(element)
this.root.appendChild(element)
node = createRoot(element)
node = createRoot(element)
win = {
id: id,
node: node,
onClose: onClose,
closeOnClickOutside: closeOnClickOutside,
}
win = {
id: id,
node: node,
onClose: onClose,
closeOnClickOutside: closeOnClickOutside,
}
this.windows.push(win)
this.windows.push(win)
// if closeOnClickOutside is true, add click event listener
if (closeOnClickOutside === true) {
document.addEventListener(
"click",
(e) => this.handleWrapperClick(id, e),
{ once: true },
)
}
}
// if closeOnClickOutside is true, add click event listener
if (closeOnClickOutside === true) {
document.addEventListener(
"click",
(e) => this.handleWrapperClick(id, e),
{ once: true },
)
}
}
// if useFrame is true, wrap the fragment with a DefaultWindow component
if (useFrame) {
fragment = <DefaultWindow>
{fragment}
</DefaultWindow>
}
// if useFrame is true, wrap the fragment with a DefaultWindow component
if (useFrame) {
fragment = <DefaultWindow>{fragment}</DefaultWindow>
}
node.render(React.cloneElement(fragment, {
close: () => {
this.close(id, onClose)
}
}))
if (React.isValidElement(fragment)) {
node.render(
React.cloneElement(fragment, {
close: () => {
this.close(id, onClose)
},
}),
)
} else {
node.render(
React.createElement(fragment, {
close: () => {
this.close(id, onClose)
},
}),
)
}
return {
element,
...win,
}
}
return {
element,
...win,
}
}
/**
* Closes the window with the given ID.
*
* @param {string} id - The ID of the window to be closed.
* @return {boolean} Returns true if the window was successfully closed, false otherwise.
*/
async close(id) {
const element = document.getElementById(id)
/**
* Closes the window with the given ID.
*
* @param {string} id - The ID of the window to be closed.
* @return {boolean} Returns true if the window was successfully closed, false otherwise.
*/
async close(id) {
const element = document.getElementById(id)
const win = this.windows.find((node) => {
return node.id === id
})
const win = this.windows.find((node) => {
return node.id === id
})
if (!win || !element) {
this.console.error(`Window [${id}] not found`)
return false
}
if (!win || !element) {
this.console.error(`Window [${id}] not found`)
return false
}
this.console.debug(`Closing window ${id}`, win, element)
this.console.debug(`Closing window ${id}`, win, element)
// if onClose callback is defined, call it
if (typeof win.onClose === "function") {
this.console.debug(`Trigging close callback for window ${id}`)
await win.onClose()
}
// if onClose callback is defined, call it
if (typeof win.onClose === "function") {
this.console.debug(`Trigging close callback for window ${id}`)
await win.onClose()
}
// remove the element from the DOM
this.console.debug(`Removing element from DOM for window ${id}`)
win.node.unmount()
this.root.removeChild(element)
// remove the element from the DOM
this.console.debug(`Removing element from DOM for window ${id}`)
win.node.unmount()
this.root.removeChild(element)
// remove the window from the list
this.console.debug(`Removing window from list for window ${id}`)
this.windows = this.windows.filter((node) => {
return node.id !== id
})
// remove the window from the list
this.console.debug(`Removing window from list for window ${id}`)
this.windows = this.windows.filter((node) => {
return node.id !== id
})
this.console.debug(`Window ${id} closed`)
return true
}
}
this.console.debug(`Window ${id} closed`)
return true
}
}