mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
269 lines
7.1 KiB
JavaScript
269 lines
7.1 KiB
JavaScript
import React from "react"
|
|
import lodable from "@loadable/component"
|
|
import * as antd from "antd"
|
|
|
|
import { SortableList, SortableItem } from "components/SortableList"
|
|
|
|
import StoragedState from "utils/storagedState"
|
|
|
|
import "./index.less"
|
|
|
|
class WidgetComponent extends React.Component {
|
|
state = {
|
|
mountedCssFiles: [],
|
|
loading: true,
|
|
}
|
|
|
|
componentDidMount = async () => {
|
|
if (Array.isArray(this.props.manifest.cssFiles)) {
|
|
for await (const cssFile of this.props.manifest.cssFiles) {
|
|
const cssFileElement = document.createElement("link")
|
|
|
|
cssFileElement.rel = "stylesheet"
|
|
cssFileElement.href = cssFile
|
|
|
|
document.head.appendChild(cssFileElement)
|
|
|
|
await this.setState({
|
|
mountedCssFiles: [
|
|
...this.state.mountedCssFiles,
|
|
cssFileElement,
|
|
]
|
|
})
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
this.setState({
|
|
loading: false,
|
|
})
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.state.mountedCssFiles.forEach((cssFileElement) => {
|
|
cssFileElement.remove()
|
|
})
|
|
}
|
|
|
|
// catch if render error
|
|
componentDidCatch = (error, errorInfo) => {
|
|
console.error(error, errorInfo)
|
|
|
|
this.setState({
|
|
loading: false,
|
|
renderError: error,
|
|
})
|
|
}
|
|
|
|
render() {
|
|
const { RenderComponent, manifest } = this.props
|
|
|
|
if (this.state.renderError) {
|
|
return <div className="widget_item">
|
|
<antd.Result
|
|
status="error"
|
|
title="Failed to render widget"
|
|
subTitle={this.state.renderError.message}
|
|
/>
|
|
</div>
|
|
}
|
|
|
|
if (this.state.loading) {
|
|
return <div className="widget_item">
|
|
<antd.Skeleton active />
|
|
</div>
|
|
}
|
|
|
|
try {
|
|
if (!manifest) {
|
|
throw new Error("Widget has no manifest")
|
|
}
|
|
|
|
if (!RenderComponent) {
|
|
throw new Error("Widget has not valid render")
|
|
}
|
|
|
|
return <div
|
|
className="widget_item"
|
|
id={manifest.id}
|
|
>
|
|
<RenderComponent />
|
|
</div>
|
|
} catch (error) {
|
|
console.error(error)
|
|
|
|
return <div className="widget_item">
|
|
Invalid widget
|
|
</div>
|
|
}
|
|
}
|
|
}
|
|
|
|
function extendsWidgetClass(parentClass, ctx) {
|
|
return class extends parentClass {
|
|
constructor(...args) {
|
|
super(...args)
|
|
|
|
this.ctx = ctx
|
|
|
|
if (typeof this.__entry_init === "function") {
|
|
this.__entry_init()
|
|
}
|
|
}
|
|
|
|
__entry_init = async () => {
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
const generateRemoteComponent = (props) => {
|
|
return lodable(async () => {
|
|
try {
|
|
let virtualModule = await import(/* @vite-ignore */props.url)
|
|
|
|
virtualModule = virtualModule.default
|
|
|
|
if (!virtualModule) {
|
|
throw new Error("Widget has not valid module")
|
|
}
|
|
|
|
let RenderComponent = virtualModule.renderComponent
|
|
|
|
if (!RenderComponent) {
|
|
throw new Error("Widget has not valid render")
|
|
}
|
|
|
|
console.log(`🔄 Generating widget [${virtualModule.manifest.name}]`)
|
|
|
|
let ctx = Object()
|
|
|
|
// check if static storagedStateKey exists on RenderComponent
|
|
if (RenderComponent.storagedStateKey) {
|
|
const storagedStateEngine = new StoragedState()
|
|
|
|
const defaultValue = await storagedStateEngine.getState(RenderComponent.storagedStateKey)
|
|
|
|
ctx["storagedState"] = class {
|
|
static get defaultValue() {
|
|
return defaultValue
|
|
}
|
|
|
|
static setState = async (value) => {
|
|
return await storagedStateEngine.setState(RenderComponent.storagedStateKey, value)
|
|
}
|
|
|
|
static getState = async () => {
|
|
return await storagedStateEngine.getState(RenderComponent.storagedStateKey)
|
|
}
|
|
}
|
|
}
|
|
|
|
RenderComponent = extendsWidgetClass(RenderComponent, ctx)
|
|
|
|
return () => <WidgetComponent
|
|
RenderComponent={RenderComponent}
|
|
manifest={virtualModule.manifest}
|
|
key={props.index}
|
|
index={props.index}
|
|
id={`${virtualModule.manifest.name}-${props.index}`}
|
|
/>
|
|
} catch (error) {
|
|
console.error(error)
|
|
|
|
return () => <div className="widget_item">
|
|
Error loading widget
|
|
</div>
|
|
}
|
|
}, {
|
|
fallback: <antd.Skeleton active />
|
|
})
|
|
}
|
|
|
|
function getWidgets() {
|
|
let installedWidgets = app.cores.widgets.getInstalled()
|
|
|
|
// filter widgets that are not visible
|
|
installedWidgets = installedWidgets.filter((widget) => {
|
|
return widget.visible
|
|
})
|
|
|
|
return installedWidgets.map((manifest, index) => {
|
|
return {
|
|
name: manifest.name,
|
|
id: manifest._id,
|
|
url: manifest.uri,
|
|
RenderItem: generateRemoteComponent({
|
|
url: manifest.uri,
|
|
index: index,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
export default class WidgetsWrapper extends React.Component {
|
|
state = {
|
|
widgetsRender: getWidgets(),
|
|
}
|
|
|
|
events = {
|
|
"widgets:installed": () => {
|
|
this.loadWidgets()
|
|
},
|
|
"widgets:uninstalled": () => {
|
|
this.loadWidgets()
|
|
}
|
|
}
|
|
|
|
componentDidMount() {
|
|
for (const [eventName, eventHandler] of Object.entries(this.events)) {
|
|
app.eventBus.on(eventName, eventHandler)
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
for (const [eventName, eventHandler] of Object.entries(this.events)) {
|
|
app.eventBus.off(eventName, eventHandler)
|
|
}
|
|
}
|
|
|
|
handleOnSortEnd = (widgetsRender) => {
|
|
this.setState({
|
|
widgetsRender
|
|
})
|
|
|
|
app.cores.widgets.sort(widgetsRender)
|
|
}
|
|
|
|
loadWidgets = () => {
|
|
this.setState({
|
|
widgetsRender: getWidgets(),
|
|
})
|
|
}
|
|
|
|
render() {
|
|
return <div className="widgets_wrapper">
|
|
<SortableList
|
|
items={this.state.widgetsRender}
|
|
onChange={this.handleOnSortEnd}
|
|
renderItem={(item, index) => {
|
|
const RenderItem = item.RenderItem
|
|
return <SortableItem id={item.id}>
|
|
<RenderItem />
|
|
</SortableItem>
|
|
}}
|
|
useDragOverlay
|
|
activeDragActions={[
|
|
{
|
|
id: "settings",
|
|
icon: "Settings",
|
|
onClick: () => {
|
|
app.location.push("/settings?tab=widgets")
|
|
}
|
|
},
|
|
]}
|
|
/>
|
|
</div>
|
|
}
|
|
} |