Skip to content

Commit

Permalink
77 active page tab stuck once popping rdt out into new window (#80)
Browse files Browse the repository at this point in the history
* #77 - Detached mode location tracking fix

* action color change
  • Loading branch information
AlemTuzlak authored Dec 2, 2023
1 parent 1f3045d commit 99f38c4
Show file tree
Hide file tree
Showing 32 changed files with 834 additions and 38 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "remix-development-tools",
"description": "Remix development tools - a set of tools for developing/debugging Remix.run apps",
"author": "Alem Tuzlak",
"version": "3.5.0",
"version": "3.5.1",
"license": "MIT",
"keywords": [
"remix",
Expand Down
3 changes: 2 additions & 1 deletion src/RemixDevTools/RemixDevTools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useOpenElementSource } from "./hooks/useOpenElementSource.js";
import { RdtPlugin } from "../client.js";
import { useAttachBodyListener } from "./hooks/useAttachListener.js";
import { useDebounce } from "./hooks/useDebounce.js";
import { useListenToRouteChange } from "./hooks/detached/useListenToRouteChange.js";

const DevTools = ({ plugins: pluginArray }: RemixDevToolsProps) => {
useTimelineHandler();
Expand All @@ -34,7 +35,7 @@ const DevTools = ({ plugins: pluginArray }: RemixDevToolsProps) => {
useSyncStateWhenDetached();
useDevServerConnection();
useOpenElementSource();

useListenToRouteChange();
const url = useLocation().search;
const { detachedWindowOwner, isDetached, setDetachedWindowOwner } = useDetachedWindowControls();
const { settings } = useSettingsContext();
Expand Down
3 changes: 1 addition & 2 deletions src/RemixDevTools/context/RDTContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Dispatch } from "react";
import React, { useMemo, createContext, useReducer, useEffect } from "react";
import { RemixDevToolsActions, RemixDevToolsState, rdtReducer, initialState } from "./rdtReducer.js";
import { useRemoveBody } from "../hooks/detached/useRemoveBody.js";
import { useListenToRouteChange } from "../hooks/detached/useListenToRouteChange.js";
import {
setSessionItem,
setStorageItem,
Expand Down Expand Up @@ -84,7 +83,7 @@ export const getExistingStateFromStorage = () => {
export const RDTContextProvider = ({ children }: ContextProps) => {
const [state, dispatch] = useReducer<typeof rdtReducer>(rdtReducer, getExistingStateFromStorage());
const value = useMemo(() => ({ state, dispatch }), [state, dispatch]);
useListenToRouteChange();

useRemoveBody(state);

useEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/RemixDevTools/context/rdtReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TimelineEvent } from "./timeline/types.js";
import type { Tabs } from "../tabs/index.js";
import { Terminal } from "./terminal/types.js";
import { ActionEvent, LoaderEvent } from "../../dev-server/event-queue.js";
import { cutArrayToLastN } from "../utils/common.js";
import { cutArrayToFirstN } from "../utils/common.js";

export const defaultServerRouteState: ServerRouteInfo = {
highestExecutionTime: 0,
Expand Down Expand Up @@ -268,7 +268,7 @@ export const rdtReducer = (
case "SET_TIMELINE_EVENT":
return {
...state,
timeline: cutArrayToLastN([payload, ...state.timeline], 30),
timeline: cutArrayToFirstN([payload, ...state.timeline], 30),
};

case "SET_WHOLE_STATE": {
Expand Down
13 changes: 9 additions & 4 deletions src/RemixDevTools/hooks/detached/useListenToRouteChange.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useLocation, useNavigate, useNavigation } from "@remix-run/react";
import { useEffect, useRef } from "react";
import { useAttachListener } from '../useAttachListener.js';
import { getStorageItem, setStorageItem } from '../../utils/storage.js';
import { useDetachedWindowControls } from '../../context/useRDTContext.js';
import { useAttachListener } from "../useAttachListener.js";
import { getStorageItem, setStorageItem } from "../../utils/storage.js";
import { useDetachedWindowControls } from "../../context/useRDTContext.js";
import { detachedModeSetup } from "../../context/RDTContext.js";

export const LOCAL_STORAGE_ROUTE_KEY = "rdt_route";

Expand All @@ -22,8 +23,12 @@ export const useListenToRouteChange = () => {

// Used by the owner window only
useEffect(() => {
const { detachedWindowOwner } = detachedModeSetup();
if (!detachedWindowOwner) {
return;
}
// If the route changes and this is the original window store the event into local storage
if (route !== locationRoute && detachedWindowOwner) {
if (route !== locationRoute) {
setRouteInLocalStorage(locationRoute);
}
}, [locationRoute, detachedWindowOwner, route]);
Expand Down
5 changes: 5 additions & 0 deletions src/RemixDevTools/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ export const cutArrayToLastN = <T>(arr: T[], n: number) => {
if (arr.length < n) return arr;
return arr.slice(arr.length - n);
};

export const cutArrayToFirstN = <T>(arr: T[], n: number) => {
if (arr.length < n) return arr;
return arr.slice(0, n);
};
2 changes: 1 addition & 1 deletion src/dev-server/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const actionLog = (message: string) => {
if (config.logs?.actions === false) {
return;
}
log(`${chalk.red.bold("ACTION")} ${message}`);
log(`${chalk.yellowBright.bold("ACTION")} ${message}`);
};

export const successLog = (message: string) => {
Expand Down
13 changes: 7 additions & 6 deletions src/dev-server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,16 @@ const logTrigger = (id: string, type: "action" | "loader", end: number) => {
}
};

const extractHeadersFromResponseOrRequest = (response: Response | Request): Record<string, string> => {
const extractHeadersFromResponseOrRequest = (response: Response | Request) => {
const headers = new Headers(response.headers);
return Object.fromEntries(headers.entries());
};

const extractDataFromResponseOrRequest = async (response: Response | Request): Promise<null | unknown> => {
const extractable = new Response(response.body, response)
const headers = new Headers(extractable.headers);
const contentType = headers.get("Content-Type");
try {
const extractable = new Response(response.body, response);
const headers = new Headers(extractable.headers);
const contentType = headers.get("Content-Type");
if (contentType?.includes("application/json")) {
return extractable.json();
}
Expand All @@ -170,16 +170,17 @@ const storeAndEmitActionOrLoaderInfo = async (
) => {
const isResponse = response instanceof Response;
const responseHeaders = isResponse ? extractHeadersFromResponseOrRequest(response) : null;
const requestHeaders = extractHeadersFromResponseOrRequest(args.request);
// create the event
const event = {
type,
data: {
id: route.id,
executionTime: end,
timestamp: new Date().getTime(),
responseHeaders,
requestHeaders: extractHeadersFromResponseOrRequest(args.request),
requestData: await extractDataFromResponseOrRequest(args.request),
requestHeaders,
responseHeaders,
},
};
const port =
Expand Down
15 changes: 15 additions & 0 deletions src/test-apps/remix-vite/app/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ReactNode } from "react";

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode
}

const Button = ({ children, ...props }: ButtonProps) => {
return (
<button {...props}>
{children}
</button>
);
}

export { Button };
2 changes: 1 addition & 1 deletion src/test-apps/remix-vite/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function App() {
</head>
<body>
<Form method="post">

<input readOnly type="text" name="name" value={"name"} />
<button type="submit">
Submit
</button>
Expand Down
57 changes: 54 additions & 3 deletions src/test-apps/remix-vite/app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,69 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { json, redirect, type LoaderFunctionArgs, defer } from "@remix-run/node";
import type { MetaFunction } from "@remix-run/node";
import { Link, useFetcher, useSubmit } from "@remix-run/react";
import { Button } from "../components/Button";

export const meta: MetaFunction = () => {
return [
{ title: "New Remix App" },
{ name: "description", content: "Welcome to Remix!" },
];
};

export default function Index() {

export const loader = async ({ request }: LoaderFunctionArgs) => {

const test = new Promise((resolve) => {
setTimeout(() => {
resolve("test");
}, 1000);
})
return defer({ message: "Hello World!", test });
};

export const action = async ({ request }: ActionFunctionArgs) => {
return redirect("/login");
};

export default function Index() {
const lFetcher = useFetcher();
const pFetcher = useFetcher();
const submit = useSubmit();
const data = new FormData();
data.append("test", "test");
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
<h1>Welcome to Remix</h1>
<Button
onClick={(e) => {
console.log(e);
lFetcher.submit(null, { method: "get", action: "/" })
}}
>
FETCHER Loader
</Button>
<button
onClick={() => pFetcher.submit(data, { method: "POST", action: "/" })}
>
FETCHER Action
</button>


<button onClick={() => submit(null, { method: "POST", action: "/" })}>
SUBMIT Action
</button>
<button onClick={() => submit(data, { method: "PATCH", action: "/" })}>
SUBMIT Action PATCH
</button>
<button onClick={() => submit(null, { method: "DELETE", action: "/" })}>
SUBMIT Action DELETE
</button>
<button onClick={() => submit(null, { method: "PUT", action: "/" })}>
SUBMIT Action PUT
</button>

<Link to="/login">Login</Link>
<ul>

<li>
<a
target="_blank"
Expand Down
5 changes: 5 additions & 0 deletions src/test-apps/remix-vite/app/routes/_layout.added.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function RouteComponent(){
return (
<div />
);
}
51 changes: 51 additions & 0 deletions src/test-apps/remix-vite/app/routes/_layout.final_test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { V2_MetaFunction } from "@remix-run/react/dist/routeModules";
import type { HeadersFunction, LinksFunction, LoaderArgs, ActionArgs } from "@remix-run/node";
import { useLoaderData, isRouteErrorResponse, useRouteError } from "@remix-run/react";
import type { ShouldRevalidateFunction } from "@remix-run/react";

export const links: LinksFunction = () => (
[
// your links here
]
);

export const meta: V2_MetaFunction = () => [
// your meta here
];

export const handle = () => ({
// your handler here
});

export const headers: HeadersFunction = () => (
{
// your headers here
}
);

export const loader = async ({ request }: LoaderArgs) => {
return null;
};

export const action = async ({ request }: ActionArgs) => {
return null;
};

export default function RouteComponent(){
const data = useLoaderData<typeof loader>()
return (
<div />
);
}

export function ErrorBoundary(){
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <div/>
}
return <div/>
}

export const shouldRevalidate: ShouldRevalidateFunction = () => {
return true;
};
5 changes: 5 additions & 0 deletions src/test-apps/remix-vite/app/routes/_layout.new_route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function RouteComponent(){
return (
<div />
);
}
51 changes: 51 additions & 0 deletions src/test-apps/remix-vite/app/routes/_layout.one_more_time.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { V2_MetaFunction } from "@remix-run/react/dist/routeModules";
import type { HeadersFunction, LinksFunction, LoaderArgs, ActionArgs } from "@remix-run/node";
import { useLoaderData, isRouteErrorResponse, useRouteError } from "@remix-run/react";
import type { ShouldRevalidateFunction } from "@remix-run/react";

export const links: LinksFunction = () => (
[
// your links here
]
);

export const meta: V2_MetaFunction = () => [
// your meta here
];

export const handle = () => ({
// your handler here
});

export const headers: HeadersFunction = () => (
{
// your headers here
}
);

export const loader = async ({ request }: LoaderArgs) => {
return null;
};

export const action = async ({ request }: ActionArgs) => {
return null;
};

export default function RouteComponent(){
const data = useLoaderData<typeof loader>()
return (
<div />
);
}

export function ErrorBoundary(){
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <div/>
}
return <div/>
}

export const shouldRevalidate: ShouldRevalidateFunction = () => {
return true;
};
Loading

0 comments on commit 99f38c4

Please sign in to comment.