install/uninstall plugin

This commit is contained in:
Jan Prochazka
2020-11-21 17:33:59 +01:00
parent 1f4a93f1d5
commit e14165c403
9 changed files with 234 additions and 83 deletions

View File

@@ -1,7 +0,0 @@
import React from 'react';
export default function PluginIcon({ plugin, className = undefined }) {
return (
<img src="https://raw.githubusercontent.com/dbshell/dbgate-plugin-csv/master/icon.svg" className={className} />
);
}

View File

@@ -3,7 +3,7 @@ import styled from 'styled-components';
import useTheme from '../theme/useTheme';
import { openNewTab } from '../utility/common';
import { useSetOpenedTabs } from '../utility/globalState';
import PluginIcon from './PluginIcon';
import { extractPluginIcon, extractPluginAuthor } from '../plugins/manifestExtractors';
const Wrapper = styled.div`
margin: 1px 3px 10px 5px;
@@ -26,7 +26,7 @@ const Line = styled.div`
display: flex;
`;
const Icon = styled(PluginIcon)`
const Icon = styled.img`
width: 50px;
height: 50px;
`;
@@ -43,33 +43,33 @@ const Version = styled.div`
margin-left: 5px;
`;
function openPlugin(setOpenedTabs, plugin) {
function openPlugin(setOpenedTabs, packageManifest) {
openNewTab(setOpenedTabs, {
title: plugin.package.name,
title: packageManifest.name,
icon: 'icon plugin',
tabComponent: 'PluginTab',
props: {
plugin,
packageName: packageManifest.name,
},
});
}
function PluginsListItem({ plugin }) {
function PluginsListItem({ packageManifest }) {
const setOpenedTabs = useSetOpenedTabs();
const theme = useTheme();
return (
<Wrapper onClick={() => openPlugin(setOpenedTabs, plugin)} theme={theme}>
<Icon plugin={plugin} />
<Wrapper onClick={() => openPlugin(setOpenedTabs, packageManifest)} theme={theme}>
<Icon src={extractPluginIcon(packageManifest)} />
<Texts>
<Line>
<Name>{plugin.package.name}</Name>
<Version>{plugin.package.version}</Version>
<Name>{packageManifest.name}</Name>
<Version>{packageManifest.version}</Version>
</Line>
<Line>
<Description>{plugin.package.description}</Description>
<Description>{packageManifest.description}</Description>
</Line>
<Line>
<Author>{plugin.package.author && plugin.package.author.name}</Author>
<Author>{extractPluginAuthor(packageManifest)}</Author>
</Line>
</Texts>
</Wrapper>
@@ -79,8 +79,8 @@ function PluginsListItem({ plugin }) {
export default function PluginsList({ plugins }) {
return (
<>
{plugins.map((plugin) => (
<PluginsListItem plugin={plugin} key={plugin.package.name} />
{plugins.map((packageManifest) => (
<PluginsListItem packageManifest={packageManifest} key={packageManifest.name} />
))}
</>
);

View File

@@ -1,23 +1,40 @@
import React from 'react';
import _ from 'lodash';
import axios from '../utility/axios';
import { useInstalledPlugins } from '../utility/metadataLoaders';
const PluginsContext = React.createContext(null);
export default function PluginsProvider({ children }) {
const [plugins, setPlugins] = React.useState(null);
const handleLoadPlugin = async () => {
const resp = await axios.request({
method: 'get',
url: 'plugins/script',
params: {
plugin: 'csv',
},
});
const module = eval(resp.data);
console.log('MODULE', module);
const installedPlugins = useInstalledPlugins();
const [plugins, setPlugins] = React.useState({});
const handleLoadPlugins = async () => {
setPlugins((x) =>
_.pick(
x,
installedPlugins.map((y) => y.name)
)
);
for (const installed of installedPlugins) {
if (!_.keys(plugins).includes(installed.name)) {
console.log('Loading module', installed.name);
const resp = await axios.request({
method: 'get',
url: 'plugins/script',
params: {
packageName: installed.name,
},
});
const module = eval(resp.data);
setPlugins((v) => ({
...v,
[installed.name]: module,
}));
}
}
};
React.useEffect(() => {
handleLoadPlugin();
}, []);
return <PluginsContext.Provider value={{ plugins, setPlugins }}>{children}</PluginsContext.Provider>;
handleLoadPlugins();
}, [installedPlugins]);
return <PluginsContext.Provider value={plugins}>{children}</PluginsContext.Provider>;
}

View File

@@ -0,0 +1,20 @@
import _ from 'lodash';
export function extractPluginIcon(packageManifest) {
const { links } = packageManifest || {};
const { repository, homepage } = links || {};
const tested = repository || homepage || packageManifest.homepage;
if (tested) {
const match = tested.match(/https:\/\/github.com\/([^/]*)\/([^/]*)/);
if (match) {
return `https://raw.githubusercontent.com/${match[1]}/${match[2]}/master/icon.svg`;
}
}
// eslint-disable-next-line no-undef
return `${process.env.PUBLIC_URL}/unknown.svg`;
}
export function extractPluginAuthor(packageManifest) {
return _.isPlainObject(packageManifest.author) ? packageManifest.author.name : packageManifest.author;
}

View File

@@ -2,14 +2,13 @@ import React from 'react';
import styled from 'styled-components';
import _ from 'lodash';
import ReactMarkdown from 'react-markdown';
import ObjectListControl from '../utility/ObjectListControl';
import { TableColumn } from '../utility/TableControl';
import columnAppObject from '../appobj/columnAppObject';
import constraintAppObject from '../appobj/constraintAppObject';
import { useTableInfo, useDbCore } from '../utility/metadataLoaders';
import useTheme from '../theme/useTheme';
import useFetch from '../utility/useFetch';
import LoadingInfo from '../widgets/LoadingInfo';
import { extractPluginIcon, extractPluginAuthor } from '../plugins/manifestExtractors';
import FormStyledButton from '../widgets/FormStyledButton';
import axios from '../utility/axios';
import { useInstalledPlugins } from '../utility/metadataLoaders';
const WhitePage = styled.div`
position: absolute;
@@ -22,23 +21,84 @@ const WhitePage = styled.div`
padding: 10px;
`;
const Title = styled.div`
font-size: 20pt;
border-bottom: 1px solid ${(props) => props.theme.border};
const Icon = styled.img`
width: 80px;
height: 80px;
`;
export default function PluginTab({ plugin }) {
const Header = styled.div`
display: flex;
border-bottom: 1px solid ${(props) => props.theme.border};
margin-bottom: 20px;
padding-bottom: 20px;
`;
const HeaderBody = styled.div`
margin-left: 10px;
`;
const Title = styled.div`
font-size: 20pt;
`;
const HeaderLine = styled.div`
margin-top: 5px;
`;
const Author = styled.span`
font-weight: bold;
`;
const Version = styled.span``;
function Delimiter() {
return <span> | </span>;
}
export default function PluginTab({ packageName }) {
const theme = useTheme();
const packageName = plugin.package.name;
const readme = useFetch({
const installed = useInstalledPlugins();
const info = useFetch({
params: { packageName },
url: 'plugins/readme',
url: 'plugins/info',
defaultValue: null,
});
const { readme, manifest } = info || {};
const handleInstall = async () => {
axios.post('plugins/install', { packageName });
};
const handleUninstall = async () => {
axios.post('plugins/uninstall', { packageName });
};
return (
<WhitePage theme={theme}>
<Title theme={theme}>{packageName}</Title>
{readme == null ? <LoadingInfo message="Loading extension detail" /> : <ReactMarkdown>{readme}</ReactMarkdown>}
{info == null || manifest == null ? (
<LoadingInfo message="Loading extension detail" />
) : (
<>
<Header theme={theme}>
<Icon src={extractPluginIcon(manifest)} />
<HeaderBody>
<Title theme={theme}>{packageName}</Title>
<HeaderLine>
<Author>{extractPluginAuthor(manifest)}</Author>
<Delimiter />
<Version>{manifest.version && manifest.version}</Version>
</HeaderLine>
<HeaderLine>
{!installed.find((x) => x.name == packageName) && (
<FormStyledButton type="button" value="Install" onClick={handleInstall} />
)}
{!!installed.find((x) => x.name == packageName) && (
<FormStyledButton type="button" value="Uninstall" onClick={handleUninstall} />
)}
</HeaderLine>
</HeaderBody>
</Header>
<ReactMarkdown>{readme}</ReactMarkdown>
</>
)}
</WhitePage>
);
}

View File

@@ -88,6 +88,12 @@ const connectionListLoader = () => ({
reloadTrigger: `connection-list-changed`,
});
const insttalledPluginsLoader = () => ({
url: 'plugins/installed',
params: {},
reloadTrigger: `installed-plugins-changed`,
});
async function getCore(loader, args) {
const { url, params, reloadTrigger, transform } = loader(args);
const key = stableStringify({ url, ...params });
@@ -243,3 +249,10 @@ export function getArchiveFolders(args) {
export function useArchiveFolders(args) {
return useCore(archiveFoldersLoader, args);
}
export function getInstalledPlugins(args) {
return getCore(insttalledPluginsLoader, args) || [];
}
export function useInstalledPlugins(args) {
return useCore(insttalledPluginsLoader, args) || [];
}

View File

@@ -1,37 +1,17 @@
import React from 'react';
import _ from 'lodash';
import { AppObjectList } from '../appobj/AppObjectList';
import { useCurrentArchive, useSetCurrentArchive } from '../utility/globalState';
import { SearchBoxWrapper, WidgetsInnerContainer } from './WidgetStyles';
import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar';
import { useArchiveFiles, useArchiveFolders } from '../utility/metadataLoaders';
import archiveFolderAppObject from '../appobj/archiveFolderAppObject';
import archiveFileAppObject from '../appobj/archiveFileAppObject';
import { useInstalledPlugins } from '../utility/metadataLoaders';
import SearchInput from './SearchInput';
import InlineButton from './InlineButton';
import axios from '../utility/axios';
import useFetch from '../utility/useFetch';
import PluginsList from '../plugins/PluginsList';
function InstalledPluginsList() {
// const folders = useArchiveFolders();
// const [filter, setFilter] = React.useState('');
// const setArchive = useSetCurrentArchive();
// const handleRefreshFolders = () => {
// axios.post('archive/refresh-folders', {});
// };
const plugins = useInstalledPlugins();
return (
<WidgetsInnerContainer>
{/* <AppObjectList
list={_.sortBy(folders, 'name')}
makeAppObj={archiveFolderAppObject()}
onObjectClick={(archive) => setArchive(archive.name)}
filter={filter}
/> */}
<PluginsList plugins={plugins} />
</WidgetsInnerContainer>
);
}