mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
Merge branch 'fix/issue-6076'
* fix/issue-6076: fix(play-button): replace infinite CSS animation with periodic finite pulses fix(build): fix tag resolution and add error handling in bump-android-version
This commit is contained in:
commit
0a3444ad49
2 changed files with 82 additions and 8 deletions
|
|
@ -3,6 +3,7 @@ import {
|
|||
ChangeDetectorRef,
|
||||
Component,
|
||||
computed,
|
||||
effect,
|
||||
ElementRef,
|
||||
inject,
|
||||
input,
|
||||
|
|
@ -25,6 +26,7 @@ import { TaskService } from '../../../features/tasks/task.service';
|
|||
import { animationFrameScheduler, Subscription } from 'rxjs';
|
||||
import { distinctUntilChanged, observeOn } from 'rxjs/operators';
|
||||
import { NavigateToTaskService } from '../../navigate-to-task/navigate-to-task.service';
|
||||
import { lazySetInterval } from '../../../util/lazy-set-interval';
|
||||
|
||||
@Component({
|
||||
selector: 'play-button',
|
||||
|
|
@ -51,7 +53,10 @@ import { NavigateToTaskService } from '../../navigate-to-task/navigate-to-task.s
|
|||
</div>
|
||||
}
|
||||
@if (currentTaskId()) {
|
||||
<div class="pulse-circle"></div>
|
||||
<div
|
||||
#pulseCircle
|
||||
class="pulse-circle"
|
||||
></div>
|
||||
}
|
||||
|
||||
@if (hasTimeEstimate) {
|
||||
|
|
@ -98,14 +103,16 @@ import { NavigateToTaskService } from '../../navigate-to-task/navigate-to-task.s
|
|||
display: contents;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
/* Finite pulse animation played once per trigger (not infinite).
|
||||
* An infinite CSS animation forces the compositor to run at 60fps
|
||||
* continuously, causing ~5-10% CPU and ~30% GPU even when idle.
|
||||
* Instead, this finite animation is triggered periodically via JS
|
||||
* (see effect() in the component class). */
|
||||
@keyframes pulse-once {
|
||||
0% {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
25% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
|
|
@ -127,10 +134,17 @@ import { NavigateToTaskService } from '../../navigate-to-task/navigate-to-task.s
|
|||
bottom: 0;
|
||||
border-radius: 50%;
|
||||
margin: auto;
|
||||
transform: scale(1, 1);
|
||||
animation: pulse 2s infinite;
|
||||
transform: scale(0.7);
|
||||
background: var(--c-accent);
|
||||
opacity: 0.6;
|
||||
|
||||
/* Promote to own GPU layer so the pulse animation does not
|
||||
* trigger repaints of the button or icon above it. */
|
||||
will-change: transform;
|
||||
|
||||
&.do-pulse {
|
||||
animation: pulse-once 2s ease-in-out 1;
|
||||
}
|
||||
}
|
||||
|
||||
.circle-svg {
|
||||
|
|
@ -227,6 +241,7 @@ export class PlayButtonComponent implements OnInit, OnDestroy {
|
|||
readonly currentTaskContext = input<WorkContext | null>();
|
||||
readonly hasTrackableTasks = input<boolean>(true);
|
||||
readonly circleSvg = viewChild<ElementRef<SVGCircleElement>>('circleSvg');
|
||||
readonly pulseCircle = viewChild<ElementRef<HTMLElement>>('pulseCircle');
|
||||
|
||||
readonly isDisabled = computed(
|
||||
() => !this.currentTaskId() && !this.hasTrackableTasks(),
|
||||
|
|
@ -238,6 +253,46 @@ export class PlayButtonComponent implements OnInit, OnDestroy {
|
|||
private _subs = new Subscription();
|
||||
private circumference = 10 * 2 * Math.PI; // ~62.83
|
||||
protected hasTimeEstimate = false;
|
||||
constructor() {
|
||||
// Intermittent pulse animation to indicate active time tracking.
|
||||
//
|
||||
// Performance note (see https://github.com/super-productivity/super-productivity/issues/6076):
|
||||
// CSS `animation: infinite` forces the browser compositor to run at 60fps
|
||||
// continuously, even for a simple scale transform. This caused ~5-10% CPU
|
||||
// and ~30% GPU usage while tracking.
|
||||
//
|
||||
// Fix: use a finite CSS animation (`animation-iteration-count: 1`) triggered
|
||||
// periodically via lazySetInterval. The compositor goes fully idle between
|
||||
// pulses. Combined with `will-change: transform` on the pulse element (which
|
||||
// promotes it to its own GPU layer), the animation does not cause repaints of
|
||||
// surrounding elements (button, icon).
|
||||
effect((onCleanup) => {
|
||||
const el = this.pulseCircle()?.nativeElement;
|
||||
const isTracking = !!this.currentTaskId();
|
||||
|
||||
if (el && isTracking) {
|
||||
// Trigger an initial pulse immediately
|
||||
el.classList.add('do-pulse');
|
||||
|
||||
// Remove the class when the animation finishes so it can be re-triggered
|
||||
const onAnimEnd = (): void => el.classList.remove('do-pulse');
|
||||
el.addEventListener('animationend', onAnimEnd);
|
||||
|
||||
// Re-trigger the pulse every 5s (animation itself is 2s, leaving 3s idle)
|
||||
const stopInterval = lazySetInterval(() => {
|
||||
el.classList.add('do-pulse');
|
||||
}, 5000);
|
||||
|
||||
// Angular calls onCleanup automatically when the effect re-runs or
|
||||
// the component is destroyed — no manual ngOnDestroy cleanup needed.
|
||||
onCleanup(() => {
|
||||
stopInterval();
|
||||
el.removeEventListener('animationend', onAnimEnd);
|
||||
el.classList.remove('do-pulse');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Subscribe to current task to track if it has a time estimate
|
||||
|
|
|
|||
19
src/app/util/lazy-set-interval.ts
Normal file
19
src/app/util/lazy-set-interval.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Avoids the performance issues caused by normal setInterval when the user
|
||||
// is not at the computer for some time. Uses chained setTimeout instead.
|
||||
export const lazySetInterval = (
|
||||
func: () => void,
|
||||
intervalDuration: number,
|
||||
): (() => void) => {
|
||||
let lastTimeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
const interval = (): void => {
|
||||
lastTimeoutId = setTimeout(interval, intervalDuration);
|
||||
func.call(null);
|
||||
};
|
||||
|
||||
lastTimeoutId = setTimeout(interval, intervalDuration);
|
||||
|
||||
return () => {
|
||||
clearTimeout(lastTimeoutId);
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue