Skip to content

Commit

Permalink
Merge pull request #34 from /issues/33
Browse files Browse the repository at this point in the history
Issues/33
  • Loading branch information
NekitCorp authored Jun 5, 2024
2 parents 433d648 + 1c4e0f3 commit 1604848
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 31 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "better-workflowy",
"version": "2.2.0",
"version": "2.2.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
25 changes: 25 additions & 0 deletions src/e2e/calculate-total-hashtag-time.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,31 @@ test.describe('Calculate total hashtag time', () => {
}
}
});

// The total time must be recalculated when expanding and collapsing the item containing time.
test('Consider expanding and collapsing an item', async ({ page, testPage }) => {
const toggle = page
.locator('.name', { hasText: 'Swap hashtags on hotkey' })
.locator('a[data-handbook="expand.toggle"]');

// Collapse
await toggle.click();

// Waiting for requestIdleInterval
await page.waitForTimeout(1000);

// Minus 5 hours
await expect(page.locator('#bw-time-counter')).toHaveText('2d 9h 42m 14s');

// Expand
await toggle.click();

// Waiting for requestIdleInterval
await page.waitForTimeout(1000);

// Total time should return
await expect(page.locator('#bw-time-counter')).toHaveText('2d 14h 42m 14s');
});
});

test.describe('Option disabled', () => {
Expand Down
64 changes: 35 additions & 29 deletions src/modules/dom-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export class DomManager implements IDomManager {
private subscribers: ((node: HTMLElement) => void)[] = [];

constructor(private logger: ILogger) {
this.observe();
const observer = new MutationObserver(this.mutationCallback);
observer.observe(document.body, { childList: true, subtree: true });
}

public loadingApp(): Promise<void> {
Expand Down Expand Up @@ -79,34 +80,39 @@ export class DomManager implements IDomManager {
}, 1 * 1000);
}

private observe() {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof HTMLElement)) continue;

// Detect app load
if (
this.resolveLoadingPromise !== null &&
(node.matches(`.${PAGE_ELEMENT_CLASS_NAME}`) ||
node.querySelector(`.${PAGE_ELEMENT_CLASS_NAME}`))
) {
this.resolveLoadingPromise();
this.resolveLoadingPromise = null;
}

// Detect any changes on content rows with hashtags
if (node.classList.contains(CONTENT_ROW_ELEMENT_CLASS_NAME)) {
const contentTag = node.querySelector(`.${TAG_ELEMENT_TEXT_CLASS_NAME}`);

if (contentTag) {
this.subscribers.forEach((c) => c(node));
}
}
}
private mutationCallback: MutationCallback = (
mutations: MutationRecord[],
observer: MutationObserver,
): void => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
this.processingChangedNode(node);
}
});

observer.observe(document.body, { childList: true, subtree: true });
}
};

private processingChangedNode(node: Node): void {
if (!(node instanceof HTMLElement)) {
return;
}

// Detect app load
if (
this.resolveLoadingPromise !== null &&
(node.matches(`.${PAGE_ELEMENT_CLASS_NAME}`) ||
node.querySelector(`.${PAGE_ELEMENT_CLASS_NAME}`))
) {
this.resolveLoadingPromise();
this.resolveLoadingPromise = null;
}

// Detect any changes on content rows with hashtags
if (node.classList.contains(CONTENT_ROW_ELEMENT_CLASS_NAME)) {
const contentTag = node.querySelector(`.${TAG_ELEMENT_TEXT_CLASS_NAME}`);

if (contentTag) {
this.subscribers.forEach((c) => c(node));
}
}
}
}
91 changes: 91 additions & 0 deletions src/modules/request-idle-interval/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
type Options = {
interval: number;
timeout?: number;
};

type State = {
intervalId?: string | number | NodeJS.Timeout;
requestIdleCallbackId?: number;
isRequestIdleCallbackScheduled: boolean;
};

type CancelCallback = () => void;

/**
* `callback` function is invoked when after `interval` msec and environment is idled.
* `callback` which have a `timeout` specified may be called out-of-order if necessary in order to run them before the timeout elapses.
* @param callback
* @param options
* @return {function} return cancelRequestIdleInterval function
*/
export function requestIdleInterval(callback: () => void, options: Options): CancelCallback {
polyfill();

if (options.interval <= options.timeout) {
throw new Error(
`options.timeout should be less than options.interval. Recommended: options.timeout is less than half of options.interval.`,
);
}

const state: State = {
isRequestIdleCallbackScheduled: false,
};

state.intervalId = setInterval(() => {
// Only schedule the rIC if one has not already been set.
if (state.isRequestIdleCallbackScheduled) {
return;
}

state.isRequestIdleCallbackScheduled = true;
state.requestIdleCallbackId = requestIdleCallback(
() => {
// Reset the boolean so future rICs can be set.
state.isRequestIdleCallbackScheduled = false;
callback();
},
{
timeout: options.timeout,
},
);
}, options.interval);

// Return cancel function
return () => {
if (state.intervalId !== undefined) {
clearInterval(state.intervalId);
}

if (state.requestIdleCallbackId !== undefined) {
cancelIdleCallback(state.requestIdleCallbackId);
}
};
}

// https://developer.chrome.com/blog/using-requestidlecallback
function polyfill(): void {
if ('requestIdleCallback' in window) {
return;
}

(window as Window).requestIdleCallback = function (
callback: IdleRequestCallback,
options?: IdleRequestOptions,
) {
const start = Date.now();
const intervalId = setTimeout(function () {
callback({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start));
},
});
}, 1);

return intervalId as unknown as number;
};

(window as Window).cancelIdleCallback = function (id) {
clearTimeout(id);
};
}
3 changes: 2 additions & 1 deletion src/modules/time-manager/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { requestIdleInterval } from '../request-idle-interval';
import { formatTime, getTagSeconds } from './utils';

export class TimeManager implements ITimeManager {
Expand All @@ -17,7 +18,7 @@ export class TimeManager implements ITimeManager {
this.renderTotalTime();

this.domManager.subscribe(this.highlightTimeHashtag);
this.domManager.subscribe(this.renderTotalTime);
requestIdleInterval(this.renderTotalTime, { interval: 1000 });
}

private createTimeCounterElement() {
Expand Down

0 comments on commit 1604848

Please sign in to comment.