2020-02-19 12:13:33 +02:00
import * as React from 'react' ;
2020-09-15 15:01:07 +02:00
import ButtonBar from './ConfigScreen/ButtonBar' ;
2020-11-07 17:59:37 +02:00
import { _ } from '@joplin/lib/locale' ;
2020-02-19 12:13:33 +02:00
const { connect } = require ( 'react-redux' ) ;
2020-11-07 17:59:37 +02:00
const { themeStyle } = require ( '@joplin/lib/theme' ) ;
2020-10-09 19:35:46 +02:00
const bridge = require ( 'electron' ) . remote . require ( './bridge' ) . default ;
2020-02-19 12:13:33 +02:00
const prettyBytes = require ( 'pretty-bytes' ) ;
2020-11-07 17:59:37 +02:00
const Resource = require ( '@joplin/lib/models/Resource.js' ) ;
2020-02-19 12:13:33 +02:00
2020-04-08 12:12:25 +02:00
interface Style {
2020-11-12 21:29:22 +02:00
width : number ;
height : number ;
2020-04-08 12:12:25 +02:00
}
2020-02-19 12:13:33 +02:00
interface Props {
2020-09-15 15:01:07 +02:00
themeId : number ;
2020-11-12 21:29:22 +02:00
style : Style ;
dispatch : Function ;
2020-02-19 12:13:33 +02:00
}
2020-11-05 18:58:23 +02:00
interface InnerResource {
2020-11-12 21:29:22 +02:00
title : string ;
id : string ;
size : number ;
file_extension : string ;
2020-02-19 12:13:33 +02:00
}
interface State {
2020-11-12 21:29:22 +02:00
resources : InnerResource [ ] | undefined ;
sorting : ActiveSorting ;
isLoading : boolean ;
2020-02-19 12:13:33 +02:00
}
interface ResourceTable {
2020-11-12 21:29:22 +02:00
resources : InnerResource [ ] ;
sorting : ActiveSorting ;
onResourceClick : ( resource : InnerResource ) = > any ;
onResourceDelete : ( resource : InnerResource ) = > any ;
onToggleSorting : ( order : SortingOrder ) = > any ;
themeId : number ;
style : Style ;
2020-02-19 12:13:33 +02:00
}
2020-11-12 21:29:22 +02:00
type SortingOrder = 'size' | 'name' ;
type SortingType = 'asc' | 'desc' ;
2020-02-19 12:13:33 +02:00
interface ActiveSorting {
2020-11-12 21:29:22 +02:00
order : SortingOrder ;
type : SortingType ;
2020-02-19 12:13:33 +02:00
}
2020-11-05 18:58:23 +02:00
const ResourceTableComp = ( props : ResourceTable ) = > {
2020-09-15 15:01:07 +02:00
const theme = themeStyle ( props . themeId ) ;
2020-02-19 12:13:33 +02:00
const sortOrderEngagedMarker = ( s : SortingOrder ) = > {
return (
2020-04-08 12:12:25 +02:00
< a href = "#"
2020-09-15 15:01:07 +02:00
style = { { color : theme.urlColor } }
2020-04-08 12:12:25 +02:00
onClick = { ( ) = > props . onToggleSorting ( s ) } > {
( props . sorting . order === s && props . sorting . type === 'desc' ) ? '▾' : '▴' } < / a >
2020-02-19 12:13:33 +02:00
) ;
} ;
2020-04-08 12:12:25 +02:00
const titleCellStyle = {
2020-09-15 15:01:07 +02:00
. . . theme . textStyle ,
2020-04-08 12:12:25 +02:00
textOverflow : 'ellipsis' ,
overflowX : 'hidden' ,
maxWidth : 1 ,
width : '100%' ,
whiteSpace : 'nowrap' ,
} ;
const cellStyle = {
2020-09-15 15:01:07 +02:00
. . . theme . textStyle ,
2020-04-08 12:12:25 +02:00
whiteSpace : 'nowrap' ,
2020-09-15 15:01:07 +02:00
color : theme.colorFaded ,
2020-04-08 12:12:25 +02:00
width : 1 ,
} ;
const headerStyle = {
2020-09-15 15:01:07 +02:00
. . . theme . textStyle ,
2020-04-08 12:12:25 +02:00
whiteSpace : 'nowrap' ,
width : 1 ,
fontWeight : 'bold' ,
} ;
2020-04-08 19:40:32 +02:00
return (
< table style = { { width : '100%' } } >
< thead >
< tr >
< th style = { headerStyle } > { _ ( 'Title' ) } { sortOrderEngagedMarker ( 'name' ) } < / th >
< th style = { headerStyle } > { _ ( 'Size' ) } { sortOrderEngagedMarker ( 'size' ) } < / th >
< th style = { headerStyle } > { _ ( 'ID' ) } < / th >
< th style = { headerStyle } > { _ ( 'Action' ) } < / th >
2020-02-19 12:13:33 +02:00
< / tr >
2020-04-08 19:40:32 +02:00
< / thead >
< tbody >
2020-11-05 18:58:23 +02:00
{ props . resources . map ( ( resource : InnerResource , index : number ) = >
2020-04-08 19:40:32 +02:00
< tr key = { index } >
< td style = { titleCellStyle } className = "titleCell" >
< a
2020-09-15 15:01:07 +02:00
style = { { color : theme.urlColor } }
2020-04-08 19:40:32 +02:00
href = "#"
onClick = { ( ) = > props . onResourceClick ( resource ) } > { resource . title || ` ( ${ _ ( 'Untitled' ) } ) ` }
< / a >
< / td >
< td style = { cellStyle } className = "dataCell" > { prettyBytes ( resource . size ) } < / td >
< td style = { cellStyle } className = "dataCell" > { resource . id } < / td >
< td style = { cellStyle } className = "dataCell" >
2020-09-15 15:01:07 +02:00
< button style = { theme . buttonStyle } onClick = { ( ) = > props . onResourceDelete ( resource ) } > { _ ( 'Delete' ) } < / button >
2020-04-08 19:40:32 +02:00
< / td >
< / tr >
) }
< / tbody >
< / table >
) ;
2020-02-19 12:13:33 +02:00
} ;
const getSortingOrderColumn = ( s : SortingOrder ) : string = > {
switch ( s ) {
case 'name' : return 'title' ;
case 'size' : return 'size' ;
}
} ;
const getNextSortingOrderType = ( s : SortingType ) : SortingType = > {
if ( s === 'asc' ) {
return 'desc' ;
} else {
return 'asc' ;
}
} ;
const MAX_RESOURCES = 10000 ;
class ResourceScreenComponent extends React . Component < Props , State > {
constructor ( props : Props ) {
super ( props ) ;
this . state = {
resources : undefined ,
sorting : {
type : 'asc' ,
order : 'name' ,
} ,
isLoading : false ,
} ;
}
async reloadResources ( sorting : ActiveSorting ) {
this . setState ( { isLoading : true } ) ;
const resources = await Resource . all ( {
order : [ {
by : getSortingOrderColumn ( sorting . order ) ,
dir : sorting.type ,
} ] ,
limit : MAX_RESOURCES ,
fields : [ 'title' , 'id' , 'size' , 'file_extension' ] ,
} ) ;
this . setState ( { resources , isLoading : false } ) ;
}
componentDidMount() {
2020-11-25 16:40:25 +02:00
void this . reloadResources ( this . state . sorting ) ;
2020-02-19 12:13:33 +02:00
}
2020-11-05 18:58:23 +02:00
onResourceDelete ( resource : InnerResource ) {
2020-04-08 12:12:25 +02:00
const ok = bridge ( ) . showConfirmMessageBox ( _ ( 'Delete attachment "%s"?' , resource . title ) , {
buttons : [ _ ( 'Delete' ) , _ ( 'Cancel' ) ] ,
defaultId : 1 ,
} ) ;
if ( ! ok ) {
return ;
}
2020-02-19 12:13:33 +02:00
Resource . delete ( resource . id )
. catch ( ( error : Error ) = > {
bridge ( ) . showErrorMessageBox ( error . message ) ;
} )
. finally ( ( ) = > {
2020-11-25 16:40:25 +02:00
void this . reloadResources ( this . state . sorting ) ;
2020-02-19 12:13:33 +02:00
} ) ;
}
2020-11-05 18:58:23 +02:00
openResource ( resource : InnerResource ) {
2020-02-19 12:13:33 +02:00
const resourcePath = Resource . fullPath ( resource ) ;
const ok = bridge ( ) . openExternal ( ` file:// ${ resourcePath } ` ) ;
if ( ! ok ) {
bridge ( ) . showErrorMessageBox ( ` This file could not be opened: ${ resourcePath } ` ) ;
}
}
onToggleSortOrder ( sortOrder : SortingOrder ) {
let newSorting = { . . . this . state . sorting } ;
if ( sortOrder === this . state . sorting . order ) {
newSorting . type = getNextSortingOrderType ( newSorting . type ) ;
} else {
newSorting = {
order : sortOrder ,
type : 'desc' ,
} ;
}
this . setState ( { sorting : newSorting } ) ;
2020-11-25 16:40:25 +02:00
void this . reloadResources ( newSorting ) ;
2020-02-19 12:13:33 +02:00
}
render() {
const style = this . props . style ;
2020-09-15 15:01:07 +02:00
const theme = themeStyle ( this . props . themeId ) ;
2020-04-08 12:12:25 +02:00
2020-11-12 21:13:28 +02:00
const rootStyle : any = {
2020-04-08 12:12:25 +02:00
. . . style ,
overflowY : 'scroll' ,
color : theme.color ,
padding : 20 ,
boxSizing : 'border-box' ,
2020-09-15 15:01:07 +02:00
flex : 1 ,
2020-04-08 12:12:25 +02:00
} ;
2020-09-15 15:01:07 +02:00
// rootStyle.height = style.height - 35; // Minus the header height
delete rootStyle . height ;
2020-04-08 12:12:25 +02:00
delete rootStyle . width ;
2020-09-15 15:01:07 +02:00
const containerHeight = style . height ;
2020-04-08 19:40:32 +02:00
return (
2020-09-15 15:01:07 +02:00
< div style = { { . . . theme . containerStyle , fontFamily : theme.fontFamily , height : containerHeight , display : 'flex' , flexDirection : 'column' } } >
2020-04-08 19:40:32 +02:00
< div style = { rootStyle } >
< div style = { { . . . theme . notificationBox , marginBottom : 10 } } > {
_ ( 'This is an advanced tool to show the attachments that are linked to your notes. Please be careful when deleting one of them as they cannot be restored afterwards.' )
} < / div >
{ this . state . isLoading && < div > { _ ( 'Please wait...' ) } < / div > }
{ ! this . state . isLoading && < div >
{ ! this . state . resources && < div >
{ _ ( 'No resources!' ) }
< / div >
}
{ this . state . resources && this . state . resources . length === MAX_RESOURCES &&
< div > { _ ( 'Warning: not all resources shown for performance reasons (limit: %s).' , MAX_RESOURCES ) } < / div >
}
2020-11-05 18:58:23 +02:00
{ this . state . resources && < ResourceTableComp
2020-09-15 15:01:07 +02:00
themeId = { this . props . themeId }
2020-04-08 19:40:32 +02:00
style = { style }
resources = { this . state . resources }
sorting = { this . state . sorting }
onToggleSorting = { ( order ) = > this . onToggleSortOrder ( order ) }
onResourceClick = { ( resource ) = > this . openResource ( resource ) }
onResourceDelete = { ( resource ) = > this . onResourceDelete ( resource ) }
/ > }
2020-02-19 12:13:33 +02:00
< / div >
}
< / div >
2020-09-15 15:01:07 +02:00
< ButtonBar
onCancelClick = { ( ) = > this . props . dispatch ( { type : 'NAV_BACK' } ) }
/ >
2020-02-19 12:13:33 +02:00
< / div >
2020-04-08 19:40:32 +02:00
) ;
2020-02-19 12:13:33 +02:00
}
}
const mapStateToProps = ( state : any ) = > ( {
2020-09-15 15:01:07 +02:00
themeId : state.settings.theme ,
2020-02-19 12:13:33 +02:00
} ) ;
const ResourceScreen = connect ( mapStateToProps ) ( ResourceScreenComponent ) ;
module .exports = { ResourceScreen } ;