diff options
author | Shin'ya Ueoka <ueokande@i-beam.org> | 2021-04-11 22:30:41 +0900 |
---|---|---|
committer | Shin'ya Ueoka <ueokande@i-beam.org> | 2021-04-11 22:34:55 +0900 |
commit | 8a5bba1da639355a25da8c279a9f1cf0a7300a9f (patch) | |
tree | 2f82e9184e2a249cff4a607764f88b724fae1013 /src/console | |
parent | 6767b38c8e57c9f436936dc02ad1c8c4ffd043b2 (diff) |
Replace app state with Custom Hooks
Diffstat (limited to 'src/console')
-rw-r--r-- | src/console/actions/console.ts | 118 | ||||
-rw-r--r-- | src/console/app/actions.ts | 82 | ||||
-rw-r--r-- | src/console/app/contexts.ts | 9 | ||||
-rw-r--r-- | src/console/app/hooks.ts | 115 | ||||
-rw-r--r-- | src/console/app/provider.tsx | 14 | ||||
-rw-r--r-- | src/console/app/recuer.ts (renamed from src/console/reducers/console.ts) | 30 | ||||
-rw-r--r-- | src/console/components/AppContext.ts | 13 | ||||
-rw-r--r-- | src/console/components/CommandPrompt.tsx | 13 | ||||
-rw-r--r-- | src/console/components/Console.tsx | 36 | ||||
-rw-r--r-- | src/console/components/FindPrompt.tsx | 12 | ||||
-rw-r--r-- | src/console/index.tsx | 62 |
11 files changed, 302 insertions, 202 deletions
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<HideCommandAction> => { - 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<State>(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 ( + <AppStateContext.Provider value={state}> + <AppDispatchContext.Provider value={dispatch}> + {children} + </AppDispatchContext.Provider> + </AppStateContext.Provider> + ); +}; diff --git a/src/console/reducers/console.ts b/src/console/app/recuer.ts index babad69..e3043ee 100644 --- a/src/console/reducers/console.ts +++ b/src/console/app/recuer.ts @@ -1,12 +1,12 @@ import { - CONSOLE_HIDE, - CONSOLE_HIDE_COMMAND, - CONSOLE_SHOW_COMMAND, - CONSOLE_SHOW_ERROR, - CONSOLE_SHOW_FIND, - CONSOLE_SHOW_INFO, - ConsoleAction, -} from "../actions/console"; + HIDE, + HIDE_COMMAND, + SHOW_COMMAND, + SHOW_ERROR, + SHOW_FIND, + SHOW_INFO, + AppAction, +} from "./actions"; export interface State { mode: string; @@ -23,24 +23,24 @@ export const defaultState = { // eslint-disable-next-line max-lines-per-function export default function reducer( state: State = defaultState, - action: ConsoleAction + action: AppAction ): State { switch (action.type) { - case CONSOLE_HIDE: + case HIDE: return { ...state, mode: "" }; - case CONSOLE_SHOW_COMMAND: + case SHOW_COMMAND: return { ...state, mode: "command", consoleText: action.text, }; - case CONSOLE_SHOW_FIND: + case SHOW_FIND: return { ...state, mode: "find", consoleText: "" }; - case CONSOLE_SHOW_ERROR: + case SHOW_ERROR: return { ...state, mode: "error", messageText: action.text }; - case CONSOLE_SHOW_INFO: + case SHOW_INFO: return { ...state, mode: "info", messageText: action.text }; - case CONSOLE_HIDE_COMMAND: + case HIDE_COMMAND: return { ...state, mode: 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<Promise<ConsoleAction> | 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ initialInputValue }) => { const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { 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 <CommandPrompt initialInputValue={state.consoleText} />; - case "find": - return <FindPrompt />; - case "info": - return <InfoMessage>{state.messageText}</InfoMessage>; - case "error": - return <ErrorMessage>{state.messageText}</ErrorMessage>; - default: - return null; + if (visibleCommand) { + return <CommandPrompt initialInputValue={initialInputValue} />; + } else if (visibleFind) { + return <FindPrompt />; + } else if (visibleInfo) { + return <InfoMessage>{infoMessage}</InfoMessage>; + } else if (visibleError) { + return <ErrorMessage>{errorMessage}</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<HTMLInputElement>) => { 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 = <T extends unknown>( - dispatch: React.Dispatch<T> -): React.Dispatch<T | Promise<T>> => { - return (action: T | Promise<T>) => { - 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<any> => { 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 ( - <AppContext.Provider value={{ state, dispatch: wrapAsync(dispatch) }}> - <ColorSchemeProvider> - <Console /> - </ColorSchemeProvider> - </AppContext.Provider> - ); + return <Console />; }; +const App: React.FC = () => ( + <AppProvider> + <ColorSchemeProvider> + <RootComponent /> + </ColorSchemeProvider> + </AppProvider> +); + window.addEventListener("DOMContentLoaded", () => { const wrapper = document.getElementById("vimvixen-console"); - ReactDOM.render(<RootComponent />, wrapper); + ReactDOM.render(<App />, wrapper); }); |