mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
added extensions settings
This commit is contained in:
parent
64fd80c0aa
commit
44ce0a0d1c
@ -0,0 +1,72 @@
|
||||
import React from "react"
|
||||
import { Tag, Switch, Button } from "antd"
|
||||
import { Icons } from "@components/Icons"
|
||||
import Image from "@components/Image"
|
||||
|
||||
const ExtensionItem = ({ extension, onClickUninstall, onSwitchEnable }) => {
|
||||
return (
|
||||
<div key={extension.id} className="extension-item">
|
||||
{extension.manifest.icon && (
|
||||
<div className="extension-item-icon">
|
||||
<Image
|
||||
src={extension.manifest.icon}
|
||||
alt={extension.manifest.name}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!extension.manifest.icon && (
|
||||
<div className="extension-item-icon">
|
||||
<Icons.FiBox />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="extension-item-details">
|
||||
<p className="extension-item-name">
|
||||
{extension.manifest.name} [{extension.id}]
|
||||
</p>
|
||||
<p className="extension-item-description">
|
||||
{extension.manifest.description}
|
||||
</p>
|
||||
<div className="extension-item-indicators">
|
||||
<Tag color="blue" icon={<Icons.FiTag />}>
|
||||
v{extension.manifest.version}
|
||||
</Tag>
|
||||
<Tag color="green" icon={<Icons.FiClock />}>
|
||||
Load {extension.loadDuration.toFixed(2)}ms
|
||||
</Tag>
|
||||
{extension.manifest.author && (
|
||||
<Tag icon={<Icons.FiUser />}>
|
||||
{extension.manifest.author}
|
||||
</Tag>
|
||||
)}
|
||||
{extension.manifest.license && (
|
||||
<Tag icon={<Icons.FiLock />}>
|
||||
{extension.manifest.license}
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{extension.manifest.homepage && (
|
||||
<Tag icon={<Icons.FiExternalLink />}>
|
||||
{extension.manifest.homepage}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="extension-item-actions">
|
||||
<Switch
|
||||
defaultChecked={extension.enabled}
|
||||
onChange={(checked) => onSwitchEnable(extension, checked)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon={<Icons.FiTrash />}
|
||||
onClick={() => onClickUninstall(extension)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExtensionItem
|
@ -0,0 +1,137 @@
|
||||
import React from "react"
|
||||
import { Input, Alert, Button } from "antd"
|
||||
|
||||
const confirmInstall = async (url, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
app.layout.modal.confirm({
|
||||
headerText: "Confirm Installation",
|
||||
descriptionText:
|
||||
"Check and verify the details of the extension before installing.",
|
||||
onConfirm: async () => {
|
||||
app.extensions
|
||||
.install(url)
|
||||
.then(() => {
|
||||
app.message.success("Extension installed successfully")
|
||||
resolve()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error installing extension:", error)
|
||||
reject(error)
|
||||
})
|
||||
},
|
||||
onCancel: () => {
|
||||
resolve()
|
||||
},
|
||||
render: () => {
|
||||
return (
|
||||
<code
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
fontSize: "14px",
|
||||
backgroundColor: "var(--background-color-primary)",
|
||||
padding: "10px",
|
||||
borderRadius: "12px",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
<span>Name: {data.name}</span>
|
||||
<span>Version: {data.version}</span>
|
||||
<span>Description: {data.description}</span>
|
||||
<span>Author: {data.author}</span>
|
||||
<span>License: {data.license}</span>
|
||||
<span>Homepage: {data.homepage}</span>
|
||||
</code>
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const InstallCustom = (props) => {
|
||||
const [url, setUrl] = React.useState("")
|
||||
const [error, setError] = React.useState("")
|
||||
const [installing, setInstalling] = React.useState(false)
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
setUrl(event.target.value)
|
||||
}
|
||||
|
||||
const handleInstallClick = async () => {
|
||||
setError(null)
|
||||
setInstalling(true)
|
||||
|
||||
if (!url) {
|
||||
setError("Please enter a valid URL")
|
||||
setInstalling(false)
|
||||
return false
|
||||
}
|
||||
|
||||
let data = await fetch(url).catch((error) => {
|
||||
return null
|
||||
})
|
||||
|
||||
if (
|
||||
!data ||
|
||||
data.status !== 200 ||
|
||||
!data.headers.get("content-type").includes("application/json")
|
||||
) {
|
||||
setError("Failed to fetch extension data")
|
||||
setInstalling(false)
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
data = await data.json()
|
||||
} catch (error) {
|
||||
setError("Failed to parse extension data")
|
||||
setInstalling(false)
|
||||
return false
|
||||
}
|
||||
|
||||
await confirmInstall(url, data)
|
||||
|
||||
setInstalling(false)
|
||||
|
||||
if (props.close) {
|
||||
props.close()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="install-custom-extension">
|
||||
<div className="install-custom-extension-header">
|
||||
<h2>Install Custom Extension</h2>
|
||||
<p>
|
||||
Please enter the URL of the extension you want to install.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
placeholder="https://example.com/extension/package.json"
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={handleInstallClick}
|
||||
disabled={installing}
|
||||
loading={installing}
|
||||
>
|
||||
Install
|
||||
</Button>
|
||||
|
||||
{error && <Alert message={error} type="error" />}
|
||||
|
||||
<Alert
|
||||
message="Be aware installing custom extensions may pose security risks. Only install extensions from trusted sources."
|
||||
type="warning"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InstallCustom
|
@ -2,11 +2,12 @@ import React from "react"
|
||||
import loadable from "@loadable/component"
|
||||
|
||||
export default {
|
||||
id: "extensions",
|
||||
icon: "MdOutlineCode",
|
||||
label: "Extensions",
|
||||
group: "advanced",
|
||||
settings: [
|
||||
|
||||
]
|
||||
}
|
||||
id: "extensions",
|
||||
icon: "MdOutlineCode",
|
||||
label: "Extensions",
|
||||
group: "advanced",
|
||||
render: () => {
|
||||
const ExtensionsPage = loadable(() => import("./page"))
|
||||
return <ExtensionsPage />
|
||||
},
|
||||
}
|
||||
|
117
packages/app/src/settings/extensions/index.less
Normal file
117
packages/app/src/settings/extensions/index.less
Normal file
@ -0,0 +1,117 @@
|
||||
.extensions-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 20px;
|
||||
|
||||
.extensions-page-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.extensions-page-header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.extensions-page-header-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.extensions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.extension-item {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
|
||||
border-radius: 12px;
|
||||
background-color: var(--background-color-accent);
|
||||
|
||||
.extension-item-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.extension-item-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 5px;
|
||||
|
||||
.ant-tag {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
|
||||
align-items: center;
|
||||
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.extension-item-name {
|
||||
font-family: "DM Mono", monospace;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.extension-item-actions {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.install-custom-extension {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
|
||||
font-size: 0.9rem;
|
||||
}
|
104
packages/app/src/settings/extensions/page.jsx
Normal file
104
packages/app/src/settings/extensions/page.jsx
Normal file
@ -0,0 +1,104 @@
|
||||
import React from "react"
|
||||
import { Tag, Switch, Button } from "antd"
|
||||
import { Icons } from "@components/Icons"
|
||||
import Image from "@components/Image"
|
||||
|
||||
import ExtensionItem from "./components/ExtensionItem"
|
||||
import InstallCustom from "./components/InstallCustom"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
function getInstalledExtensions() {
|
||||
let extensions = []
|
||||
|
||||
for (let extension of app.extensions.extensions.values()) {
|
||||
extensions.push(extension)
|
||||
}
|
||||
|
||||
return extensions
|
||||
}
|
||||
|
||||
const ExtensionsPage = () => {
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
const [extensions, setExtensions] = React.useState([])
|
||||
|
||||
const events = {
|
||||
"extension:installed": () => {
|
||||
setExtensions(getInstalledExtensions())
|
||||
},
|
||||
"extension:uninstalled": () => {
|
||||
setExtensions(getInstalledExtensions())
|
||||
},
|
||||
}
|
||||
|
||||
const onSwitchEnable = (extension, checked) => {
|
||||
app.extensions.toggleExtension(extension.id, checked)
|
||||
return !checked
|
||||
}
|
||||
|
||||
const onClickUninstall = (extension) => {
|
||||
app.layout.modal.confirm({
|
||||
headerText: "Uninstall Extension",
|
||||
descriptionText:
|
||||
"Are you sure you want to uninstall this extension?",
|
||||
onConfirm: async () => {
|
||||
await app.extensions.uninstall(extension.id)
|
||||
app.message.success("Extension uninstalled")
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoading(true)
|
||||
setExtensions(getInstalledExtensions())
|
||||
setLoading(false)
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
for (const event in events) {
|
||||
app.eventBus.on(event, events[event])
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (const event in events) {
|
||||
app.eventBus.off(event, events[event])
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="extensions-page">
|
||||
<div className="extensions-page-header">
|
||||
<div className="extensions-page-header-text">
|
||||
<h1>Extensions</h1>
|
||||
<p>Manage your extensions here.</p>
|
||||
</div>
|
||||
<div className="extensions-page-header-actions">
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<Icons.FiPlus />}
|
||||
onClick={() =>
|
||||
app.layout.modal.open(
|
||||
"install_custom",
|
||||
InstallCustom,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="extensions-list">
|
||||
{extensions.map((extension) => (
|
||||
<ExtensionItem
|
||||
key={extension.id}
|
||||
extension={extension}
|
||||
onSwitchEnable={onSwitchEnable}
|
||||
onClickUninstall={onClickUninstall}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExtensionsPage
|
Loading…
x
Reference in New Issue
Block a user