1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Desktop: Note attachments screen: Allow searching for attachments (#10442)

This commit is contained in:
Henry Heino 2024-05-21 02:14:39 -07:00 committed by GitHub
parent c5e3672e9e
commit 2dd27cdd00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -7,6 +7,7 @@ const { themeStyle } = require('@joplin/lib/theme');
import bridge from '../services/bridge'; import bridge from '../services/bridge';
const prettyBytes = require('pretty-bytes'); const prettyBytes = require('pretty-bytes');
import Resource from '@joplin/lib/models/Resource'; import Resource from '@joplin/lib/models/Resource';
import { LoadOptions } from '@joplin/lib/models/utils/types';
interface Style { interface Style {
width: number; width: number;
@ -31,6 +32,7 @@ interface State {
resources: InnerResource[] | undefined; resources: InnerResource[] | undefined;
sorting: ActiveSorting; sorting: ActiveSorting;
isLoading: boolean; isLoading: boolean;
filter: string;
} }
interface ResourceTable { interface ResourceTable {
@ -42,6 +44,7 @@ interface ResourceTable {
onResourceDelete: (resource: InnerResource)=> any; onResourceDelete: (resource: InnerResource)=> any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onToggleSorting: (order: SortingOrder)=> any; onToggleSorting: (order: SortingOrder)=> any;
filter: string;
themeId: number; themeId: number;
style: Style; style: Style;
} }
@ -89,6 +92,10 @@ const ResourceTableComp = (props: ResourceTable) => {
fontWeight: 'bold', fontWeight: 'bold',
}; };
const filteredResources = props.resources.filter(
(resource: InnerResource) => !props.filter || resource.title?.includes(props.filter) || resource.id.includes(props.filter),
);
return ( return (
<table style={{ width: '100%' }}> <table style={{ width: '100%' }}>
<thead> <thead>
@ -100,7 +107,7 @@ const ResourceTableComp = (props: ResourceTable) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{props.resources.map((resource: InnerResource, index: number) => {filteredResources.map((resource: InnerResource, index: number) =>
<tr key={index}> <tr key={index}>
<td style={titleCellStyle} className="titleCell"> <td style={titleCellStyle} className="titleCell">
<a <a
@ -136,13 +143,15 @@ const getNextSortingOrderType = (s: SortingType): SortingType => {
} }
}; };
const MAX_RESOURCES = 10000; const defaultMaxResources = 10000;
const searchMaxResources = 1000;
class ResourceScreenComponent extends React.Component<Props, State> { class ResourceScreenComponent extends React.Component<Props, State> {
public constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
resources: undefined, resources: undefined,
filter: '',
sorting: { sorting: {
type: 'asc', type: 'asc',
order: 'name', order: 'name',
@ -151,22 +160,57 @@ class ResourceScreenComponent extends React.Component<Props, State> {
}; };
} }
public async reloadResources(sorting: ActiveSorting) { private get maxResources() {
// Use a smaller maximum when searching for performance -- results update
// when the search input changes.
if (this.state.filter) {
return searchMaxResources;
} else {
return defaultMaxResources;
}
}
private reloadResourcesCounter = 0;
public async reloadResources() {
this.setState({ isLoading: true }); this.setState({ isLoading: true });
this.reloadResourcesCounter ++;
const currentCounterValue = this.reloadResourcesCounter;
let searchOptions: Partial<LoadOptions> = {};
if (this.state.filter) {
const search = `%${this.state.filter}%`;
searchOptions = {
where: 'id LIKE ? OR title LIKE ?',
whereParams: [search, search],
};
}
const resources = await Resource.all({ const resources = await Resource.all({
order: [{ order: [{
by: getSortingOrderColumn(sorting.order), by: getSortingOrderColumn(this.state.sorting.order),
dir: sorting.type, dir: this.state.sorting.type,
caseInsensitive: true, caseInsensitive: true,
}], }],
limit: MAX_RESOURCES, limit: this.maxResources,
fields: ['title', 'id', 'size', 'file_extension'], fields: ['title', 'id', 'size', 'file_extension'],
...searchOptions,
}); });
const cancelled = currentCounterValue !== this.reloadResourcesCounter;
if (cancelled) return;
this.setState({ resources, isLoading: false }); this.setState({ resources, isLoading: false });
} }
public componentDidMount() { public componentDidMount() {
void this.reloadResources(this.state.sorting); void this.reloadResources();
}
public componentDidUpdate(_prevProps: Props, prevState: State) {
if (prevState.sorting !== this.state.sorting || prevState.filter !== this.state.filter) {
void this.reloadResources();
}
} }
public onResourceDelete(resource: InnerResource) { public onResourceDelete(resource: InnerResource) {
@ -185,7 +229,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
}) })
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied // eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
.finally(() => { .finally(() => {
void this.reloadResources(this.state.sorting); void this.reloadResources();
}); });
} }
@ -208,9 +252,12 @@ class ResourceScreenComponent extends React.Component<Props, State> {
}; };
} }
this.setState({ sorting: newSorting }); this.setState({ sorting: newSorting });
void this.reloadResources(newSorting);
} }
public onFilterUpdate = (updateEvent: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ filter: updateEvent.target.value });
};
public render() { public render() {
const style = this.props.style; const style = this.props.style;
const theme = themeStyle(this.props.themeId); const theme = themeStyle(this.props.themeId);
@ -236,20 +283,30 @@ class ResourceScreenComponent extends React.Component<Props, State> {
<div style={{ ...theme.notificationBox, marginBottom: 10 }}>{ <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.') _('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> }</div>
<div style={{ float: 'right' }}>
<input
style={theme.inputStyle}
type="search"
value={this.state.filter}
onChange={this.onFilterUpdate}
placeholder={_('Search...')}
/>
</div>
{this.state.isLoading && <div>{_('Please wait...')}</div>} {this.state.isLoading && <div>{_('Please wait...')}</div>}
{!this.state.isLoading && <div> {!this.state.isLoading && <div>
{!this.state.resources && <div> {!this.state.resources && <div>
{_('No resources!')} {_('No resources!')}
</div> </div>
} }
{this.state.resources && this.state.resources.length === MAX_RESOURCES && {this.state.resources && this.state.resources.length === this.maxResources &&
<div>{_('Warning: not all resources shown for performance reasons (limit: %s).', MAX_RESOURCES)}</div> <div>{_('Warning: not all resources shown for performance reasons (limit: %s).', this.maxResources)}</div>
} }
{this.state.resources && <ResourceTableComp {this.state.resources && <ResourceTableComp
themeId={this.props.themeId} themeId={this.props.themeId}
style={style} style={style}
resources={this.state.resources} resources={this.state.resources}
sorting={this.state.sorting} sorting={this.state.sorting}
filter={this.state.filter}
onToggleSorting={(order) => this.onToggleSortOrder(order)} onToggleSorting={(order) => this.onToggleSortOrder(order)}
onResourceClick={(resource) => this.openResource(resource)} onResourceClick={(resource) => this.openResource(resource)}
onResourceDelete={(resource) => this.onResourceDelete(resource)} onResourceDelete={(resource) => this.onResourceDelete(resource)}