mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
some improvements on component renders
This commit is contained in:
parent
08ba20b275
commit
1c5c213b96
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user