import * as React from 'react';
import { useCallback, useId, useMemo } from 'react';
import { _ } from '@joplin/lib/locale';
import styled from 'styled-components';
import ToggleButton from '../../../lib/ToggleButton/ToggleButton';
import Button, { ButtonLevel } from '../../../Button/Button';
import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
import bridge from '../../../../services/bridge';
import { ItemEvent, PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
import PluginService from '@joplin/lib/services/plugins/PluginService';
import getPluginHelpUrl from '@joplin/lib/services/plugins/utils/getPluginHelpUrl';
export enum InstallState {
NotInstalled = 1,
Installing = 2,
Installed = 3,
}
export enum UpdateState {
Idle = 1,
CanUpdate = 2,
Updating = 3,
HasBeenUpdated = 4,
}
interface Props {
item?: PluginItem;
manifest?: PluginManifest;
installState?: InstallState;
updateState?: UpdateState;
themeId: number;
isCompatible: boolean;
onToggle?: (event: ItemEvent)=> void;
onDelete?: (event: ItemEvent)=> void;
onInstall?: (event: ItemEvent)=> void;
onUpdate?: (event: ItemEvent)=> void;
}
function manifestToItem(manifest: PluginManifest): PluginItem {
return {
manifest: manifest,
installed: true,
enabled: true,
deleted: false,
devMode: false,
builtIn: false,
hasBeenUpdated: false,
};
}
const CellRoot = styled.div<{ isCompatible: boolean }>`
display: flex;
box-sizing: border-box;
background-color: ${props => props.theme.backgroundColor};
flex-direction: column;
align-items: stretch;
min-height: 164px;
padding: 20px;
border: 1px solid ${props => props.theme.dividerColor};
border-radius: 8px;
width: 100%;
box-shadow: 0 1px 2px rgba(0,0,0,0.06), 0 8px 20px rgba(0,0,0,0.04);
transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease;
opacity: ${props => props.isCompatible ? '1' : '0.6'};
&:hover {
border-color: ${props => props.theme.color};
box-shadow: 0 3px 6px rgba(0,0,0,0.07), 0 12px 28px rgba(0,0,0,0.08);
transform: translateY(-1px);
}
&:focus-within {
border-color: ${props => props.theme.color};
box-shadow: 0 0 0 2px ${props => props.theme.color}33, 0 3px 6px rgba(0,0,0,0.07);
}
@media (prefers-reduced-motion: reduce) {
transition: none;
&:hover { transform: none; }
}
`;
const CellTop = styled.div`
display: flex;
flex-direction: row;
width: 100%;
margin-bottom: 14px;
align-items: flex-start;
`;
const CellContent = styled.div`
display: flex;
margin-bottom: 10px;
flex: 1;
`;
const CellFooter = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
min-height: 32px;
`;
const NeedUpgradeMessage = styled.span`
font-family: ${props => props.theme.fontFamily};
color: ${props => props.theme.colorWarn};
font-size: ${props => props.theme.fontSize}px;
`;
const BoxedLabel = styled.div`
border: 1px solid ${props => props.theme.color};
border-radius: 6px;
padding: 5px 8px;
font-size: ${props => props.theme.fontSize * 0.78}px;
font-weight: 600;
color: ${props => props.theme.color};
flex-grow: 0;
height: min-content;
margin-top: auto;
`;
const StyledNameAndVersion = styled.div<{ mb: string | number }>`
font-family: ${props => props.theme.fontFamily};
color: ${props => props.theme.color};
font-size: ${props => props.theme.fontSize * 1.08}px;
font-weight: 650;
padding-right: 12px;
flex: 1;
`;
const StyledName = styled.a`
color: ${props => props.theme.color};
&:hover {
text-decoration: underline;
}
`;
const StyledVersion = styled.span`
color: ${props => props.theme.colorFaded};
font-size: ${props => props.theme.fontSize * 0.82}px;
font-weight: 500;
white-space: nowrap;
`;
const StyledDescription = styled.div`
font-family: ${props => props.theme.fontFamily};
color: ${props => props.theme.colorFaded};
font-size: ${props => props.theme.fontSize}px;
line-height: 1.55;
`;
const RecommendedBadge = styled.a`
font-family: ${props => props.theme.fontFamily};
color: ${props => props.theme.colorWarn};
font-size: ${props => props.theme.fontSize}px;
border: 1px solid ${props => props.theme.colorWarn};
padding: 5px 8px;
border-radius: 6px;
opacity: 0.8;
&:hover {
opacity: 1;
}
`;
export default function(props: Props) {
const item = useMemo(() => {
return props.item ? props.item : manifestToItem(props.manifest);
}, [props.item, props.manifest]);
const onNameClick = useCallback(() => {
const manifest = item.manifest;
void bridge().openExternal(getPluginHelpUrl(manifest.id));
}, [item]);
const onRecommendedClick = useCallback(() => {
void bridge().openExternal('https://github.com/joplin/plugins/blob/master/readme/recommended.md#recommended-plugins');
}, []);
// For plugins in dev mode things like enabling/disabling or
// uninstalling them doesn't make sense, as that should be done by
// adding/removing them from wherever they were loaded from.
function renderToggleButton() {
if (!props.onToggle) return null;
if (item.devMode) {
return DEV;
}
return props.onToggle({ item })}
aria-label={item.enabled ? _('Disable %s', item.manifest.name) : _('Enable %s', item.manifest.name)}
/>;
}
function renderDeleteButton() {
// Built-in plugins can only be disabled
if (item.builtIn) return null;
if (!props.onDelete) return null;
return