'use client' import { useEffect, useRef, useState } from 'react' import { plugins, extensionPoints, } from '@/../../electron/core/plugin-manager/execution/index' import { ChartPieIcon, CommandLineIcon, PlayIcon, } from '@heroicons/react/24/outline' import { MagnifyingGlassIcon } from '@heroicons/react/20/solid' import classNames from 'classnames' import { PluginService, preferences } from '@janhq/core' import { execute } from '../../../electron/core/plugin-manager/execution/extension-manager' import LoadingIndicator from './LoadingIndicator' export const Preferences = () => { const [search, setSearch] = useState('') const [activePlugins, setActivePlugins] = useState([]) const [preferenceItems, setPreferenceItems] = useState([]) const [preferenceValues, setPreferenceValues] = useState([]) const [isTestAvailable, setIsTestAvailable] = useState(false) const [fileName, setFileName] = useState('') const [pluginCatalog, setPluginCatalog] = useState([]) const [isLoading, setIsLoading] = useState(false) const experimentRef = useRef(null) const preferenceRef = useRef(null) /** * Loads the plugin catalog module from a CDN and sets it as the plugin catalog state. * The `webpackIgnore` comment is used to prevent Webpack from bundling the module. */ useEffect(() => { // @ts-ignore import(/* webpackIgnore: true */ PLUGIN_CATALOGS).then((module) => { console.log(module) setPluginCatalog(module.default) }) }, []) /** * Fetches the active plugins and their preferences from the `plugins` and `preferences` modules. * If the `experimentComponent` extension point is available, it executes the extension point and * appends the returned components to the `experimentRef` element. * If the `PluginPreferences` extension point is available, it executes the extension point and * fetches the preferences for each plugin using the `preferences.get` function. */ useEffect(() => { const getActivePlugins = async () => { const plgs = await plugins.getActive() setActivePlugins(plgs) if (extensionPoints.get('experimentComponent')) { const components = await Promise.all( extensionPoints.execute('experimentComponent') ) if (components.length > 0) { setIsTestAvailable(true) } components.forEach((e) => { if (experimentRef.current) { // @ts-ignore experimentRef.current.appendChild(e) } }) } if (extensionPoints.get('PluginPreferences')) { const data = await Promise.all( extensionPoints.execute('PluginPreferences') ) setPreferenceItems(Array.isArray(data) ? data : []) Promise.all( (Array.isArray(data) ? data : []).map((e) => preferences .get(e.pluginName, e.preferenceKey) .then((k) => ({ key: e.preferenceKey, value: k })) ) ).then((data) => { setPreferenceValues(data) }) } } getActivePlugins() }, []) /** * Installs a plugin by calling the `plugins.install` function with the plugin file path. * If the installation is successful, the application is relaunched using the `coreAPI.relaunch` function. * @param e - The event object. */ const install = async (e: any) => { e.preventDefault() //@ts-ignore const pluginFile = new FormData(e.target).get('plugin-file').path // Send the filename of the to be installed plugin // to the main process for installation const installed = await plugins.install([pluginFile]) if (installed) window.coreAPI?.relaunch() } /** * Uninstalls a plugin by calling the `plugins.uninstall` function with the plugin name. * If the uninstallation is successful, the application is relaunched using the `coreAPI.relaunch` function. * @param name - The name of the plugin to uninstall. */ const uninstall = async (name: string) => { // Send the filename of the to be uninstalled plugin // to the main process for removal const res = await plugins.uninstall([name]) if (res) window.coreAPI?.relaunch() } /** * Updates a plugin by calling the `window.pluggableElectronIpc.update` function with the plugin name. * If the update is successful, the application is relaunched using the `window.coreAPI.relaunch` function. * TODO: should update using window.coreAPI rather than pluggableElectronIpc (Plugin Manager Facades) * @param plugin - The name of the plugin to update. */ const update = async (plugin: string) => { if (typeof window !== 'undefined') { // @ts-ignore await window.pluggableElectronIpc.update([plugin], true) window.coreAPI?.relaunch() } } /** * Downloads a remote plugin tarball and installs it using the `plugins.install` function. * If the installation is successful, the application is relaunched using the `coreAPI.relaunch` function. * @param pluginName - The name of the remote plugin to download and install. */ const downloadTarball = async (pluginName: string) => { setIsLoading(true) const pluginPath = await window.coreAPI?.installRemotePlugin(pluginName) const installed = await plugins.install([pluginPath]) setIsLoading(false) if (installed) window.coreAPI.relaunch() } /** * Notifies plugins of a preference update by executing the `PluginService.OnPreferencesUpdate` event. * If a timeout is already set, it is cleared before setting a new timeout to execute the event. */ let timeout: any | undefined = undefined function notifyPreferenceUpdate() { if (timeout) { clearTimeout(timeout) } if(extensionPoints.get(PluginService.OnPreferencesUpdate)) timeout = setTimeout(() => execute(PluginService.OnPreferencesUpdate), 100) } /** * Handles the change event of the plugin file input element by setting the file name state. * Its to be used to display the plugin file name of the selected file. * @param event - The change event object. */ const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0] if (file) { setFileName(file.name) } else { setFileName('') } } return (
{/* Separator */}
{/* Content */}
Install Plugin
Installed Plugins
{activePlugins .filter( (e) => search.trim() === '' || e.name.toLowerCase().includes(search.toLowerCase()) ) .map((e) => (

{e.name}

Version: {e.version}

{e.description ?? "Jan's Plugin"}

))}
Explore Plugins
{pluginCatalog .filter( (e: any) => search.trim() === '' || e.name.toLowerCase().includes(search.toLowerCase()) ) .map((e: any) => (

{e.name}

Version: {e.version}

{e.description ?? "Jan's Plugin"}

{e.version !== activePlugins.filter((p) => p.name === e.name)[0] ?.version && ( )}
))}
{activePlugins.length > 0 && isTestAvailable && (
Test Plugins
)}
Preferences
{preferenceItems?.map((e) => (
Setting:{' '} {e.preferenceName}
{e.preferenceDescription}
v.key === e.preferenceKey )[0]?.value } onChange={(event) => { preferences .set(e.pluginName, e.preferenceKey, event.target.value) .then(() => notifyPreferenceUpdate()) }} >
))}
{isLoading && (
Installing...
)}
) }