mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 09:11:06 +00:00
fix: fix animations on analyitics page when setting disabled (#474)
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
} from "./timeRange";
|
} from "./timeRange";
|
||||||
import { computeGradeForecast, aggregateToMonthlyPoints } from "./utils/gradePrediction";
|
import { computeGradeForecast, aggregateToMonthlyPoints } from "./utils/gradePrediction";
|
||||||
import PredictionMonthsSlider from "./PredictionMonthsSlider.svelte";
|
import PredictionMonthsSlider from "./PredictionMonthsSlider.svelte";
|
||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: Assessment[];
|
data: Assessment[];
|
||||||
@@ -166,6 +167,8 @@
|
|||||||
return monthly.length >= 3;
|
return monthly.length >= 3;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const animationsOn = $derived($settingsState.animations);
|
||||||
|
|
||||||
/** Historical + future forecast points so tooltips work across the dashed line. */
|
/** Historical + future forecast points so tooltips work across the dashed line. */
|
||||||
const chartData = $derived.by((): TrendPoint[] => {
|
const chartData = $derived.by((): TrendPoint[] => {
|
||||||
if (!showPrediction || !forecast?.points.length) {
|
if (!showPrediction || !forecast?.points.length) {
|
||||||
@@ -243,7 +246,7 @@
|
|||||||
curve: curveMonotoneX,
|
curve: curveMonotoneX,
|
||||||
"fill-opacity": showSubjectTrends ? 0.12 : 0.35,
|
"fill-opacity": showSubjectTrends ? 0.12 : 0.35,
|
||||||
line: { class: "stroke-2" },
|
line: { class: "stroke-2" },
|
||||||
motion: "tween",
|
motion: animationsOn ? "tween" : false,
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
ticks: timeRange === "7d" ? 7 : undefined,
|
ticks: timeRange === "7d" ? 7 : undefined,
|
||||||
@@ -267,12 +270,12 @@
|
|||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<ChartClipPath
|
<ChartClipPath
|
||||||
initialWidth={showPrediction ? undefined : 0}
|
initialWidth={animationsOn && !showPrediction ? 0 : undefined}
|
||||||
motion={showPrediction
|
motion={animationsOn && !showPrediction
|
||||||
? undefined
|
? {
|
||||||
: {
|
|
||||||
width: { type: "tween", duration: 900, easing: cubicInOut },
|
width: { type: "tween", duration: 900, easing: cubicInOut },
|
||||||
}}
|
}
|
||||||
|
: undefined}
|
||||||
>
|
>
|
||||||
{#each series as s, i (s.key)}
|
{#each series as s, i (s.key)}
|
||||||
{@const meta = chartSeries.find((c) => c.key === s.key)}
|
{@const meta = chartSeries.find((c) => c.key === s.key)}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
} from "./gradeDistribution";
|
} from "./gradeDistribution";
|
||||||
|
|
||||||
import { loadDistributionMode, saveDistributionMode } from "./storage";
|
import { loadDistributionMode, saveDistributionMode } from "./storage";
|
||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -312,7 +313,7 @@
|
|||||||
|
|
||||||
motion: {
|
motion: {
|
||||||
|
|
||||||
y: { type: "tween", duration: 600, easing: cubicInOut },
|
y: { type: "tween", duration: $settingsState.animations ? 600 : 0, easing: cubicInOut },
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
type TimeRange,
|
type TimeRange,
|
||||||
} from "./timeRange";
|
} from "./timeRange";
|
||||||
import { openAnalyticsPrivacyPopup } from "./openAnalyticsPrivacyPopup";
|
import { openAnalyticsPrivacyPopup } from "./openAnalyticsPrivacyPopup";
|
||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
|
||||||
let analyticsData: Assessment[] | null = $state(null);
|
let analyticsData: Assessment[] | null = $state(null);
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
@@ -39,6 +40,8 @@
|
|||||||
|
|
||||||
let timestampInterval: ReturnType<typeof setInterval> | null = null;
|
let timestampInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
let contentReady = $state(false);
|
let contentReady = $state(false);
|
||||||
|
const fadeDuration = $derived($settingsState.animations ? 200 : 0);
|
||||||
|
const emptyFadeDuration = $derived($settingsState.animations ? 300 : 0);
|
||||||
|
|
||||||
const formattedTimestamp = $derived(() => {
|
const formattedTimestamp = $derived(() => {
|
||||||
if (!lastUpdated) return "";
|
if (!lastUpdated) return "";
|
||||||
@@ -204,7 +207,7 @@
|
|||||||
|
|
||||||
<div class="bsplus-analytics-root">
|
<div class="bsplus-analytics-root">
|
||||||
{#if error}
|
{#if error}
|
||||||
<p class="bsplus-analytics-alert bsplus-analytics-animate" role="alert" transition:fade={{ duration: 200 }}>
|
<p class="bsplus-analytics-alert bsplus-analytics-animate" role="alert" transition:fade={{ duration: fadeDuration }}>
|
||||||
{error}
|
{error}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -462,7 +465,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="bsplus-analytics-layout bsplus-analytics-animate" transition:fade={{ duration: 300 }}>
|
<div class="bsplus-analytics-layout bsplus-analytics-animate" transition:fade={{ duration: emptyFadeDuration }}>
|
||||||
<aside class="bsplus-analytics-filters" aria-label="Analytics">
|
<aside class="bsplus-analytics-filters" aria-label="Analytics">
|
||||||
{@render sidebarTitle()}
|
{@render sidebarTitle()}
|
||||||
{@render sidebarActions()}
|
{@render sidebarActions()}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from "svelte";
|
||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
value = $bindable<[number, number]>([0, 100]),
|
value = $bindable<[number, number]>([0, 100]),
|
||||||
@@ -88,7 +89,7 @@
|
|||||||
|
|
||||||
value = next;
|
value = next;
|
||||||
|
|
||||||
if (animate) {
|
if (animate && $settingsState.animations) {
|
||||||
animateVisualTo(next);
|
animateVisualTo(next);
|
||||||
} else {
|
} else {
|
||||||
visual = next;
|
visual = next;
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
ANALYTICS_MOTION_CLASS,
|
||||||
|
analyticsMotionEnabled,
|
||||||
|
applyAnalyticsMotionClass,
|
||||||
|
} from "./motion";
|
||||||
|
|
||||||
|
describe("analyticsMotionEnabled", () => {
|
||||||
|
it("is on only when the animations setting is true", () => {
|
||||||
|
expect(analyticsMotionEnabled(true)).toBe(true);
|
||||||
|
expect(analyticsMotionEnabled(false)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applyAnalyticsMotionClass", () => {
|
||||||
|
it("adds the motion class when animations are enabled", () => {
|
||||||
|
const el = { classList: { toggle: jest.fn() } };
|
||||||
|
applyAnalyticsMotionClass(el, true);
|
||||||
|
expect(el.classList.toggle).toHaveBeenCalledWith(ANALYTICS_MOTION_CLASS, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes the motion class when animations are disabled", () => {
|
||||||
|
const el = { classList: { toggle: jest.fn() } };
|
||||||
|
applyAnalyticsMotionClass(el, false);
|
||||||
|
expect(el.classList.toggle).toHaveBeenCalledWith(ANALYTICS_MOTION_CLASS, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export const ANALYTICS_MOTION_CLASS = "bsplus-analytics-motion";
|
||||||
|
|
||||||
|
export function analyticsMotionEnabled(animations: boolean | undefined): boolean {
|
||||||
|
return animations === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyAnalyticsMotionClass(
|
||||||
|
target: { classList: { toggle: (token: string, force?: boolean) => unknown } },
|
||||||
|
animations: boolean | undefined,
|
||||||
|
): void {
|
||||||
|
target.classList.toggle(ANALYTICS_MOTION_CLASS, analyticsMotionEnabled(animations));
|
||||||
|
}
|
||||||
@@ -114,6 +114,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bsplus-analytics-mount:not(.bsplus-analytics-motion) .bsplus-analytics-animate {
|
||||||
|
animation: none;
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-analytics-mount:not(.bsplus-analytics-motion) .bsplus-analytics-btn,
|
||||||
|
.bsplus-analytics-mount:not(.bsplus-analytics-motion) .bsplus-analytics-card,
|
||||||
|
.bsplus-analytics-mount:not(.bsplus-analytics-motion) .bsplus-analytics-dropdown-trigger,
|
||||||
|
.bsplus-analytics-mount:not(.bsplus-analytics-motion) .bsplus-analytics-select {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-analytics-mount:not(.bsplus-analytics-motion) .bsplus-analytics-btn:hover:not(:disabled),
|
||||||
|
.bsplus-analytics-mount:not(.bsplus-analytics-motion) .bsplus-analytics-btn:active:not(:disabled),
|
||||||
|
.bsplus-analytics-mount:not(.bsplus-analytics-motion) .bsplus-analytics-dropdown-trigger:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.bsplus-analytics-delay-1 {
|
.bsplus-analytics-delay-1 {
|
||||||
animation-delay: 80ms;
|
animation-delay: 80ms;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ import { mount, unmount } from "svelte";
|
|||||||
import GradeAnalyticsPage from "./GradeAnalyticsPage.svelte";
|
import GradeAnalyticsPage from "./GradeAnalyticsPage.svelte";
|
||||||
import { buildContrastAccentPalette } from "./utils/accentColor";
|
import { buildContrastAccentPalette } from "./utils/accentColor";
|
||||||
import { extractSolidColor } from "@/seqta/ui/colors/parseCssColor";
|
import { extractSolidColor } from "@/seqta/ui/colors/parseCssColor";
|
||||||
|
import { applyAnalyticsMotionClass } from "./motion";
|
||||||
|
|
||||||
type ThemeSettingKey =
|
type ThemeSettingKey =
|
||||||
| "selectedColor"
|
| "selectedColor"
|
||||||
| "DarkMode"
|
| "DarkMode"
|
||||||
| "adaptiveThemeColour"
|
| "adaptiveThemeColour"
|
||||||
| "adaptiveThemeGradient"
|
| "adaptiveThemeGradient"
|
||||||
| "selectedTheme";
|
| "selectedTheme"
|
||||||
|
| "animations";
|
||||||
|
|
||||||
type ThemeListenerRegistration = {
|
type ThemeListenerRegistration = {
|
||||||
key: ThemeSettingKey;
|
key: ThemeSettingKey;
|
||||||
@@ -117,7 +119,10 @@ function syncThemeFromPage(target: HTMLElement) {
|
|||||||
|
|
||||||
function syncThemeToAnalyticsUi() {
|
function syncThemeToAnalyticsUi() {
|
||||||
if (shadowHost) syncThemeFromPage(shadowHost);
|
if (shadowHost) syncThemeFromPage(shadowHost);
|
||||||
if (analyticsRoot) syncThemeFromPage(analyticsRoot);
|
if (analyticsRoot) {
|
||||||
|
syncThemeFromPage(analyticsRoot);
|
||||||
|
applyAnalyticsMotionClass(analyticsRoot, settingsState.animations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearThemeListeners() {
|
function clearThemeListeners() {
|
||||||
@@ -136,6 +141,7 @@ function watchThemeChanges() {
|
|||||||
"adaptiveThemeColour",
|
"adaptiveThemeColour",
|
||||||
"adaptiveThemeGradient",
|
"adaptiveThemeGradient",
|
||||||
"selectedTheme",
|
"selectedTheme",
|
||||||
|
"animations",
|
||||||
];
|
];
|
||||||
|
|
||||||
const listener = () => syncThemeToAnalyticsUi();
|
const listener = () => syncThemeToAnalyticsUi();
|
||||||
|
|||||||
Reference in New Issue
Block a user