From 2dd27cdd0026882d699b8801e129ffe158811de3 Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Tue, 21 May 2024 02:14:39 -0700 Subject: [PATCH] Desktop: Note attachments screen: Allow searching for attachments (#10442) --- packages/app-desktop/gui/ResourceScreen.tsx | 79 ++++++++++++++++++--- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/packages/app-desktop/gui/ResourceScreen.tsx b/packages/app-desktop/gui/ResourceScreen.tsx index 8036008967..871078a63c 100644 --- a/packages/app-desktop/gui/ResourceScreen.tsx +++ b/packages/app-desktop/gui/ResourceScreen.tsx @@ -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; @@ -31,6 +32,7 @@ interface State { resources: InnerResource[] | undefined; sorting: ActiveSorting; isLoading: boolean; + filter: string; } interface ResourceTable { @@ -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; } @@ -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 ( @@ -100,7 +107,7 @@ const ResourceTableComp = (props: ResourceTable) => { - {props.resources.map((resource: InnerResource, index: number) => + {filteredResources.map((resource: InnerResource, index: number) =>
{ } }; -const MAX_RESOURCES = 10000; +const defaultMaxResources = 10000; +const searchMaxResources = 1000; class ResourceScreenComponent extends React.Component { public constructor(props: Props) { super(props); this.state = { resources: undefined, + filter: '', sorting: { type: 'asc', order: 'name', @@ -151,22 +160,57 @@ class ResourceScreenComponent extends React.Component { }; } - 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 = {}; + 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) { @@ -185,7 +229,7 @@ class ResourceScreenComponent extends React.Component { }) // 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(); }); } @@ -208,9 +252,12 @@ class ResourceScreenComponent extends React.Component { }; } this.setState({ sorting: newSorting }); - void this.reloadResources(newSorting); } + public onFilterUpdate = (updateEvent: React.ChangeEvent) => { + this.setState({ filter: updateEvent.target.value }); + }; + public render() { const style = this.props.style; const theme = themeStyle(this.props.themeId); @@ -236,20 +283,30 @@ class ResourceScreenComponent extends React.Component {
{ _('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.state.resources.length === this.maxResources && +
{_('Warning: not all resources shown for performance reasons (limit: %s).', this.maxResources)}
} {this.state.resources && this.onToggleSortOrder(order)} onResourceClick={(resource) => this.openResource(resource)} onResourceDelete={(resource) => this.onResourceDelete(resource)}