From 8a5bba1da639355a25da8c279a9f1cf0a7300a9f Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 11 Apr 2021 22:30:41 +0900 Subject: Replace app state with Custom Hooks --- src/console/actions/console.ts | 118 ------------------------------- src/console/app/actions.ts | 82 +++++++++++++++++++++ src/console/app/contexts.ts | 9 +++ src/console/app/hooks.ts | 115 ++++++++++++++++++++++++++++++ src/console/app/provider.tsx | 14 ++++ src/console/app/recuer.ts | 52 ++++++++++++++ src/console/components/AppContext.ts | 13 ---- src/console/components/CommandPrompt.tsx | 13 ++-- src/console/components/Console.tsx | 36 ++++++---- src/console/components/FindPrompt.tsx | 12 ++-- src/console/index.tsx | 62 ++++++++-------- src/console/reducers/console.ts | 52 -------------- 12 files changed, 339 insertions(+), 239 deletions(-) delete mode 100644 src/console/actions/console.ts create mode 100644 src/console/app/actions.ts create mode 100644 src/console/app/contexts.ts create mode 100644 src/console/app/hooks.ts create mode 100644 src/console/app/provider.tsx create mode 100644 src/console/app/recuer.ts delete mode 100644 src/console/components/AppContext.ts delete mode 100644 src/console/reducers/console.ts (limited to 'src/console') diff --git a/src/console/actions/console.ts b/src/console/actions/console.ts deleted file mode 100644 index 2338067..0000000 --- a/src/console/actions/console.ts +++ /dev/null @@ -1,118 +0,0 @@ -import * as messages from "../../shared/messages"; - -export const CONSOLE_SHOW_COMMAND = "console.show.command"; -export const CONSOLE_SHOW_ERROR = "console.show.error"; -export const CONSOLE_SHOW_INFO = "console.show.info"; -export const CONSOLE_HIDE_COMMAND = "console.hide.command"; -export const CONSOLE_SHOW_FIND = "console.show.find"; -export const CONSOLE_HIDE = "console.hide"; - -export interface HideAction { - type: typeof CONSOLE_HIDE; -} - -export interface ShowCommand { - type: typeof CONSOLE_SHOW_COMMAND; - text: string; -} - -export interface ShowFindAction { - type: typeof CONSOLE_SHOW_FIND; -} - -export interface ShowErrorAction { - type: typeof CONSOLE_SHOW_ERROR; - text: string; -} - -export interface ShowInfoAction { - type: typeof CONSOLE_SHOW_INFO; - text: string; -} - -export interface HideCommandAction { - type: typeof CONSOLE_HIDE_COMMAND; -} - -export type ConsoleAction = - | HideAction - | ShowCommand - | ShowFindAction - | ShowErrorAction - | ShowInfoAction - | HideCommandAction; - -const hide = (): ConsoleAction => { - return { - type: CONSOLE_HIDE, - }; -}; - -const showCommand = (text: string): ShowCommand => { - return { - type: CONSOLE_SHOW_COMMAND, - text, - }; -}; - -const showFind = (): ShowFindAction => { - return { - type: CONSOLE_SHOW_FIND, - }; -}; - -const showError = (text: string): ShowErrorAction => { - return { - type: CONSOLE_SHOW_ERROR, - text: text, - }; -}; - -const showInfo = (text: string): ShowInfoAction => { - return { - type: CONSOLE_SHOW_INFO, - text: text, - }; -}; - -const hideCommand = (): HideCommandAction => { - window.top.postMessage( - JSON.stringify({ - type: messages.CONSOLE_UNFOCUS, - }), - "*" - ); - return { - type: CONSOLE_HIDE_COMMAND, - }; -}; - -const enterCommand = async (text: string): Promise => { - await browser.runtime.sendMessage({ - type: messages.CONSOLE_ENTER_COMMAND, - text, - }); - return hideCommand(); -}; - -const enterFind = (text?: string): HideCommandAction => { - window.top.postMessage( - JSON.stringify({ - type: messages.CONSOLE_ENTER_FIND, - text, - }), - "*" - ); - return hideCommand(); -}; - -export { - hide, - showCommand, - showFind, - showError, - showInfo, - hideCommand, - enterCommand, - enterFind, -}; diff --git a/src/console/app/actions.ts b/src/console/app/actions.ts new file mode 100644 index 0000000..5538ae5 --- /dev/null +++ b/src/console/app/actions.ts @@ -0,0 +1,82 @@ +export const SHOW_COMMAND = "show.command"; +export const SHOW_ERROR = "show.error"; +export const SHOW_INFO = "show.info"; +export const HIDE_COMMAND = "hide.command"; +export const SHOW_FIND = "show.find"; +export const HIDE = "hide"; + +export interface HideAction { + type: typeof HIDE; +} + +export interface ShowCommand { + type: typeof SHOW_COMMAND; + text: string; +} + +export interface ShowFindAction { + type: typeof SHOW_FIND; +} + +export interface ShowErrorAction { + type: typeof SHOW_ERROR; + text: string; +} + +export interface ShowInfoAction { + type: typeof SHOW_INFO; + text: string; +} + +export interface HideCommandAction { + type: typeof HIDE_COMMAND; +} + +export type AppAction = + | HideAction + | ShowCommand + | ShowFindAction + | ShowErrorAction + | ShowInfoAction + | HideCommandAction; + +const hide = (): HideAction => { + return { + type: HIDE, + }; +}; + +const showCommand = (text: string): ShowCommand => { + return { + type: SHOW_COMMAND, + text, + }; +}; + +const showFind = (): ShowFindAction => { + return { + type: SHOW_FIND, + }; +}; + +const showError = (text: string): ShowErrorAction => { + return { + type: SHOW_ERROR, + text: text, + }; +}; + +const showInfo = (text: string): ShowInfoAction => { + return { + type: SHOW_INFO, + text: text, + }; +}; + +const hideCommand = (): HideCommandAction => { + return { + type: HIDE_COMMAND, + }; +}; + +export { hide, showCommand, showFind, showError, showInfo, hideCommand }; diff --git a/src/console/app/contexts.ts b/src/console/app/contexts.ts new file mode 100644 index 0000000..7e4f323 --- /dev/null +++ b/src/console/app/contexts.ts @@ -0,0 +1,9 @@ +import React from "react"; +import { State, defaultState } from "./recuer"; +import { AppAction } from "./actions"; + +export const AppStateContext = React.createContext(defaultState); + +export const AppDispatchContext = React.createContext< + (action: AppAction) => void +>(() => {}); diff --git a/src/console/app/hooks.ts b/src/console/app/hooks.ts new file mode 100644 index 0000000..eefdea3 --- /dev/null +++ b/src/console/app/hooks.ts @@ -0,0 +1,115 @@ +import React from "react"; +import * as actions from "./actions"; +import { AppDispatchContext, AppStateContext } from "./contexts"; +import * as messages from "../../shared/messages"; + +export const useHide = () => { + const dispatch = React.useContext(AppDispatchContext); + const hide = React.useCallback(() => { + window.top.postMessage( + JSON.stringify({ + type: messages.CONSOLE_UNFOCUS, + }), + "*" + ); + dispatch(actions.hide()); + }, [dispatch]); + + return hide; +}; + +export const useCommandMode = () => { + const state = React.useContext(AppStateContext); + const dispatch = React.useContext(AppDispatchContext); + + const show = React.useCallback( + (initialInputValue: string) => { + dispatch(actions.showCommand(initialInputValue)); + }, + [dispatch] + ); + + return { + visible: state.mode === "command", + initialInputValue: state.consoleText, + show, + }; +}; + +export const useFindMode = () => { + const state = React.useContext(AppStateContext); + const dispatch = React.useContext(AppDispatchContext); + + const show = React.useCallback(() => { + dispatch(actions.showFind()); + }, [dispatch]); + + return { + visible: state.mode === "find", + show, + }; +}; + +export const useInfoMessage = () => { + const state = React.useContext(AppStateContext); + const dispatch = React.useContext(AppDispatchContext); + + const show = React.useCallback( + (message: string) => { + dispatch(actions.showInfo(message)); + }, + [dispatch] + ); + + return { + visible: state.mode === "info", + message: state.mode === "info" ? state.messageText : "", + show, + }; +}; + +export const useErrorMessage = () => { + const state = React.useContext(AppStateContext); + const dispatch = React.useContext(AppDispatchContext); + + const show = React.useCallback( + (message: string) => { + dispatch(actions.showError(message)); + }, + [dispatch] + ); + + return { + visible: state.mode === "error", + message: state.mode === "error" ? state.messageText : "", + show, + }; +}; + +export const getInitialInputValue = () => { + const state = React.useContext(AppStateContext); + return state.consoleText; +}; + +export const useExecCommand = () => { + const execCommand = React.useCallback((text: string) => { + browser.runtime.sendMessage({ + type: messages.CONSOLE_ENTER_COMMAND, + text, + }); + }, []); + return execCommand; +}; + +export const useExecFind = () => { + const execFind = React.useCallback((text?: string) => { + window.top.postMessage( + JSON.stringify({ + type: messages.CONSOLE_ENTER_FIND, + text, + }), + "*" + ); + }, []); + return execFind; +}; diff --git a/src/console/app/provider.tsx b/src/console/app/provider.tsx new file mode 100644 index 0000000..397f165 --- /dev/null +++ b/src/console/app/provider.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import reducer, { defaultState } from "./recuer"; +import { AppDispatchContext, AppStateContext } from "./contexts"; + +export const AppProvider: React.FC = ({ children }) => { + const [state, dispatch] = React.useReducer(reducer, defaultState); + return ( + + + {children} + + + ); +}; diff --git a/src/console/app/recuer.ts b/src/console/app/recuer.ts new file mode 100644 index 0000000..e3043ee --- /dev/null +++ b/src/console/app/recuer.ts @@ -0,0 +1,52 @@ +import { + HIDE, + HIDE_COMMAND, + SHOW_COMMAND, + SHOW_ERROR, + SHOW_FIND, + SHOW_INFO, + AppAction, +} from "./actions"; + +export interface State { + mode: string; + messageText: string; + consoleText: string; +} + +export const defaultState = { + mode: "", + messageText: "", + consoleText: "", +}; + +// eslint-disable-next-line max-lines-per-function +export default function reducer( + state: State = defaultState, + action: AppAction +): State { + switch (action.type) { + case HIDE: + return { ...state, mode: "" }; + case SHOW_COMMAND: + return { + ...state, + mode: "command", + consoleText: action.text, + }; + case SHOW_FIND: + return { ...state, mode: "find", consoleText: "" }; + case SHOW_ERROR: + return { ...state, mode: "error", messageText: action.text }; + case SHOW_INFO: + return { ...state, mode: "info", messageText: action.text }; + case HIDE_COMMAND: + return { + ...state, + mode: + state.mode === "command" || state.mode === "find" ? "" : state.mode, + }; + default: + return state; + } +} diff --git a/src/console/components/AppContext.ts b/src/console/components/AppContext.ts deleted file mode 100644 index a930e14..0000000 --- a/src/console/components/AppContext.ts +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import { State, defaultState } from "../reducers/console"; -import { ConsoleAction } from "../actions/console"; - -const AppContext = React.createContext<{ - state: State; - dispatch: React.Dispatch | ConsoleAction>; -}>({ - state: defaultState, - dispatch: () => null, -}); - -export default AppContext; diff --git a/src/console/components/CommandPrompt.tsx b/src/console/components/CommandPrompt.tsx index 24f46ae..1b6281b 100644 --- a/src/console/components/CommandPrompt.tsx +++ b/src/console/components/CommandPrompt.tsx @@ -1,12 +1,11 @@ import React from "react"; -import * as consoleActions from "../actions/console"; -import AppContext from "./AppContext"; import Completion from "./console/Completion"; import Input from "./console//Input"; import styled from "styled-components"; import { useCompletions, useSelectCompletion } from "../completion/hooks"; import useAutoResize from "../hooks/useAutoResize"; import { CompletionProvider } from "../completion/provider"; +import { useExecCommand, useHide } from "../app/hooks"; const COMPLETION_MAX_ITEMS = 33; @@ -19,7 +18,7 @@ interface Props { } const CommandPromptInner: React.FC = ({ initialInputValue }) => { - const { dispatch } = React.useContext(AppContext); + const hide = useHide(); const [inputValue, setInputValue] = React.useState(initialInputValue); const { completions, updateCompletions } = useCompletions(); const { @@ -28,11 +27,12 @@ const CommandPromptInner: React.FC = ({ initialInputValue }) => { selectNext, selectPrev, } = useSelectCompletion(); + const execCommand = useExecCommand(); useAutoResize(); const onBlur = () => { - dispatch(consoleActions.hideCommand()); + hide(); }; const isCancelKey = React.useCallback( @@ -63,10 +63,11 @@ const CommandPromptInner: React.FC = ({ initialInputValue }) => { const onKeyDown = (e: React.KeyboardEvent) => { if (isCancelKey(e)) { - dispatch(consoleActions.hideCommand()); + hide(); } else if (isEnterKey(e)) { const value = (e.target as HTMLInputElement).value; - dispatch(consoleActions.enterCommand(value)); + execCommand(value); + hide(); } else if (isNextKey(e)) { selectNext(); } else if (isPrevKey(e)) { diff --git a/src/console/components/Console.tsx b/src/console/components/Console.tsx index c8642c8..db18fa0 100644 --- a/src/console/components/Console.tsx +++ b/src/console/components/Console.tsx @@ -3,31 +3,37 @@ import FindPrompt from "./FindPrompt"; import CommandPrompt from "./CommandPrompt"; import InfoMessage from "./InfoMessage"; import ErrorMessage from "./ErrorMessage"; -import AppContext from "./AppContext"; import { useColorSchemeRefresh } from "../colorscheme/hooks"; +import { + useCommandMode, + useErrorMessage, + useFindMode, + useInfoMessage, +} from "../app/hooks"; const Console: React.FC = () => { - const { state } = React.useContext(AppContext); const refreshColorScheme = useColorSchemeRefresh(); + const { visible: visibleCommand, initialInputValue } = useCommandMode(); + const { visible: visibleFind } = useFindMode(); + const { visible: visibleInfo, message: infoMessage } = useInfoMessage(); + const { visible: visibleError, message: errorMessage } = useErrorMessage(); React.useEffect(() => { - if (state.mode !== "") { + if (visibleCommand || visibleFind || visibleInfo || visibleError) { refreshColorScheme(); } - }, [state.mode]); + }, [visibleCommand, visibleFind, visibleInfo, visibleError]); - switch (state.mode) { - case "command": - return ; - case "find": - return ; - case "info": - return {state.messageText}; - case "error": - return {state.messageText}; - default: - return null; + if (visibleCommand) { + return ; + } else if (visibleFind) { + return ; + } else if (visibleInfo) { + return {infoMessage}; + } else if (visibleError) { + return {errorMessage}; } + return null; }; export default Console; diff --git a/src/console/components/FindPrompt.tsx b/src/console/components/FindPrompt.tsx index 10fa6c3..c437d16 100644 --- a/src/console/components/FindPrompt.tsx +++ b/src/console/components/FindPrompt.tsx @@ -1,20 +1,20 @@ import React from "react"; -import * as consoleActions from "../../console/actions/console"; -import AppContext from "./AppContext"; import Input from "./console/Input"; import styled from "styled-components"; import useAutoResize from "../hooks/useAutoResize"; +import { useExecFind, useHide } from "../app/hooks"; const ConsoleWrapper = styled.div` border-top: 1px solid gray; `; const FindPrompt: React.FC = () => { - const { dispatch } = React.useContext(AppContext); const [inputValue, setInputValue] = React.useState(""); + const hide = useHide(); + const execFind = useExecFind(); const onBlur = () => { - dispatch(consoleActions.hideCommand()); + hide(); }; useAutoResize(); @@ -24,13 +24,13 @@ const FindPrompt: React.FC = () => { e.preventDefault(); const value = (e.target as HTMLInputElement).value; - dispatch(consoleActions.enterFind(value === "" ? undefined : value)); + execFind(value === "" ? undefined : value); }; const onKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case "Escape": - dispatch(consoleActions.hideCommand()); + hide(); break; case "Enter": doEnter(e); diff --git a/src/console/index.tsx b/src/console/index.tsx index 71f2a27..29fa11f 100644 --- a/src/console/index.tsx +++ b/src/console/index.tsx @@ -1,42 +1,44 @@ import * as messages from "../shared/messages"; -import reducers, { defaultState } from "./reducers/console"; -import * as consoleActions from "./actions/console"; import Console from "./components/Console"; -import AppContext from "./components/AppContext"; import "./index.css"; import React from "react"; import ReactDOM from "react-dom"; import ColorSchemeProvider from "./colorscheme/providers"; - -const wrapAsync = ( - dispatch: React.Dispatch -): React.Dispatch> => { - return (action: T | Promise) => { - if (action instanceof Promise) { - action.then((a) => dispatch(a)).catch(console.error); - } else { - dispatch(action); - } - }; -}; +import { AppProvider } from "./app/provider"; +import { + useCommandMode, + useFindMode, + useInfoMessage, + useErrorMessage, + useHide, +} from "./app/hooks"; const RootComponent: React.FC = () => { - const [state, dispatch] = React.useReducer(reducers, defaultState); + const hide = useHide(); + const { show: showCommand } = useCommandMode(); + const { show: showFind } = useFindMode(); + const { show: showError } = useErrorMessage(); + const { show: showInfo } = useInfoMessage(); React.useEffect(() => { const onMessage = async (message: any): Promise => { const msg = messages.valueOf(message); switch (msg.type) { case messages.CONSOLE_SHOW_COMMAND: - return dispatch(await consoleActions.showCommand(msg.command)); + showCommand(msg.command); + break; case messages.CONSOLE_SHOW_FIND: - return dispatch(consoleActions.showFind()); + showFind(); + break; case messages.CONSOLE_SHOW_ERROR: - return dispatch(consoleActions.showError(msg.text)); + showError(msg.text); + break; case messages.CONSOLE_SHOW_INFO: - return dispatch(consoleActions.showInfo(msg.text)); + showInfo(msg.text); + break; case messages.CONSOLE_HIDE: - return dispatch(consoleActions.hide()); + hide(); + break; } }; @@ -47,16 +49,18 @@ const RootComponent: React.FC = () => { port.onMessage.addListener(onMessage); }, []); - return ( - - - - - - ); + return ; }; +const App: React.FC = () => ( + + + + + +); + window.addEventListener("DOMContentLoaded", () => { const wrapper = document.getElementById("vimvixen-console"); - ReactDOM.render(, wrapper); + ReactDOM.render(, wrapper); }); diff --git a/src/console/reducers/console.ts b/src/console/reducers/console.ts deleted file mode 100644 index babad69..0000000 --- a/src/console/reducers/console.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - CONSOLE_HIDE, - CONSOLE_HIDE_COMMAND, - CONSOLE_SHOW_COMMAND, - CONSOLE_SHOW_ERROR, - CONSOLE_SHOW_FIND, - CONSOLE_SHOW_INFO, - ConsoleAction, -} from "../actions/console"; - -export interface State { - mode: string; - messageText: string; - consoleText: string; -} - -export const defaultState = { - mode: "", - messageText: "", - consoleText: "", -}; - -// eslint-disable-next-line max-lines-per-function -export default function reducer( - state: State = defaultState, - action: ConsoleAction -): State { - switch (action.type) { - case CONSOLE_HIDE: - return { ...state, mode: "" }; - case CONSOLE_SHOW_COMMAND: - return { - ...state, - mode: "command", - consoleText: action.text, - }; - case CONSOLE_SHOW_FIND: - return { ...state, mode: "find", consoleText: "" }; - case CONSOLE_SHOW_ERROR: - return { ...state, mode: "error", messageText: action.text }; - case CONSOLE_SHOW_INFO: - return { ...state, mode: "info", messageText: action.text }; - case CONSOLE_HIDE_COMMAND: - return { - ...state, - mode: - state.mode === "command" || state.mode === "find" ? "" : state.mode, - }; - default: - return state; - } -} -- cgit v1.2.3