From c6e9784172c40874c1da06507d9789d78aeb6c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Szpanelewski?= Date: Wed, 20 Mar 2024 20:24:53 +0100 Subject: [PATCH] feat(custom-commands): Expand custom command arg with placeholder and hidden options * Separate CustomCommandItem component --- .../app/devtools/ReactotronConfig.ts | 26 +++- .../components/CustomCommandItem.tsx | 118 ++++++++++++++++ .../renderer/pages/customCommands/index.tsx | 128 +----------------- .../reducers/customCommandItemReducer.tsx | 42 ++++++ .../src/reactotron-core-client.ts | 2 + .../CustomCommands/useCustomCommands.ts | 10 +- 6 files changed, 196 insertions(+), 130 deletions(-) create mode 100644 apps/reactotron-app/src/renderer/pages/customCommands/components/CustomCommandItem.tsx create mode 100644 apps/reactotron-app/src/renderer/pages/customCommands/reducers/customCommandItemReducer.tsx diff --git a/apps/example-app/app/devtools/ReactotronConfig.ts b/apps/example-app/app/devtools/ReactotronConfig.ts index e8f931d60..bc00f3d79 100644 --- a/apps/example-app/app/devtools/ReactotronConfig.ts +++ b/apps/example-app/app/devtools/ReactotronConfig.ts @@ -42,6 +42,13 @@ if (Platform.OS !== "web") { }) } +type Arg = { + name: AN + type: AT + placeholder?: string + hidden?: boolean +} + /** * Reactotron allows you to define custom commands that you can run * from Reactotron itself, and they will run in your app. @@ -83,7 +90,7 @@ reactotron.onCustomCommand({ }, }) -reactotron.onCustomCommand<[{ name: "route"; type: ArgType.String }]>({ +reactotron.onCustomCommand<[Arg<"route">]>({ command: "navigateTo", handler: (args) => { const { route } = args ?? {} @@ -109,6 +116,23 @@ reactotron.onCustomCommand({ }, }) +reactotron.onCustomCommand<[Arg<"data">]>({ + title: "Log hidden data", + description: "Logs hidden input data", + command: "logHiddenData", + handler: (args) => { + Reactotron.log(`Hidden data: ${args?.data}`) + }, + args: [ + { + name: "data", + placeholder: "Provide hidden data", + hidden: true, + type: ArgType.String, + }, + ], +}) + /** * We're going to add `console.tron` to the Reactotron object. * Now, anywhere in our app in development, we can use Reactotron like so: diff --git a/apps/reactotron-app/src/renderer/pages/customCommands/components/CustomCommandItem.tsx b/apps/reactotron-app/src/renderer/pages/customCommands/components/CustomCommandItem.tsx new file mode 100644 index 000000000..95de104a7 --- /dev/null +++ b/apps/reactotron-app/src/renderer/pages/customCommands/components/CustomCommandItem.tsx @@ -0,0 +1,118 @@ +import type { CustomCommand } from "reactotron-core-ui" +import React, { useReducer } from "react" +import { + CustomCommandItemState, + CustomCommandItemActionType, + customCommandItemReducer, + customCommandItemReducerInitializer, +} from "../reducers/customCommandItemReducer" +import styled from "styled-components" + +const ButtonContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; + margin-bottom: 24px; + color: ${(props) => props.theme.foreground}; +` +const Title = styled.div` + font-size: 24px; + margin-bottom: 12px; +` +const Description = styled.div` + margin-bottom: 12px; +` +const ArgsContainer = styled.div` + margin-bottom: 24px; +` +const SendButton = styled.div` + display: flex; + align-items: center; + justify-content: center; + background-color: ${(props) => props.theme.backgroundLighter}; + border-radius: 4px; + width: 200px; + min-height: 50px; + margin-bottom: 24px; + cursor: pointer; + color: white; + transition: background-color 0.25s ease-in-out; + + &:hover { + background-color: #e73435; + } +` +const ArgContainer = styled.div` + &:not(:last-child) { + margin-bottom: 12px; + } +` +const ArgName = styled.div` + margin-bottom: 8px; +` +const ArgInput = styled.input` + padding: 10px 12px; + outline: none; + border-radius: 4px; + width: 90%; + border: none; + font-size: 16px; +` + +export default function CustomCommandItem({ + customCommand, + sendCustomCommand, +}: { + customCommand: CustomCommand + sendCustomCommand: (command: string, args: CustomCommandItemState) => void +}) { + const [state, dispatch] = useReducer( + customCommandItemReducer, + customCommand.args, + customCommandItemReducerInitializer + ) + + const handleArgInputChange = (argName: string) => { + return (event: React.ChangeEvent) => { + dispatch({ + type: CustomCommandItemActionType.UPDATE_ARG, + payload: { + argName, + value: event.target.value, + }, + }) + } + } + + return ( + + {customCommand.title || customCommand.command} + {customCommand.description || "No Description Provided"} + {!!customCommand.args && customCommand.args.length > 0 && ( + + {customCommand.args.map((arg) => { + const hidden = arg.hidden || false + return ( + + {arg.name} + + + ) + })} + + )} + { + sendCustomCommand(customCommand.command, state) + }} + > + Send Command + + + ) +} diff --git a/apps/reactotron-app/src/renderer/pages/customCommands/index.tsx b/apps/reactotron-app/src/renderer/pages/customCommands/index.tsx index b297826ee..e69c75d89 100644 --- a/apps/reactotron-app/src/renderer/pages/customCommands/index.tsx +++ b/apps/reactotron-app/src/renderer/pages/customCommands/index.tsx @@ -1,10 +1,9 @@ -import React, { useState, useContext, useReducer } from "react" +import React, { useState, useContext } from "react" import { Header, EmptyState, CustomCommandsContext } from "reactotron-core-ui" -import type { CustomCommand } from "reactotron-core-ui" import styled from "styled-components" import { MdSearch } from "react-icons/md" import { FaMagic } from "react-icons/fa" -import { produce } from "immer" +import CustomCommandItem from "./components/CustomCommandItem" const Container = styled.div` display: flex; @@ -42,129 +41,6 @@ const SearchInput = styled.input` font-size: 14px; ` -const ButtonContianer = styled.div` - display: flex; - flex-direction: column; - width: 100%; - margin-bottom: 24px; - color: ${(props) => props.theme.foreground}; -` -const Title = styled.div` - font-size: 24px; - margin-bottom: 12px; -` -const Description = styled.div` - margin-bottom: 12px; -` -const ArgsContainer = styled.div` - margin-bottom: 24px; -` -const SendButton = styled.div` - display: flex; - align-items: center; - justify-content: center; - background-color: ${(props) => props.theme.backgroundLighter}; - border-radius: 4px; - width: 200px; - min-height: 50px; - margin-bottom: 24px; - cursor: pointer; - color: white; - transition: background-color 0.25s ease-in-out; - - &:hover { - background-color: #e73435; - } -` -const ArgContainer = styled.div` - &:not(:last-child) { - margin-bottom: 12px; - } -` -const ArgName = styled.div` - margin-bottom: 8px; -` -const ArgInput = styled.input` - padding: 10px 12px; - outline: none; - border-radius: 4px; - width: 90%; - border: none; - font-size: 16px; -` - -// TODO: This item thing is getting complicated, move it out! -// TODO: Better typing -function customCommandItemReducer(state: any, action: any) { - switch (action.type) { - case "UPDATE_ARG": - return produce(state, (draftState) => { - draftState[action.payload.argName] = action.payload.value - }) - default: - return state - } -} - -function CustomCommandItem({ - customCommand, - sendCustomCommand, -}: { - customCommand: CustomCommand - sendCustomCommand: (command: any, args: any) => void -}) { - const [state, dispatch] = useReducer(customCommandItemReducer, customCommand.args, (args) => { - if (!args) return {} - - const argMap = {} - - args.forEach((arg) => { - argMap[arg.name] = "" - }) - - return argMap - }) - - return ( - - {customCommand.title || customCommand.command} - {customCommand.description || "No Description Provided"} - {!!customCommand.args && customCommand.args.length > 0 && ( - - {customCommand.args.map((arg) => { - return ( - - {arg.name} - { - dispatch({ - type: "UPDATE_ARG", - payload: { - argName: arg.name, - value: e.target.value, - }, - }) - }} - /> - - ) - })} - - )} - { - sendCustomCommand(customCommand.command, state) - }} - > - Send Command - - - ) -} - function CustomCommands() { const [isSearchOpen, setSearchOpen] = useState(false) const [search, setSearch] = useState("") diff --git a/apps/reactotron-app/src/renderer/pages/customCommands/reducers/customCommandItemReducer.tsx b/apps/reactotron-app/src/renderer/pages/customCommands/reducers/customCommandItemReducer.tsx new file mode 100644 index 000000000..ba52afeb3 --- /dev/null +++ b/apps/reactotron-app/src/renderer/pages/customCommands/reducers/customCommandItemReducer.tsx @@ -0,0 +1,42 @@ +import { produce } from "immer" +import { CustomCommandArg } from "reactotron-core-client" + +export enum CustomCommandItemActionType { + UPDATE_ARG = "UPDATE_ARG", +} + +export type CustomCommandItemState = { + [argName: string]: string +} +export type CustomCommandItemActionPayload = { + argName: string + value: string +} +export type CustomCommandItemAction = { + type: CustomCommandItemActionType + payload: CustomCommandItemActionPayload +} + +export function customCommandItemReducer( + state: CustomCommandItemState, + action: CustomCommandItemAction +) { + switch (action.type) { + case CustomCommandItemActionType.UPDATE_ARG: + return produce(state, (draftState) => { + draftState[action.payload.argName] = action.payload.value + }) + default: + return state + } +} + +export function customCommandItemReducerInitializer(args: CustomCommandArg[]) { + if (!args) { + return {} + } + return args.reduce((acc, arg) => { + acc[arg.name] = "" + return acc + }, {}) +} diff --git a/lib/reactotron-core-client/src/reactotron-core-client.ts b/lib/reactotron-core-client/src/reactotron-core-client.ts index d522a61ac..9d1621832 100644 --- a/lib/reactotron-core-client/src/reactotron-core-client.ts +++ b/lib/reactotron-core-client/src/reactotron-core-client.ts @@ -25,6 +25,8 @@ export enum ArgType { export interface CustomCommandArg { name: string type: ArgType + placeholder?: string + hidden?: boolean } // #region Plugin Types diff --git a/lib/reactotron-core-ui/src/contexts/CustomCommands/useCustomCommands.ts b/lib/reactotron-core-ui/src/contexts/CustomCommands/useCustomCommands.ts index 42ffb6996..adb89b6d9 100644 --- a/lib/reactotron-core-ui/src/contexts/CustomCommands/useCustomCommands.ts +++ b/lib/reactotron-core-ui/src/contexts/CustomCommands/useCustomCommands.ts @@ -5,15 +5,19 @@ import type { Command } from "reactotron-core-contract" import { CommandType } from "reactotron-core-contract" import ReactotronContext from "../Reactotron" +export type CustomCommandArg = { + name: string + placeholder?: string + hidden?: boolean +} + export interface CustomCommand { clientId: string id: string title?: string command: string description?: string - args?: { - name: string - }[] + args?: CustomCommandArg[] } interface CustomCommandState {