Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/bridges/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ const settingsBridge = {
});
},

getAnimationMotionPref: (): AnimationMotionPref => {
return ipcRenderer.sendSync("get-animation-motion-pref");
getAnimationMotionPref: (): Promise<AnimationMotionPref> => {
return ipcRenderer.invoke("get-animation-motion-pref");
},
setAnimationMotionPref: (pref: AnimationMotionPref) => {
ipcRenderer.invoke("set-animation-motion-pref", pref);
setAnimationMotionPref: (pref: AnimationMotionPref): Promise<void> => {
return ipcRenderer.invoke("set-animation-motion-pref", pref);
},

getNativeWindowFramePref: (): boolean => {
Expand All @@ -67,11 +67,11 @@ const settingsBridge = {
ipcRenderer.invoke("set-window-native-frame-pref", pref);
},

getThumbnailTypePref: (): ThumbnailTypePref => {
return ipcRenderer.sendSync("get-thumbnail-type-pref");
getThumbnailTypePref: (): Promise<ThumbnailTypePref> => {
return ipcRenderer.invoke("get-thumbnail-type-pref");
},
setThumbnailTypePref: (pref: ThumbnailTypePref) => {
ipcRenderer.invoke("set-thumbnail-type-pref", pref);
setThumbnailTypePref: (pref: ThumbnailTypePref): Promise<void> => {
return ipcRenderer.invoke("set-thumbnail-type-pref", pref);
},

setLocaleSettings: (option: string) => {
Expand Down
109 changes: 58 additions & 51 deletions src/components/cards/cached-img.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as React from "react";
import { AnimationMotionPref } from "../../schema-types";
import { getAnimationMotionPref } from "../../scripts/settings";

type ImgProps = {
src: string;
animationPref: AnimationMotionPref;
className?: string;
width?: number;
height?: number;
Expand All @@ -22,12 +22,10 @@ class CachedImg extends React.Component<ImgProps> {
| PlaceholderImageSource
>();
private readonly _canvasRef = React.createRef<HTMLCanvasElement>();
private _animationMotionPref: AnimationMotionPref;
private _needsRescaling: boolean = true;
private _imgSource:
| HTMLImageElement
| HTMLVideoElement
| VideoFrame[]
| null = null;
HTMLImageElement | HTMLVideoElement | VideoFrame[] | null = null;
private _animationTimeout: NodeJS.Timeout | null = null;
private _requestVideoFrameCallback: number | null = null;
private _maxCanvasWidth: number;
Expand All @@ -36,6 +34,7 @@ class CachedImg extends React.Component<ImgProps> {

constructor(props: ImgProps) {
super(props);
this._animationMotionPref = props.animationPref;
this._maxCanvasWidth = props.width ?? 512;
this._maxCanvasHeight = props.height ?? 512;
}
Expand Down Expand Up @@ -143,14 +142,6 @@ class CachedImg extends React.Component<ImgProps> {
}

private async renderImage(): Promise<void> {
const animationMotionPref = getAnimationMotionPref();
const realisedAnimationMotionPref =
animationMotionPref !== AnimationMotionPref.System
? animationMotionPref
: window.utils.systemPreferencesGetAnimationSettings()
.prefersReducedMotion
? AnimationMotionPref.Off
: AnimationMotionPref.On;
if (CachedImg._cache.has(this.props.src)) {
const imgSource = CachedImg._cache.get(this.props.src);
if (imgSource === placeholderImageSource) {
Expand Down Expand Up @@ -215,47 +206,63 @@ class CachedImg extends React.Component<ImgProps> {
this.forceUpdate();
return;
}
} else if (this._imgSource instanceof HTMLVideoElement) {
const video = this._imgSource;
if (this._requestVideoFrameCallback !== null) {
video.cancelVideoFrameCallback(
this._requestVideoFrameCallback,
);
this._requestVideoFrameCallback = null;
}
const requestFrame = () => {
this.draw(video);
if (realisedAnimationMotionPref === AnimationMotionPref.On)
this._requestVideoFrameCallback =
video.requestVideoFrameCallback(requestFrame);
};
if (video.readyState < video.HAVE_CURRENT_DATA)
video.addEventListener("loadeddata", requestFrame);
else requestFrame();
} else {
const frames = this._imgSource;
let frameIndex = 0;
if (this._animationTimeout) {
clearTimeout(this._animationTimeout);
this._animationTimeout = null;
}
const drawFrame = () => {
if (frames.length === 0) return;
const frame = frames[frameIndex];
const duration = frame.duration / 1000;
this.draw(frame);
if (
frames.length > 1 &&
realisedAnimationMotionPref === AnimationMotionPref.On
) {
frameIndex = (frameIndex + 1) % frames.length;
this._animationTimeout = setTimeout(
() => drawFrame(),
duration,
const realisedAmp =
this._animationMotionPref !== AnimationMotionPref.System
? this._animationMotionPref
: window.utils.systemPreferencesGetAnimationSettings()
.prefersReducedMotion
? AnimationMotionPref.Off
: AnimationMotionPref.On;

if (this._imgSource instanceof HTMLVideoElement) {
const video = this._imgSource;
if (this._requestVideoFrameCallback !== null) {
video.cancelVideoFrameCallback(
this._requestVideoFrameCallback,
);
this._requestVideoFrameCallback = null;
}
};
drawFrame();
const requestFrame = async () => {
this.draw(video);
const realisedAnimationMotionPref = realisedAmp;
if (
realisedAnimationMotionPref ===
AnimationMotionPref.On
)
this._requestVideoFrameCallback =
video.requestVideoFrameCallback(requestFrame);
};
if (video.readyState < video.HAVE_CURRENT_DATA)
video.addEventListener("loadeddata", requestFrame);
else await requestFrame();
} else {
const frames = this._imgSource;
let frameIndex = 0;
if (this._animationTimeout) {
clearTimeout(this._animationTimeout);
this._animationTimeout = null;
}
const drawFrame = async () => {
if (frames.length === 0) return;
const frame = frames[frameIndex];
const duration = frame.duration / 1000;
this.draw(frame);
const realisedAnimationMotionPref = realisedAmp;
if (
frames.length > 1 &&
realisedAnimationMotionPref ===
AnimationMotionPref.On
) {
frameIndex = (frameIndex + 1) % frames.length;
this._animationTimeout = setTimeout(
() => drawFrame(),
duration,
);
}
};
await drawFrame();
}
}
}
}
Expand Down
23 changes: 21 additions & 2 deletions src/components/cards/thumbnail.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import * as React from "react";
import { RSSItem } from "../../scripts/models/item";
import CachedImg from "./cached-img";
import { getThumbnailTypePref } from "../../scripts/settings";
import {
type AnimationMotionPref,
type ThumbnailTypePref,
} from "../../schema-types";
import { type RootState, useAppSelector } from "../../scripts/reducer";

type CardThumbnailProps = {
item: RSSItem;
Expand All @@ -12,28 +16,43 @@ type CardThumbnailProps = {

const mediaElement = (
src: string,
animationPref: AnimationMotionPref,
className: string,
width: number | null,
height: number | null,
) => {
return (
<CachedImg
src={src}
animationPref={animationPref}
className={className}
width={width}
height={height}></CachedImg>
);
};

/** Selector for animation motion preferences. */
function useAnimationMotionPref(state: RootState) {
return state.app.animationMotionPref;
}

/** Selector for the thumbnail preference. */
function useThumbnailTypePref(state: RootState): ThumbnailTypePref {
return state.app.thumbnailTypePref;
}

function CardThumbnail(props: CardThumbnailProps): React.JSX.Element {
const preferredThumbnailType = getThumbnailTypePref();
const preferredThumbnailType = useAppSelector(useThumbnailTypePref);
const animationMotionPref = useAppSelector(useAnimationMotionPref);

const preferredThumbnail = props.item.thumbnails?.filter(
(t) => t.type === preferredThumbnailType,
)?.[0];
const selectedThumbnail = preferredThumbnail ?? props.item.thumbnails?.[0];
return selectedThumbnail
? mediaElement(
new URL(selectedThumbnail.url, props.item.link).toString(),
animationMotionPref,
props.className,
props.width,
props.height,
Expand Down
38 changes: 23 additions & 15 deletions src/components/settings/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ import {
getThemeSettings,
setThemeSettings,
getAnimationMotionPref,
setAnimationMotionPref,
setDefaultOpenTargetPref,
getNativeWindowFramePref,
setNativeWindowFramePref,
exportAll,
getThumbnailTypePref,
setThumbnailTypePref,
} from "../../scripts/settings";
import { SET_DEFAULT_OPEN_TARGET } from "../../scripts/models/app";
import {
SET_DEFAULT_OPEN_TARGET,
setThumbnailPref,
setAnimationPref,
} from "../../scripts/models/app";
import {
ChoiceGroup,
DefaultButton,
Expand Down Expand Up @@ -382,13 +383,18 @@ function PacSettings() {
);
}

/** Selector for the animation preference. */
function useAnimationMotionPref(state: RootState): AnimationMotionPref {
return state.app.animationMotionPref;
}

/**
* React component for animation preference dropdown.
*/
function AnimationPreferences(): React.JSX.Element {
const [animationProp, setAnimationProp] = React.useState(
getAnimationMotionPref(),
);
const dispatch = useAppDispatch();
const animationPref = useAppSelector(useAnimationMotionPref);

const preferenceOptions: IDropdownOption[] = [
{
key: AnimationMotionPref.System,
Expand All @@ -408,8 +414,7 @@ function AnimationPreferences(): React.JSX.Element {
},
];
const prefChange = (_: any, item: IDropdownOption) => {
setAnimationMotionPref(item.key as AnimationMotionPref);
setAnimationProp(item.key as AnimationMotionPref);
dispatch(setAnimationPref(item.key as AnimationMotionPref));
};

return (
Expand All @@ -421,7 +426,7 @@ function AnimationPreferences(): React.JSX.Element {
<Stack.Item>
<Dropdown
options={preferenceOptions}
selectedKey={animationProp}
selectedKey={animationPref}
onChange={prefChange}
style={{ width: 200 }}
/>
Expand Down Expand Up @@ -459,10 +464,14 @@ function NativeWindowFramePreference(): React.JSX.Element {
);
}

/** Selector for the thumbnail preference. */
function useThumbnailTypePref(state: RootState): ThumbnailTypePref {
return state.app.thumbnailTypePref;
}

function ThumbnailTypePreferences() {
const [thumbnailType, setThumbnailType] = React.useState(
getThumbnailTypePref(),
);
const dispatch = useAppDispatch();
const thumbnailType = useAppSelector(useThumbnailTypePref);
const preferenceOptions: IDropdownOption[] = [
{
key: ThumbnailTypePref.OpenGraph,
Expand All @@ -484,8 +493,7 @@ function ThumbnailTypePreferences() {
},
];
const prefChange = (_: any, item: IDropdownOption) => {
setThumbnailTypePref(item.key as ThumbnailTypePref);
setThumbnailType(item.key as ThumbnailTypePref);
dispatch(setThumbnailPref(item.key as ThumbnailTypePref));
};

return (
Expand Down
11 changes: 4 additions & 7 deletions src/main/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ export function setThemeListener(manager: WindowManager) {
}

const ANIMATION_MOTION_PREF_KEY = "animationMotionPref";
ipcMain.on("get-animation-motion-pref", (event) => {
event.returnValue = getStore().get(
ipcMain.handle("get-animation-motion-pref", () => {
return getStore().get(
ANIMATION_MOTION_PREF_KEY,
AnimationMotionPref.System,
);
Expand All @@ -126,11 +126,8 @@ ipcMain.handle("set-window-native-frame-pref", (_, pref: boolean) => {
});

const THUMBNAIL_TYPE_PREF = "thumbnailTypePref";
ipcMain.on("get-thumbnail-type-pref", (event) => {
event.returnValue = getStore().get(
THUMBNAIL_TYPE_PREF,
ThumbnailTypePref.OpenGraph,
);
ipcMain.handle("get-thumbnail-type-pref", () => {
return getStore().get(THUMBNAIL_TYPE_PREF, ThumbnailTypePref.OpenGraph);
});
ipcMain.handle("set-thumbnail-type-pref", (_, pref: ThumbnailTypePref) => {
getStore().set(THUMBNAIL_TYPE_PREF, pref);
Expand Down
Loading
Loading