aboutsummaryrefslogtreecommitdiff
path: root/src/console
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2021-04-11 22:30:41 +0900
committerShin'ya Ueoka <ueokande@i-beam.org>2021-04-11 22:34:55 +0900
commit8a5bba1da639355a25da8c279a9f1cf0a7300a9f (patch)
tree2f82e9184e2a249cff4a607764f88b724fae1013 /src/console
parent6767b38c8e57c9f436936dc02ad1c8c4ffd043b2 (diff)
Replace app state with Custom Hooks
Diffstat (limited to 'src/console')
-rw-r--r--src/console/actions/console.ts118
-rw-r--r--src/console/app/actions.ts82
-rw-r--r--src/console/app/contexts.ts9
-rw-r--r--src/console/app/hooks.ts115
-rw-r--r--src/console/app/provider.tsx14
-rw-r--r--src/console/app/recuer.ts (renamed from src/console/reducers/console.ts)30
-rw-r--r--src/console/components/AppContext.ts13
-rw-r--r--src/console/components/CommandPrompt.tsx13
-rw-r--r--src/console/components/Console.tsx36
-rw-r--r--src/console/components/FindPrompt.tsx12
-rw-r--r--src/console/index.tsx62
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);
});