import * as React from 'react'; import ButtonBar from './ConfigScreen/ButtonBar'; import { _ } from '@joplin/lib/locale'; const { connect } = require('react-redux'); const { themeStyle } = require('@joplin/lib/theme'); const bridge = require('@electron/remote').require('./bridge').default; const prettyBytes = require('pretty-bytes'); import Resource from '@joplin/lib/models/Resource'; interface Style { width: number; height: number; } interface Props { themeId: number; style: Style; // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied dispatch: Function; } interface InnerResource { title: string; id: string; size: number; file_extension: string; } interface State { resources: InnerResource[] | undefined; sorting: ActiveSorting; isLoading: boolean; } interface ResourceTable { resources: InnerResource[]; sorting: ActiveSorting; onResourceClick: (resource: InnerResource)=> any; onResourceDelete: (resource: InnerResource)=> any; onToggleSorting: (order: SortingOrder)=> any; themeId: number; style: Style; } type SortingOrder = 'size' | 'name'; type SortingType = 'asc' | 'desc'; interface ActiveSorting { order: SortingOrder; type: SortingType; } const ResourceTableComp = (props: ResourceTable) => { const theme = themeStyle(props.themeId); const sortOrderEngagedMarker = (s: SortingOrder) => { return ( props.onToggleSorting(s)}>{ (props.sorting.order === s && props.sorting.type === 'desc') ? '▾' : '▴'} ); }; const titleCellStyle = { ...theme.textStyle, textOverflow: 'ellipsis', overflowX: 'hidden', maxWidth: 1, width: '100%', whiteSpace: 'nowrap', }; const cellStyle = { ...theme.textStyle, whiteSpace: 'nowrap', color: theme.colorFaded, width: 1, }; const headerStyle = { ...theme.textStyle, whiteSpace: 'nowrap', width: 1, fontWeight: 'bold', }; return ( {props.resources.map((resource: InnerResource, index: number) => )}
{_('Title')} {sortOrderEngagedMarker('name')} {_('Size')} {sortOrderEngagedMarker('size')} {_('ID')} {_('Action')}
props.onResourceClick(resource)}>{resource.title || `(${_('Untitled')})`} {prettyBytes(resource.size)} {resource.id}
); }; 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 { public constructor(props: Props) { super(props); this.state = { resources: undefined, sorting: { type: 'asc', order: 'name', }, isLoading: false, }; } public async reloadResources(sorting: ActiveSorting) { this.setState({ isLoading: true }); const resources = await Resource.all({ order: [{ by: getSortingOrderColumn(sorting.order), dir: sorting.type, caseInsensitive: true, }], limit: MAX_RESOURCES, fields: ['title', 'id', 'size', 'file_extension'], }); this.setState({ resources, isLoading: false }); } public componentDidMount() { void this.reloadResources(this.state.sorting); } public onResourceDelete(resource: InnerResource) { const ok = bridge().showConfirmMessageBox(_('Delete attachment "%s"?', resource.title), { buttons: [_('Delete'), _('Cancel')], defaultId: 1, }); if (!ok) { return; } Resource.delete(resource.id) // eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied .catch((error: Error) => { bridge().showErrorMessageBox(error.message); }) // eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied .finally(() => { void this.reloadResources(this.state.sorting); }); } public openResource(resource: InnerResource) { const resourcePath = Resource.fullPath(resource); const ok = bridge().openExternal(`file://${resourcePath}`); if (!ok) { bridge().showErrorMessageBox(`This file could not be opened: ${resourcePath}`); } } public 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 }); void this.reloadResources(newSorting); } public render() { const style = this.props.style; const theme = themeStyle(this.props.themeId); const rootStyle: any = { ...style, overflowY: 'scroll', color: theme.color, padding: 20, boxSizing: 'border-box', flex: 1, }; // rootStyle.height = style.height - 35; // Minus the header height delete rootStyle.height; delete rootStyle.width; const containerHeight = style.height; return (
{ _('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.') }
{this.state.isLoading &&
{_('Please wait...')}
} {!this.state.isLoading &&
{!this.state.resources &&
{_('No resources!')}
} {this.state.resources && this.state.resources.length === MAX_RESOURCES &&
{_('Warning: not all resources shown for performance reasons (limit: %s).', MAX_RESOURCES)}
} {this.state.resources && this.onToggleSortOrder(order)} onResourceClick={(resource) => this.openResource(resource)} onResourceDelete={(resource) => this.onResourceDelete(resource)} />}
}
this.props.dispatch({ type: 'NAV_BACK' })} />
); } } const mapStateToProps = (state: any) => ({ themeId: state.settings.theme, }); const ResourceScreen = connect(mapStateToProps)(ResourceScreenComponent); module.exports = { ResourceScreen };