Skip to content

Commit

Permalink
feat(react): new react hooks to communicate between functional-compon…
Browse files Browse the repository at this point in the history
…ent and model/controller (PR #2002)

- useTranslator
- useDispatch and useSelector
- useApi
- useKeyboardShortcut
  • Loading branch information
panaC authored Sep 21, 2023
1 parent c88f70b commit 87d059c
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 0 deletions.
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@
"timeout-signal": "^2.0.0",
"tmp": "^0.2.1",
"typed-redux-saga": "^1.5.0",
"use-sync-external-store": "^1.2.0",
"uuid": "^9.0.0",
"validator": "^13.11.0",
"xml-js": "^1.6.11",
Expand Down Expand Up @@ -335,6 +336,7 @@
"@types/remote-redux-devtools": "^0.5.5",
"@types/tmp": "^0.2.3",
"@types/urijs": "^1.19.19",
"@types/use-sync-external-store": "^0.0.4",
"@types/uuid": "^9.0.2",
"@types/validator": "^13.11.1",
"@types/xmldom": "^0.1.31",
Expand Down
20 changes: 20 additions & 0 deletions src/common/services/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,23 @@ export type I18nTyped = TFunction;
@injectable()
export class Translator {
public translate = this._translate as I18nTyped;
public subscribe = this._subscribe.bind(this);
private locale = "en";
private listeners: Set<() => void>;

constructor() {
this.listeners = new Set();
}

private _subscribe(fn: () => void) {
if (fn) {
this.listeners.add(fn);
return () => {
this.listeners.delete(fn);
};
}
return () => {};
}

public getLocale(): string {
return this.locale;
Expand All @@ -215,6 +231,10 @@ export class Translator {
} else {
resolve();
}
}).finally(() => {
for (const listener of this.listeners) {
listener();
}
});
}

Expand Down
36 changes: 36 additions & 0 deletions src/renderer/common/hooks/useApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import * as React from "react";
import { ReactReduxContext } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import { TApiMethod, TApiMethodName } from "readium-desktop/common/api/api.type";
import { TModuleApi } from "readium-desktop/common/api/moduleApi.type";
import { TMethodApi } from "readium-desktop/common/api/methodApi.type";
import { apiActions } from "readium-desktop/common/redux/actions";
import { ApiResponse } from "readium-desktop/common/redux/states/api";
import { TReturnPromiseOrGeneratorType } from "readium-desktop/typings/api";
import { useSyncExternalStore } from "./useSyncExternalStore";

export function useApi<T extends TApiMethodName>(_requestId: string, apiPath: T, ...requestData: Parameters<TApiMethod[T]>): ApiResponse<TReturnPromiseOrGeneratorType<TApiMethod[T]>> {

const requestId = _requestId || React.useMemo(() => uuidv4(), []);
const { store } = React.useContext(ReactReduxContext);
React.useEffect(() => {
const splitPath = apiPath.split("/");
const moduleId = splitPath[0] as TModuleApi;
const methodId = splitPath[1] as TMethodApi;
store.dispatch(apiActions.request.build(requestId, moduleId, methodId, requestData));

return () => {
store.dispatch(apiActions.clean.build(requestId));
};
}, []); // componentDidMount

const apiResult = useSyncExternalStore(store.subscribe, () => store.getState().api[requestId]);
return apiResult;
};
18 changes: 18 additions & 0 deletions src/renderer/common/hooks/useDispatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import * as React from "react";
import { ReactReduxContext} from "react-redux";
import { Action } from "readium-desktop/common/models/redux";
import { Dispatch } from "redux";

export function useDispatch<A extends Action<any, any, any>>(): Dispatch<A> {

const {store} = React.useContext(ReactReduxContext);
const storeDispatchFn = store.dispatch;
return storeDispatchFn;
}
23 changes: 23 additions & 0 deletions src/renderer/common/hooks/useKeyboardShortcut.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import * as React from "react";
import { TKeyboardShortcutReadOnly } from "readium-desktop/common/keyboard";
import { registerKeyboardListener, unregisterKeyboardListener } from "../keyboard";
import { useSelector } from "./useSelector";
import { ICommonRootState } from "readium-desktop/common/redux/states/commonRootState";

export function useKeyboardShortcut(ListenForKeyUP: boolean, keyboardShortcut: (s: ICommonRootState["keyboard"]["shortcuts"]) => TKeyboardShortcutReadOnly, callback: () => void) {

const keyboardShortcutState = useSelector((state: ICommonRootState) => state.keyboard.shortcuts);
React.useEffect(() => {
registerKeyboardListener(ListenForKeyUP, keyboardShortcut(keyboardShortcutState), callback);
return () => unregisterKeyboardListener(callback);
}, [keyboardShortcutState]);

return ;
}
17 changes: 17 additions & 0 deletions src/renderer/common/hooks/useSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import * as React from "react";
import { ReactReduxContext, ReactReduxContextValue } from "react-redux";
import { useSyncExternalStore } from "./useSyncExternalStore";

export function useSelector<State, Selected>(selector: (state: State) => Selected): Selected {

const {store} = React.useContext<ReactReduxContextValue<State>>(ReactReduxContext);
const selected = useSyncExternalStore(store.subscribe, () => selector(store.getState()));
return selected;
}
9 changes: 9 additions & 0 deletions src/renderer/common/hooks/useSyncExternalStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import { useSyncExternalStore } from "use-sync-external-store/shim";
export { useSyncExternalStore };
26 changes: 26 additions & 0 deletions src/renderer/common/hooks/useTranslator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// ==LICENSE-BEGIN==
// Copyright 2017 European Digital Reading Lab. All rights reserved.
// Licensed to the Readium Foundation under one or more contributor license agreements.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import * as React from "react";
import { Translator } from "readium-desktop/common/services/translator";
import { TranslatorContext } from "readium-desktop/renderer/common/translator.context";

export function useTranslator(): [typeof Translator.prototype.translate, Translator] {

const translator = React.useContext(TranslatorContext);
const { translate: __ } = translator;

const [, forceUpdate] = React.useReducer(x => x + 1, 0);
React.useEffect(() => {
const handleLocaleChange = () => {
forceUpdate();
};
return translator.subscribe(handleLocaleChange);
}, [translator.subscribe]);

return [__, translator];
}

0 comments on commit 87d059c

Please sign in to comment.