mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 18:44:16 +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
|
@ -6,7 +6,8 @@ export default {
|
|||||||
icon: "MdOutlineCode",
|
icon: "MdOutlineCode",
|
||||||
label: "Extensions",
|
label: "Extensions",
|
||||||
group: "advanced",
|
group: "advanced",
|
||||||
settings: [
|
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