2024-03-09 23:07:09 +01:00
|
|
|
import {useStore} from "../store/store.ts";
|
2024-03-10 23:18:50 +01:00
|
|
|
import {useEffect, useMemo, useState} from "react";
|
2024-03-09 23:07:09 +01:00
|
|
|
import {InstalledPlugin, PluginDef, SearchParams} from "./Plugin.ts";
|
|
|
|
import {useDebounce} from "../utils/useDebounce.ts";
|
|
|
|
import {Trans, useTranslation} from "react-i18next";
|
2024-03-13 15:47:02 +01:00
|
|
|
import {SearchField} from "../components/SearchField.tsx";
|
|
|
|
import {Download, Trash} from "lucide-react";
|
|
|
|
import {IconButton} from "../components/IconButton.tsx";
|
2024-08-13 21:32:40 +02:00
|
|
|
import {determineSorting} from "../utils/sorting.ts";
|
2024-03-09 23:07:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
export const HomePage = () => {
|
|
|
|
const pluginsSocket = useStore(state=>state.pluginsSocket)
|
|
|
|
const [plugins,setPlugins] = useState<PluginDef[]>([])
|
|
|
|
const [installedPlugins, setInstalledPlugins] = useState<InstalledPlugin[]>([])
|
2024-08-13 21:32:40 +02:00
|
|
|
const [searchParams, setSearchParams] = useState<SearchParams>({
|
|
|
|
offset: 0,
|
|
|
|
limit: 99999,
|
|
|
|
sortBy: 'name',
|
|
|
|
sortDir: 'asc',
|
|
|
|
searchTerm: ''
|
|
|
|
})
|
|
|
|
|
|
|
|
const filteredInstallablePlugins = useMemo(()=>{
|
|
|
|
return plugins.sort((a, b)=>{
|
|
|
|
if(searchParams.sortBy === "version"){
|
|
|
|
if(searchParams.sortDir === "asc"){
|
|
|
|
return a.version.localeCompare(b.version)
|
|
|
|
}
|
|
|
|
return b.version.localeCompare(a.version)
|
|
|
|
}
|
|
|
|
|
|
|
|
if(searchParams.sortBy === "last-updated"){
|
|
|
|
if(searchParams.sortDir === "asc"){
|
|
|
|
return a.time.localeCompare(b.time)
|
|
|
|
}
|
|
|
|
return b.time.localeCompare(a.time)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (searchParams.sortBy === "name") {
|
|
|
|
if(searchParams.sortDir === "asc"){
|
|
|
|
return a.name.localeCompare(b.name)
|
|
|
|
}
|
|
|
|
return b.name.localeCompare(a.name)
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
})
|
|
|
|
}, [plugins, searchParams])
|
|
|
|
|
2024-03-10 23:18:50 +01:00
|
|
|
const sortedInstalledPlugins = useMemo(()=>{
|
|
|
|
return installedPlugins.sort((a, b)=>{
|
2024-08-13 21:32:40 +02:00
|
|
|
|
2024-03-10 23:18:50 +01:00
|
|
|
if(a.name < b.name){
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
if(a.name > b.name){
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
})
|
|
|
|
|
2024-08-13 21:32:40 +02:00
|
|
|
} ,[installedPlugins, searchParams])
|
|
|
|
|
2024-03-09 23:07:09 +01:00
|
|
|
const [searchTerm, setSearchTerm] = useState<string>('')
|
|
|
|
const {t} = useTranslation()
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if(!pluginsSocket){
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
pluginsSocket.on('results:installed', (data:{
|
|
|
|
installed: InstalledPlugin[]
|
|
|
|
})=>{
|
|
|
|
setInstalledPlugins(data.installed)
|
|
|
|
})
|
|
|
|
|
|
|
|
pluginsSocket.on('results:updatable', (data) => {
|
|
|
|
data.updatable.forEach((pluginName: string) => {
|
|
|
|
setInstalledPlugins(installedPlugins.map(plugin => {
|
|
|
|
if (plugin.name === pluginName) {
|
|
|
|
return {
|
|
|
|
...plugin,
|
|
|
|
updatable: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return plugin
|
|
|
|
}))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
pluginsSocket.on('finished:install', () => {
|
|
|
|
pluginsSocket!.emit('getInstalled');
|
|
|
|
})
|
|
|
|
|
|
|
|
pluginsSocket.on('finished:uninstall', () => {
|
|
|
|
console.log("Finished uninstall")
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// Reload on reconnect
|
|
|
|
pluginsSocket.on('connect', ()=>{
|
|
|
|
// Initial retrieval of installed plugins
|
|
|
|
pluginsSocket.emit('getInstalled');
|
|
|
|
pluginsSocket.emit('search', searchParams)
|
|
|
|
})
|
|
|
|
|
|
|
|
pluginsSocket.emit('getInstalled');
|
|
|
|
|
|
|
|
// check for updates every 5mins
|
|
|
|
const interval = setInterval(() => {
|
|
|
|
pluginsSocket.emit('checkUpdates');
|
|
|
|
}, 1000 * 60 * 5);
|
|
|
|
|
|
|
|
return ()=>{
|
|
|
|
clearInterval(interval)
|
|
|
|
}
|
|
|
|
}, [pluginsSocket]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!pluginsSocket) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pluginsSocket?.emit('search', searchParams)
|
|
|
|
pluginsSocket!.on('results:search', (data: {
|
|
|
|
results: PluginDef[]
|
|
|
|
}) => {
|
2024-06-10 13:00:58 -04:00
|
|
|
setPlugins(data.results)
|
|
|
|
})
|
|
|
|
pluginsSocket!.on('results:searcherror', (data: {error: string}) => {
|
|
|
|
console.log(data.error)
|
|
|
|
useStore.getState().setToastState({
|
|
|
|
open: true,
|
|
|
|
title: "Error retrieving plugins",
|
|
|
|
success: false
|
|
|
|
})
|
2024-03-09 23:07:09 +01:00
|
|
|
})
|
|
|
|
}, [searchParams, pluginsSocket]);
|
|
|
|
|
|
|
|
const uninstallPlugin = (pluginName: string)=>{
|
|
|
|
pluginsSocket!.emit('uninstall', pluginName);
|
|
|
|
// Remove plugin
|
|
|
|
setInstalledPlugins(installedPlugins.filter(i=>i.name !== pluginName))
|
|
|
|
}
|
|
|
|
|
|
|
|
const installPlugin = (pluginName: string)=>{
|
|
|
|
pluginsSocket!.emit('install', pluginName);
|
|
|
|
setPlugins(plugins.filter(plugin=>plugin.name !== pluginName))
|
|
|
|
}
|
|
|
|
|
|
|
|
useDebounce(()=>{
|
|
|
|
setSearchParams({
|
|
|
|
...searchParams,
|
|
|
|
offset: 0,
|
|
|
|
searchTerm: searchTerm
|
|
|
|
})
|
|
|
|
}, 500, [searchTerm])
|
|
|
|
|
|
|
|
return <div>
|
|
|
|
<h1><Trans i18nKey="admin_plugins"/></h1>
|
|
|
|
|
|
|
|
<h2><Trans i18nKey="admin_plugins.installed"/></h2>
|
|
|
|
|
2024-03-13 15:47:02 +01:00
|
|
|
<table id="installed-plugins">
|
2024-03-09 23:07:09 +01:00
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th><Trans i18nKey="admin_plugins.name"/></th>
|
|
|
|
<th><Trans i18nKey="admin_plugins.version"/></th>
|
2024-03-13 15:47:02 +01:00
|
|
|
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
|
2024-03-09 23:07:09 +01:00
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody style={{overflow: 'auto'}}>
|
2024-03-10 23:18:50 +01:00
|
|
|
{sortedInstalledPlugins.map((plugin, index) => {
|
2024-03-09 23:07:09 +01:00
|
|
|
return <tr key={index}>
|
2024-04-16 19:48:34 +02:00
|
|
|
<td><a rel="noopener noreferrer" href={`https://npmjs.com/${plugin.name}`} target="_blank">{plugin.name}</a></td>
|
2024-03-09 23:07:09 +01:00
|
|
|
<td>{plugin.version}</td>
|
|
|
|
<td>
|
|
|
|
{
|
|
|
|
plugin.updatable ?
|
|
|
|
<button onClick={() => installPlugin(plugin.name)}>Update</button>
|
2024-03-13 15:47:02 +01:00
|
|
|
: <IconButton disabled={plugin.name == "ep_etherpad-lite"} icon={<Trash/>} title={<Trans i18nKey="admin_plugins.installed_uninstall.value"/>} onClick={() => uninstallPlugin(plugin.name)}/>
|
2024-03-09 23:07:09 +01:00
|
|
|
}
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
})}
|
2024-06-10 13:00:58 -04:00
|
|
|
</tbody>
|
|
|
|
</table>
|
2024-03-09 23:07:09 +01:00
|
|
|
|
|
|
|
|
2024-06-10 13:00:58 -04:00
|
|
|
<h2><Trans i18nKey="admin_plugins.available"/></h2>
|
|
|
|
<SearchField onChange={v=>{setSearchTerm(v.target.value)}} placeholder={t('admin_plugins.available_search.placeholder')} value={searchTerm}/>
|
2024-03-09 23:07:09 +01:00
|
|
|
|
2024-03-13 15:47:02 +01:00
|
|
|
<table id="available-plugins">
|
2024-03-09 23:07:09 +01:00
|
|
|
<thead>
|
|
|
|
<tr>
|
2024-08-13 21:32:40 +02:00
|
|
|
<th className={determineSorting(searchParams.sortBy, searchParams.sortDir == "asc", 'name')} onClick={()=>{
|
|
|
|
setSearchParams({
|
|
|
|
...searchParams,
|
|
|
|
sortBy: 'name',
|
|
|
|
sortDir: searchParams.sortDir === "asc"? "desc": "asc"
|
|
|
|
})
|
|
|
|
}}>
|
|
|
|
<Trans i18nKey="admin_plugins.name" /></th>
|
2024-03-09 23:07:09 +01:00
|
|
|
<th style={{width: '30%'}}><Trans i18nKey="admin_plugins.description"/></th>
|
2024-08-13 21:32:40 +02:00
|
|
|
<th className={determineSorting(searchParams.sortBy, searchParams.sortDir == "asc", 'version')} onClick={()=>{
|
|
|
|
setSearchParams({
|
|
|
|
...searchParams,
|
|
|
|
sortBy: 'version',
|
|
|
|
sortDir: searchParams.sortDir === "asc"? "desc": "asc"
|
|
|
|
})
|
|
|
|
}}><Trans i18nKey="admin_plugins.version"/></th>
|
|
|
|
<th className={determineSorting(searchParams.sortBy, searchParams.sortDir == "asc", 'last-updated')} onClick={()=>{
|
|
|
|
setSearchParams({
|
|
|
|
...searchParams,
|
|
|
|
sortBy: 'last-updated',
|
|
|
|
sortDir: searchParams.sortDir === "asc"? "desc": "asc"
|
|
|
|
})
|
|
|
|
}}><Trans i18nKey="admin_plugins.last-update"/></th>
|
2024-03-13 15:47:02 +01:00
|
|
|
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
|
2024-03-09 23:07:09 +01:00
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody style={{overflow: 'auto'}}>
|
2024-08-13 21:32:40 +02:00
|
|
|
{(filteredInstallablePlugins.length > 0) ?
|
|
|
|
filteredInstallablePlugins.map((plugin) => {
|
2024-06-10 13:00:58 -04:00
|
|
|
return <tr key={plugin.name}>
|
|
|
|
<td><a rel="noopener noreferrer" href={`https://npmjs.com/${plugin.name}`} target="_blank">{plugin.name}</a></td>
|
|
|
|
<td>{plugin.description}</td>
|
|
|
|
<td>{plugin.version}</td>
|
|
|
|
<td>{plugin.time}</td>
|
|
|
|
<td>
|
|
|
|
<IconButton icon={<Download/>} onClick={() => installPlugin(plugin.name)} title={<Trans i18nKey="admin_plugins.available_install.value"/>}/>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
})
|
|
|
|
:
|
|
|
|
<tr><td colSpan={5}>{searchTerm == '' ? <Trans i18nKey="pad.loading"/>: <Trans i18nKey="admin_plugins.available_not-found"/>}</td></tr>
|
|
|
|
}
|
2024-03-09 23:07:09 +01:00
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
}
|