Skip to content

Commit 59d99f9

Browse files
authored
Merge pull request #3438 from obsidian-tasks-group/do-not-render-on-inactive-tabs
feat: Stop tasks searches from running on inactive tabs
2 parents 412a4b0 + bbfb431 commit 59d99f9

File tree

4 files changed

+96
-1
lines changed

4 files changed

+96
-1
lines changed

src/IQuery.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,14 @@ export interface IQuery {
8585
* @param objects
8686
*/
8787
debug(message: string, objects?: any): void;
88+
89+
/**
90+
* Write a warn log message.
91+
*
92+
* This is provided to allow the query rendering code to log progress on the rendering,
93+
* including meaningful information about the query being rendered.
94+
* @param message
95+
* @param objects
96+
*/
97+
warn(message: string, objects?: any): void;
8898
}

src/Query/Query.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,4 +458,8 @@ ${statement.explainStatement(' ')}
458458
public debug(message: string, objects?: any): void {
459459
this.logger.debugWithId(this._queryId, `"${this.filePath}": ${message}`, objects);
460460
}
461+
462+
public warn(message: string, objects?: any): void {
463+
this.logger.warnWithId(this._queryId, `"${this.filePath}": ${message}`, objects);
464+
}
461465
}

src/Renderer/QueryRenderer.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ class QueryRenderChild extends MarkdownRenderChild {
9292
private renderEventRef: EventRef | undefined;
9393
private queryReloadTimeout: NodeJS.Timeout | undefined;
9494

95+
private isCacheChangedSinceLastRedraw = false;
96+
private observer: IntersectionObserver | null = null;
97+
9598
private readonly queryResultsRenderer: QueryResultsRenderer;
9699

97100
constructor({
@@ -162,6 +165,38 @@ class QueryRenderChild extends MarkdownRenderChild {
162165
this.handleMetadataOrFilePathChange(tFile.path, fileCache);
163166
}),
164167
);
168+
169+
this.setupVisibilityObserver();
170+
}
171+
172+
private setupVisibilityObserver() {
173+
if (this.observer) {
174+
return;
175+
}
176+
177+
this.observer = new IntersectionObserver(([entry]) => {
178+
if (!this.containerEl.isShown()) {
179+
return;
180+
}
181+
182+
// entry describes a single visibility change for the specific element we are observing.
183+
// It is safe to assume `entry.target === this.containerEl` here.
184+
if (!entry.isIntersecting) {
185+
return;
186+
}
187+
188+
this.queryResultsRenderer.query.debug(
189+
`[render][observer] Became visible, isCacheChangedSinceLastRedraw:${this.isCacheChangedSinceLastRedraw}`,
190+
);
191+
if (this.isCacheChangedSinceLastRedraw) {
192+
this.queryResultsRenderer.query.debug('[render][observer] ... updating search results');
193+
this.render({ tasks: this.plugin.getTasks(), state: this.plugin.getState() })
194+
.then()
195+
.catch((e) => console.error(e));
196+
}
197+
});
198+
199+
this.observer.observe(this.containerEl);
165200
}
166201

167202
private handleMetadataOrFilePathChange(filePath: string, fileCache: CachedMetadata | null) {
@@ -189,6 +224,9 @@ class QueryRenderChild extends MarkdownRenderChild {
189224
if (this.queryReloadTimeout !== undefined) {
190225
clearTimeout(this.queryReloadTimeout);
191226
}
227+
228+
this.observer?.disconnect();
229+
this.observer = null;
192230
}
193231

194232
/**
@@ -219,6 +257,42 @@ class QueryRenderChild extends MarkdownRenderChild {
219257
}
220258

221259
private async render({ tasks, state }: { tasks: Task[]; state: State }) {
260+
// We got here because the Cache reported a change in at least one task in the vault.
261+
// So note that any results we have already drawn are now out-of-date:
262+
this.isCacheChangedSinceLastRedraw = true;
263+
264+
requestAnimationFrame(async () => {
265+
// We have to wrap the rendering inside requestAnimationFrame() to ensure
266+
// that we get correct values for isConnected and isShown().
267+
if (!this.containerEl.isConnected) {
268+
// Example reasons why we might not be "connected":
269+
// - This Tasks query block is contained within another plugin's code block,
270+
// such as a Tabs plugin. The file is closed and that plugin has not correctly
271+
// tidied up, so we have not been deleted.
272+
this.queryResultsRenderer.query.debug(
273+
'[render] Ignoring redraw request, as code block is not connected.',
274+
);
275+
return;
276+
}
277+
278+
if (!this.containerEl.isShown()) {
279+
// Example reasons why we might not be "shown":
280+
// - We are in a collapsed callout.
281+
// - We are in a note which is obscured by another note.
282+
// - We are in a Tabs plugin, in a tab which is not at the front.
283+
// - The user has not yet scrolled to this code block's position in the file.
284+
this.queryResultsRenderer.query.debug('[render] Ignoring redraw request, as code block is not shown.');
285+
return;
286+
}
287+
288+
await this.renderResults(state, tasks);
289+
290+
// Our results are now up-to-date:
291+
this.isCacheChangedSinceLastRedraw = false;
292+
});
293+
}
294+
295+
private async renderResults(state: State, tasks: Task[]) {
222296
const content = createAndAppendElement('div', this.containerEl);
223297
await this.queryResultsRenderer.render(state, tasks, content, {
224298
allTasks: this.plugin.getTasks(),

src/main.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Plugin } from 'obsidian';
22

33
import type { Task } from 'Task/Task';
44
import { i18n, initializeI18n } from './i18n/i18n';
5-
import { Cache } from './Obsidian/Cache';
5+
import { Cache, State } from './Obsidian/Cache';
66
import { Commands } from './Commands';
77
import { GlobalQuery } from './Config/GlobalQuery';
88
import { TasksEvents } from './Obsidian/TasksEvents';
@@ -107,6 +107,13 @@ export default class TasksPlugin extends Plugin {
107107
}
108108
}
109109

110+
public getState(): State {
111+
if (this.cache === undefined) {
112+
return State.Cold;
113+
}
114+
return this.cache.getState();
115+
}
116+
110117
/**
111118
* Add {@link QueryFileDefaults} properties to the Obsidian vault's types.json file,
112119
* so that they are available via auto-complete in the File Properties panel.

0 commit comments

Comments
 (0)