Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 68 additions & 11 deletions packages/app-desktop/gui/ResourceScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { themeStyle } = require('@joplin/lib/theme');
import bridge from '../services/bridge';
const prettyBytes = require('pretty-bytes');
import Resource from '@joplin/lib/models/Resource';
import { LoadOptions } from '@joplin/lib/models/utils/types';

interface Style {
width: number;
Expand All @@ -31,6 +32,7 @@ interface State {
resources: InnerResource[] | undefined;
sorting: ActiveSorting;
isLoading: boolean;
filter: string;
}

interface ResourceTable {
Expand All @@ -42,6 +44,7 @@ interface ResourceTable {
onResourceDelete: (resource: InnerResource)=> any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
onToggleSorting: (order: SortingOrder)=> any;
filter: string;
themeId: number;
style: Style;
}
Expand Down Expand Up @@ -89,6 +92,10 @@ const ResourceTableComp = (props: ResourceTable) => {
fontWeight: 'bold',
};

const filteredResources = props.resources.filter(
(resource: InnerResource) => !props.filter || resource.title?.includes(props.filter) || resource.id.includes(props.filter),
);

return (
<table style={{ width: '100%' }}>
<thead>
Expand All @@ -100,7 +107,7 @@ const ResourceTableComp = (props: ResourceTable) => {
</tr>
</thead>
<tbody>
{props.resources.map((resource: InnerResource, index: number) =>
{filteredResources.map((resource: InnerResource, index: number) =>
<tr key={index}>
<td style={titleCellStyle} className="titleCell">
<a
Expand Down Expand Up @@ -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> {
public constructor(props: Props) {
super(props);
this.state = {
resources: undefined,
filter: '',
sorting: {
type: 'asc',
order: 'name',
Expand All @@ -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.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({
order: [{
by: getSortingOrderColumn(sorting.order),
dir: sorting.type,
by: getSortingOrderColumn(this.state.sorting.order),
dir: this.state.sorting.type,
caseInsensitive: true,
}],
limit: MAX_RESOURCES,
limit: this.maxResources,
fields: ['title', 'id', 'size', 'file_extension'],
...searchOptions,
});

const cancelled = currentCounterValue !== this.reloadResourcesCounter;
if (cancelled) return;

this.setState({ resources, isLoading: false });
}

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) {
Expand All @@ -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
.finally(() => {
void this.reloadResources(this.state.sorting);
void this.reloadResources();
});
}

Expand All @@ -208,9 +252,12 @@ class ResourceScreenComponent extends React.Component<Props, State> {
};
}
this.setState({ sorting: newSorting });
void this.reloadResources(newSorting);
}

public onFilterUpdate = (updateEvent: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ filter: updateEvent.target.value });
};

public render() {
const style = this.props.style;
const theme = themeStyle(this.props.themeId);
Expand All @@ -236,20 +283,30 @@ class ResourceScreenComponent extends React.Component<Props, State> {
<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>
<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>
{!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>
{this.state.resources && this.state.resources.length === this.maxResources &&
<div>{_('Warning: not all resources shown for performance reasons (limit: %s).', this.maxResources)}</div>
}
{this.state.resources && <ResourceTableComp
themeId={this.props.themeId}
style={style}
resources={this.state.resources}
sorting={this.state.sorting}
filter={this.state.filter}
onToggleSorting={(order) => this.onToggleSortOrder(order)}
onResourceClick={(resource) => this.openResource(resource)}
onResourceDelete={(resource) => this.onResourceDelete(resource)}
Expand Down
Loading