2021-01-07 18:30:53 +02:00
import * as React from 'react' ;
2021-03-10 21:12:43 +02:00
import { useCallback , useMemo } from 'react' ;
2021-01-07 18:30:53 +02:00
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' ;
2021-01-24 20:20:00 +02:00
import bridge from '../../../../services/bridge' ;
2024-03-09 13:03:57 +02:00
import { ItemEvent , PluginItem } from '@joplin/lib/components/shared/config/plugins/types' ;
2024-04-03 19:51:09 +02:00
import PluginService from '@joplin/lib/services/plugins/PluginService' ;
2021-01-07 18:30:53 +02:00
export enum InstallState {
NotInstalled = 1 ,
Installing = 2 ,
Installed = 3 ,
}
2021-01-20 00:58:09 +02:00
export enum UpdateState {
Idle = 1 ,
CanUpdate = 2 ,
Updating = 3 ,
HasBeenUpdated = 4 ,
}
2021-01-07 18:30:53 +02:00
interface Props {
item? : PluginItem ;
manifest? : PluginManifest ;
installState? : InstallState ;
2021-01-20 00:58:09 +02:00
updateState? : UpdateState ;
2021-01-07 18:30:53 +02:00
themeId : number ;
2021-01-24 20:45:42 +02:00
isCompatible : boolean ;
2021-01-27 01:56:35 +02:00
onToggle ? : ( event : ItemEvent ) = > void ;
onDelete ? : ( event : ItemEvent ) = > void ;
onInstall ? : ( event : ItemEvent ) = > void ;
onUpdate ? : ( event : ItemEvent ) = > void ;
2021-01-07 18:30:53 +02:00
}
function manifestToItem ( manifest : PluginManifest ) : PluginItem {
return {
2021-01-24 20:45:42 +02:00
manifest : manifest ,
2024-06-04 10:57:52 +02:00
installed : true ,
2021-01-07 18:30:53 +02:00
enabled : true ,
deleted : false ,
devMode : false ,
2023-12-22 13:31:57 +02:00
builtIn : false ,
2021-01-20 00:58:09 +02:00
hasBeenUpdated : false ,
2021-01-07 18:30:53 +02:00
} ;
}
2023-02-16 11:23:19 +02:00
const CellRoot = styled . div < { isCompatible : boolean } > `
2021-01-07 18:30:53 +02:00
display : flex ;
2021-01-09 15:14:39 +02:00
box - sizing : border - box ;
2021-01-07 18:30:53 +02:00
background - color : $ { props = > props . theme . backgroundColor } ;
flex - direction : column ;
2023-12-11 15:58:45 +02:00
align - items : stretch ;
2021-01-07 18:30:53 +02:00
padding : 15px ;
border : 1px solid $ { props = > props . theme . dividerColor } ;
border - radius : 6px ;
2021-01-20 00:58:09 +02:00
width : 320px ;
2021-01-07 18:30:53 +02:00
margin - right : 20px ;
margin - bottom : 20px ;
box - shadow : 1px 1 px 3 px rgba ( 0 , 0 , 0 , 0.2 ) ;
2021-01-24 20:45:42 +02:00
opacity : $ { props = > props . isCompatible ? '1' : '0.6' } ;
2021-01-07 18:30:53 +02:00
` ;
const CellTop = styled . div `
display : flex ;
flex - direction : row ;
width : 100 % ;
margin - bottom : 10px ;
` ;
const CellContent = styled . div `
display : flex ;
margin - bottom : 10px ;
flex : 1 ;
` ;
const CellFooter = styled . div `
display : flex ;
flex - direction : row ;
` ;
2021-01-24 20:45:42 +02:00
const NeedUpgradeMessage = styled . span `
font - family : $ { props = > props . theme . fontFamily } ;
color : $ { props = > props . theme . colorWarn } ;
font - size : $ { props = > props . theme . fontSize } px ;
` ;
2023-12-11 15:58:45 +02:00
const BoxedLabel = styled . div `
2021-01-07 18:30:53 +02:00
border : 1px solid $ { props = > props . theme . color } ;
border - radius : 4px ;
padding : 4px 6 px ;
font - size : $ { props = > props . theme . fontSize * 0.75 } px ;
color : $ { props = > props . theme . color } ;
2023-12-11 15:58:45 +02:00
flex - grow : 0 ;
height : min - content ;
margin - top : auto ;
2021-01-07 18:30:53 +02:00
` ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-02-16 11:23:19 +02:00
const StyledNameAndVersion = styled . div < { mb : any } > `
2021-01-07 18:30:53 +02:00
font - family : $ { props = > props . theme . fontFamily } ;
color : $ { props = > props . theme . color } ;
font - size : $ { props = > props . theme . fontSize } px ;
font - weight : bold ;
2021-01-22 00:57:09 +02:00
padding - right : 5px ;
2021-01-07 18:30:53 +02:00
flex : 1 ;
` ;
2021-01-24 20:20:00 +02:00
const StyledName = styled . a `
color : $ { props = > props . theme . color } ;
& : hover {
text - decoration : underline ;
}
` ;
2021-01-22 00:57:09 +02:00
const StyledVersion = styled . span `
2021-01-20 00:58:09 +02:00
color : $ { props = > props . theme . colorFaded } ;
font - size : $ { props = > props . theme . fontSize * 0.9 } px ;
` ;
2021-01-07 18:30:53 +02:00
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.6em ;
` ;
2021-09-01 13:17:20 +02:00
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 ;
border - radius : 50px ;
opacity : 0.8 ;
& : hover {
opacity : 1 ;
}
` ;
2021-01-07 18:30:53 +02:00
export default function ( props : Props ) {
2021-03-10 21:12:43 +02:00
const item = useMemo ( ( ) = > {
return props . item ? props.item : manifestToItem ( props . manifest ) ;
} , [ props . item , props . manifest ] ) ;
2021-01-07 18:30:53 +02:00
2021-01-24 20:20:00 +02:00
const onNameClick = useCallback ( ( ) = > {
2021-03-10 21:12:43 +02:00
const manifest = item . manifest ;
if ( ! manifest . homepage_url ) return ;
2021-12-28 15:17:59 +02:00
void bridge ( ) . openExternal ( manifest . homepage_url ) ;
2021-03-10 21:12:43 +02:00
} , [ item ] ) ;
2021-01-24 20:20:00 +02:00
2021-09-01 13:17:20 +02:00
const onRecommendedClick = useCallback ( ( ) = > {
2021-12-28 15:17:59 +02:00
void bridge ( ) . openExternal ( 'https://github.com/joplin/plugins/blob/master/readme/recommended.md#recommended-plugins' ) ;
2021-09-01 13:17:20 +02:00
} , [ ] ) ;
2021-01-07 18:30:53 +02:00
// 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 ) {
2023-12-11 15:58:45 +02:00
return < BoxedLabel > DEV < / BoxedLabel > ;
2021-01-07 18:30:53 +02:00
}
return < ToggleButton
themeId = { props . themeId }
value = { item . enabled }
onToggle = { ( ) = > props . onToggle ( { item } ) }
/ > ;
}
function renderDeleteButton() {
2023-12-22 13:31:57 +02:00
// Built-in plugins can only be disabled
if ( item . builtIn ) return null ;
2021-01-07 18:30:53 +02:00
if ( ! props . onDelete ) return null ;
2023-12-22 13:31:57 +02:00
2021-01-07 18:30:53 +02:00
return < Button level = { ButtonLevel . Secondary } onClick = { ( ) = > props . onDelete ( { item } ) } title = { _ ( 'Delete' ) } / > ;
}
function renderInstallButton() {
if ( ! props . onInstall ) return null ;
let title = _ ( 'Install' ) ;
if ( props . installState === InstallState . Installing ) title = _ ( 'Installing...' ) ;
if ( props . installState === InstallState . Installed ) title = _ ( 'Installed' ) ;
return < Button
level = { ButtonLevel . Secondary }
disabled = { props . installState !== InstallState . NotInstalled }
onClick = { ( ) = > props . onInstall ( { item } ) }
title = { title }
/ > ;
}
2021-01-20 00:58:09 +02:00
function renderUpdateButton() {
if ( ! props . onUpdate ) return null ;
let title = _ ( 'Update' ) ;
if ( props . updateState === UpdateState . Updating ) title = _ ( 'Updating...' ) ;
if ( props . updateState === UpdateState . Idle ) title = _ ( 'Updated' ) ;
if ( props . updateState === UpdateState . HasBeenUpdated ) title = _ ( 'Updated' ) ;
return < Button
ml = { 1 }
level = { ButtonLevel . Recommended }
onClick = { ( ) = > props . onUpdate ( { item } ) }
title = { title }
disabled = { props . updateState === UpdateState . HasBeenUpdated }
/ > ;
}
2023-12-11 15:58:45 +02:00
const renderDefaultPluginLabel = ( ) = > {
2023-12-22 13:31:57 +02:00
if ( item . builtIn ) {
2023-12-11 15:58:45 +02:00
return (
2024-02-06 18:04:29 +02:00
< BoxedLabel > { _ ( 'Built-in' ) } < / BoxedLabel >
2023-12-11 15:58:45 +02:00
) ;
}
return null ;
} ;
2021-01-07 18:30:53 +02:00
function renderFooter() {
if ( item . devMode ) return null ;
2021-01-24 20:45:42 +02:00
if ( ! props . isCompatible ) {
return (
< CellFooter >
< NeedUpgradeMessage >
2024-04-03 19:51:09 +02:00
{ PluginService . instance ( ) . describeIncompatibility ( props . manifest ) }
2021-01-24 20:45:42 +02:00
< / NeedUpgradeMessage >
< / CellFooter >
) ;
}
2021-01-07 18:30:53 +02:00
return (
< CellFooter >
{ renderDeleteButton ( ) }
{ renderInstallButton ( ) }
2021-01-20 00:58:09 +02:00
{ renderUpdateButton ( ) }
2021-01-07 18:30:53 +02:00
< div style = { { display : 'flex' , flex : 1 } } / >
2023-12-11 15:58:45 +02:00
{ renderDefaultPluginLabel ( ) }
2021-01-07 18:30:53 +02:00
< / CellFooter >
) ;
}
2021-09-01 13:17:20 +02:00
function renderRecommendedBadge() {
if ( props . onToggle ) return null ;
if ( ! item . manifest . _recommended ) return null ;
return < RecommendedBadge href = "#" title = { _ ( 'The Joplin team has vetted this plugin and it meets our standards for security and performance.' ) } onClick = { onRecommendedClick } > < i className = "fas fa-crown" > < / i > < / RecommendedBadge > ;
}
2021-01-07 18:30:53 +02:00
return (
2021-01-24 20:45:42 +02:00
< CellRoot isCompatible = { props . isCompatible } >
2021-01-07 18:30:53 +02:00
< CellTop >
2021-01-24 20:45:42 +02:00
< StyledNameAndVersion mb = { '5px' } > < StyledName onClick = { onNameClick } href = "#" style = { { marginRight : 5 } } > { item . manifest . name } { item . deleted ? _ ( '(%s)' , 'Deleted' ) : '' } < / StyledName > < StyledVersion > v { item . manifest . version } < / StyledVersion > < / StyledNameAndVersion >
2021-01-07 18:30:53 +02:00
{ renderToggleButton ( ) }
2021-09-01 13:17:20 +02:00
{ renderRecommendedBadge ( ) }
2021-01-07 18:30:53 +02:00
< / CellTop >
< CellContent >
2021-01-24 20:45:42 +02:00
< StyledDescription > { item . manifest . description } < / StyledDescription >
2021-01-07 18:30:53 +02:00
< / CellContent >
{ renderFooter ( ) }
< / CellRoot >
) ;
}