1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-26 18:58:21 +02:00

Desktop: Resolves #2639: Resource screen improvements (#2850)

Co-authored-by: Georg Grab <talkdirty@users.noreply.github.com>
This commit is contained in:
Laurent Cozic 2020-04-08 11:12:25 +01:00
parent 6d56bb8afd
commit 6b8e84332d
8 changed files with 98 additions and 24 deletions

View File

@ -622,7 +622,7 @@ class Application extends BaseApplication {
// the following menu items will be available for all OS under Tools
const toolsItemsAll = [{
label: _('Resources'),
label: _('Note attachments...'),
click: () => {
this.dispatch({
type: 'NAV_GO',

View File

@ -267,6 +267,7 @@ class HeaderComponent extends React.Component {
boxSizing: 'border-box',
cursor: 'default',
whiteSpace: 'nowrap',
userSelect: 'none',
};
if (showBackButton) {

View File

@ -8,9 +8,14 @@ const { Header } = require('./Header.min.js');
const prettyBytes = require('pretty-bytes');
const Resource = require('lib/models/Resource.js');
interface Style {
width: number
height: number
}
interface Props {
style: any;
theme: any;
style: Style
}
interface Resource {
@ -32,6 +37,8 @@ interface ResourceTable {
onResourceClick: (resource: Resource) => any
onResourceDelete: (resource: Resource) => any
onToggleSorting: (order: SortingOrder) => any
theme: any
style: Style
}
type SortingOrder = 'size' | 'name'
@ -45,29 +52,58 @@ interface ActiveSorting {
const ResourceTable: React.FC<ResourceTable> = (props: ResourceTable) => {
const sortOrderEngagedMarker = (s: SortingOrder) => {
return (
<a href="#" onClick={ () => props.onToggleSorting(s) }>{
(props.sorting.order === s && props.sorting.type === 'desc') ? '▾' : '▴' }</a>
<a href="#"
style={{ color: props.theme.htmlLinkColor }}
onClick={() => props.onToggleSorting(s)}>{
(props.sorting.order === s && props.sorting.type === 'desc') ? '▾' : '▴'}</a>
);
};
return <table style={{ width: '90%' }}>
const titleCellStyle = {
...props.theme.textStyle,
textOverflow: 'ellipsis',
overflowX: 'hidden',
maxWidth: 1,
width: '100%',
whiteSpace: 'nowrap',
};
const cellStyle = {
...props.theme.textStyleMinor,
whiteSpace: 'nowrap',
width: 1,
};
const headerStyle = {
...props.theme.textStyle,
whiteSpace: 'nowrap',
width: 1,
fontWeight: 'bold',
};
return <table style={{ width: '100%' }}>
<thead>
<tr>
<th>{_('Title')} { sortOrderEngagedMarker('name') }</th>
<th>{_('Size')} { sortOrderEngagedMarker('size') }</th>
<th>{_('ID')}</th>
<th>{_('Action')}</th>
<th style={headerStyle}>{_('Title')} {sortOrderEngagedMarker('name')}</th>
<th style={headerStyle}>{_('Size')} {sortOrderEngagedMarker('size')}</th>
<th style={headerStyle}>{_('ID')}</th>
<th style={headerStyle}>{_('Action')}</th>
</tr>
</thead>
<tbody>
{props.resources.map((resource: Resource, index: number) =>
<tr key={index}>
<td>
<a href="#" onClick={() => props.onResourceClick(resource)}>{resource.title}</a>
<td style={titleCellStyle} className="titleCell">
<a
style={{ color: props.theme.htmlLinkColor }}
href="#"
onClick={() => props.onResourceClick(resource)}>{resource.title || `(${_('Untitled')})`}
</a>
</td>
<td>{prettyBytes(resource.size)}</td>
<td>{resource.id}</td>
<td>
<button onClick={ () => props.onResourceDelete(resource) }>{_('Delete')}</button>
<td style={cellStyle} className="dataCell">{prettyBytes(resource.size)}</td>
<td style={cellStyle} className="dataCell">{resource.id}</td>
<td style={cellStyle} className="dataCell">
<button style={props.theme.buttonStyle} onClick={() => props.onResourceDelete(resource)}>{_('Delete')}</button>
</td>
</tr>
)}
@ -123,6 +159,13 @@ class ResourceScreenComponent extends React.Component<Props, State> {
}
onResourceDelete(resource: Resource) {
const ok = bridge().showConfirmMessageBox(_('Delete attachment "%s"?', resource.title), {
buttons: [_('Delete'), _('Cancel')],
defaultId: 1,
});
if (!ok) {
return;
}
Resource.delete(resource.id)
.catch((error: Error) => {
bridge().showErrorMessageBox(error.message);
@ -158,9 +201,23 @@ class ResourceScreenComponent extends React.Component<Props, State> {
const style = this.props.style;
const theme = themeStyle(this.props.theme);
const headerStyle = Object.assign({}, theme.headerStyle, { width: style.width });
return <div>
const rootStyle = {
...style,
overflowY: 'scroll',
color: theme.color,
padding: 20,
boxSizing: 'border-box',
};
rootStyle.height = style.height - 35; // Minus the header height
delete rootStyle.width;
return <div style={{ ...theme.containerStyle, fontFamily: theme.fontFamily }}>
<Header style={headerStyle} />
<div style={{ ...style, margin: '20px', overflow: 'scroll' }}>
<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>
@ -171,11 +228,13 @@ class ResourceScreenComponent extends React.Component<Props, State> {
<div>{_('Warning: not all resources shown for performance reasons (limit: %s).', MAX_RESOURCES)}</div>
}
{this.state.resources && <ResourceTable
resources={ this.state.resources }
sorting={ this.state.sorting }
onToggleSorting={ (order) => this.onToggleSortOrder(order) }
onResourceClick={ (resource) => this.openResource(resource) }
onResourceDelete={ (resource) => this.onResourceDelete(resource) }
theme={theme}
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)}
/>}
</div>
}

View File

@ -92,7 +92,7 @@ class RootComponent extends React.Component {
DropboxLogin: { screen: DropboxLoginScreen, title: () => _('Dropbox Login') },
Import: { screen: ImportScreen, title: () => _('Import') },
Config: { screen: ConfigScreen, title: () => _('Options') },
Resources: { screen: ResourceScreen, title: () => _('Resources') },
Resources: { screen: ResourceScreen, title: () => _('Note attachments') },
Status: { screen: StatusScreen, title: () => _('Synchronisation Status') },
};

View File

@ -131,6 +131,7 @@ class SideBarComponent extends React.Component {
display: 'flex',
flex: 1,
alignItems: 'center',
userSelect: 'none',
},
listItemSelected: {
backgroundColor: theme.selectedColor2,
@ -160,6 +161,7 @@ class SideBarComponent extends React.Component {
paddingLeft: 8,
display: 'flex',
alignItems: 'center',
userSelect: 'none',
},
button: {
padding: 6,
@ -176,6 +178,7 @@ class SideBarComponent extends React.Component {
marginLeft: 5,
marginRight: 5,
cursor: 'default',
userSelect: 'none',
},
syncReport: {
fontFamily: theme.fontFamily,
@ -195,6 +198,7 @@ class SideBarComponent extends React.Component {
noteCount: {
paddingLeft: 5,
opacity: 0.5,
userSelect: 'none',
},
};

View File

@ -16,6 +16,7 @@ const style = createSelector(
alignItems: 'center',
padding: 9,
backgroundColor: theme.backgroundColor,
userSelect: 'none',
},
buttonIcon: {
fontSize: 24,

View File

@ -5,7 +5,7 @@ const darkStyle = {
color: '#dddddd',
colorError: 'red',
colorWarn: '#9A5B00',
colorFaded: '#777777', // For less important text
colorFaded: '#999999', // For less important text
colorBright: '#ffffff', // For important text
dividerColor: '#555555',
selectedColor: '#333333',

View File

@ -201,6 +201,14 @@ function addExtraStyles(style) {
marginRight: 6,
};
style.notificationBox = {
backgroundColor: style.warningBackgroundColor,
display: 'flex',
alignItems: 'center',
padding: 10,
fontSize: style.fontSize,
};
style.dialogTitle = Object.assign({}, style.h1Style, { marginBottom: '1.2em' });
style.dropdownList = Object.assign({}, style.inputStyle);
@ -299,6 +307,7 @@ function themeStyle(theme) {
color: output.color,
backgroundColor: output.backgroundColor,
borderColor: output.dividerColor,
userSelect: 'none',
}
);