Skip to content

Commit 84d345f

Browse files
committed
Timeline and page tab
1 parent 9c1c753 commit 84d345f

31 files changed

Lines changed: 1977 additions & 1005 deletions

package-lock.json

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"dependencies": {
7474
"clsx": "^1.2.1",
7575
"lucide-react": "^0.252.0",
76+
"react-json-view-lite": "^0.9.6",
7677
"react-use-websocket": "^4.3.1"
7778
}
78-
}
79+
}
Lines changed: 76 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,112 @@
11
import clsx from "clsx";
22
import { Logo } from "./Logo";
33
import { useEffect, useState } from "react";
4-
import { GitMerge, Package, Server, Terminal } from "lucide-react";
5-
import { RoutesTab } from "./tabs/RoutesTab";
6-
import { TerminalTab } from "./tabs/TerminalTab";
7-
import { ServerTab } from "./tabs/ServerTab";
8-
import { PageTab } from "./tabs/PageTab";
4+
import { RDTContextProvider } from "./context/RDTContext";
5+
import { tabs } from "./tabs";
6+
import { useTimelineHandler } from "./hooks/useTimelineHandler";
7+
import { useRDTContext } from "./context/useRDTContext";
8+
import { isDev } from "./utils/isDev";
9+
import { useGetSocket } from "./hooks/useGetSocket";
10+
import { Radio } from "lucide-react";
911

10-
interface RemixDevToolsProps {}
11-
12-
let hydrating = true;
13-
14-
function useHydrated() {
15-
let [hydrated, setHydrated] = useState(() => !hydrating);
16-
17-
useEffect(function hydrate() {
18-
hydrating = false;
19-
setHydrated(true);
20-
}, []);
21-
22-
return hydrated;
23-
}
24-
25-
type Tabs = "routes" | "terminal" | "server" | "page";
26-
27-
interface Tab {
28-
id: Tabs;
29-
name: string;
30-
icon: React.ReactNode;
31-
component: JSX.Element;
32-
}
33-
34-
const RemixDevTools = ({}: RemixDevToolsProps) => {
12+
const RemixDevTools = () => {
3513
const [isOpen, setIsOpen] = useState(true);
36-
const [activeTab, setActiveTab] = useState<Tabs>("page");
37-
const hydrated = useHydrated();
38-
39-
if (!hydrated) return null;
40-
const tabs: Tab[] = [
41-
{
42-
name: "Page",
43-
icon: <Package size={16} />,
44-
id: "page",
45-
component: <PageTab />,
46-
},
47-
{
48-
name: "Routes",
49-
icon: <GitMerge size={16} />,
50-
id: "routes",
51-
component: <RoutesTab />,
52-
},
53-
{
54-
name: "Terminal",
55-
icon: <Terminal size={16} />,
56-
id: "terminal",
57-
component: <TerminalTab />,
58-
},
59-
{
60-
name: "Server",
61-
icon: <Server size={16} />,
62-
id: "server",
63-
component: <ServerTab />,
64-
},
65-
];
14+
const { activeTab, setActiveTab, setShouldConnectWithForge } =
15+
useRDTContext();
6616

17+
useTimelineHandler();
18+
const { isConnected, isConnecting } = useGetSocket();
6719
const Component = tabs.find((tab) => tab.id === activeTab)?.component;
68-
return hydrated ? (
20+
console.log(window.__remixManifest.routes);
21+
return (
6922
<div className="remix-dev-tools">
7023
<div
7124
style={{ zIndex: 9999 }}
7225
onClick={() => setIsOpen(!isOpen)}
7326
className={
74-
"fixed cursor-pointer bottom-0 m-1 w-14 h-14 right-0 rounded-full "
27+
"rdt-fixed rdt-cursor-pointer rdt-bottom-0 rdt-m-1.5 rdt-w-14 rdt-h-14 rdt-right-0 rdt-rounded-full "
7528
}
7629
>
7730
<Logo
7831
className={clsx(
79-
"rounded-full w-14 h-14 duration-200 transition-all",
80-
"hover:cursor-pointer hover:ring-2 ring-slate-600"
32+
"rdt-rounded-full rdt-w-14 rdt-h-14 rdt-duration-200 rdt-transition-all",
33+
"rdt-hover:cursor-pointer rdt-hover:ring-2 rdt-ring-slate-600"
8134
)}
8235
/>
8336
</div>
8437

8538
<div
8639
style={{ zIndex: 9998 }}
8740
className={clsx(
88-
"fixed flex flex-col left-0 box-border bottom-0 transition-all duration-600 opacity-0 bg-[#212121] w-screen text-white",
89-
isOpen ? "h-96 drop-shadow-2xl opacity-100" : "h-0"
41+
"rdt-fixed rdt-flex rdt-flex-col rdt-resize-y rdt-overflow-auto rdt-left-0 rdt-box-border rdt-bottom-0 rdt-transition-all rdt-duration-600 rdt-opacity-0 rdt-bg-[#212121] rdt-w-screen rdt-text-white",
42+
isOpen
43+
? "rdt-drop-shadow-2xl rdt-h-[40vh] rdt-opacity-100"
44+
: "rdt-h-0"
9045
)}
9146
>
92-
<div className="flex h-8 w-full bg-gray-800">
93-
{tabs.map((tab) => (
47+
<div className="rdt-flex rdt-h-8 rdt-w-full rdt-bg-gray-800">
48+
{tabs
49+
.filter((tab) => !(!isConnected && tab.requiresForge))
50+
.map((tab) => (
51+
<div
52+
key={tab.id}
53+
onClick={() => setActiveTab(tab.id)}
54+
className={clsx(
55+
"rdt-flex rdt-font-sans rdt-transition-all rdt-duration-300 rdt-items-center rdt-gap-2 rdt-cursor-pointer rdt-border-r-2 rdt-px-4 rdt-border-0 rdt-border-solid rdt-border-r-[#212121] rdt-border-b rdt-border-b-[#212121]",
56+
activeTab !== tab.id && "rdt-hover:opacity-50",
57+
activeTab === tab.id && "rdt-bg-[#212121]"
58+
)}
59+
>
60+
{tab.icon} {tab.name}
61+
</div>
62+
))}
63+
{(!isConnected || isConnecting) && (
9464
<div
95-
key={tab.id}
96-
onClick={() => setActiveTab(tab.id)}
65+
onClick={() => setShouldConnectWithForge(true)}
9766
className={clsx(
98-
"flex font-sans transition-all duration-300 items-center gap-2 cursor-pointer border-r-2 px-4 border-0 border-solid border-r-[#212121] border-b border-b-[#212121]",
99-
activeTab !== tab.id && "hover:opacity-50",
100-
activeTab === tab.id && "bg-[#212121]"
67+
isConnecting
68+
? "rdt-animate-pulse rdt-pointer-events-none rdt-cursor-default"
69+
: "",
70+
"rdt-flex rdt-font-sans rdt-transition-all rdt-duration-300 rdt-items-center rdt-gap-2 rdt-cursor-pointer rdt-border-r-2 rdt-px-4 rdt-border-0 rdt-border-solid rdt-border-r-[#212121] rdt-border-b rdt-border-b-[#212121]"
10171
)}
10272
>
103-
{tab.icon} {tab.name}
73+
<Radio size={16} />
74+
{isConnecting
75+
? "Connecting to Forge..."
76+
: "Connect to Remix Forge"}
10477
</div>
105-
))}
78+
)}
10679
</div>
107-
<div className="h-full w-full flex">
108-
<div className="w-full p-2 pr-16">{Component}</div>
80+
<div className="rdt-h-full rdt-w-full rdt-flex rdt-overflow-y-auto">
81+
<div className="rdt-w-full rdt-p-2 rdt-pr-16">{Component}</div>
10982
</div>
11083
</div>
11184
</div>
112-
) : null;
85+
);
86+
};
87+
88+
let hydrating = true;
89+
90+
function useHydrated() {
91+
let [hydrated, setHydrated] = useState(() => !hydrating);
92+
93+
useEffect(function hydrate() {
94+
hydrating = false;
95+
setHydrated(true);
96+
}, []);
97+
98+
return hydrated;
99+
}
100+
101+
const RDTWithContext = () => {
102+
const hydrated = useHydrated();
103+
const isDevelopment = isDev();
104+
if (!hydrated || !isDevelopment) return null;
105+
return (
106+
<RDTContextProvider>
107+
<RemixDevTools />
108+
</RDTContextProvider>
109+
);
113110
};
114111

115-
export { RemixDevTools };
112+
export { RDTWithContext as RemixDevTools };
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { JsonView, darkStyles, defaultStyles } from "react-json-view-lite";
2+
import "react-json-view-lite/dist/index.css";
3+
4+
interface JsonRendererProps {
5+
data: string | Record<string, unknown>;
6+
levelsShown?: number;
7+
}
8+
9+
const JsonRenderer = ({ data, levelsShown = 0 }: JsonRendererProps) => {
10+
return (
11+
<JsonView
12+
shouldInitiallyExpand={(level) => level < levelsShown}
13+
style={{
14+
...darkStyles,
15+
basicChildStyle: "rdt-ml-4",
16+
container: "-rdt-ml-4",
17+
punctuation: "rdt-mr-2 ",
18+
pointer: "rdt-mr-2 rdt-text-white rdt-text-lg",
19+
label: "rdt-mr-1 rdt-text-white/80",
20+
stringValue: "rdt-text-green-500",
21+
numberValue: "rdt-text-orange-500",
22+
nullValue: "rdt-text-blue-500",
23+
undefinedValue: "rdt-text-blue-500",
24+
booleanValue: "rdt-text-purple-500",
25+
}}
26+
data={data}
27+
/>
28+
);
29+
};
30+
31+
export { JsonRenderer };
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Dispatch } from "react";
2+
import React, { useMemo, createContext, useReducer } from "react";
3+
import {
4+
RemixDevToolsActions,
5+
RemixDevToolsState,
6+
rdtReducer,
7+
initialState,
8+
} from "./rdtReducer";
9+
10+
export const RDTContext = createContext<{
11+
state: RemixDevToolsState;
12+
dispatch: Dispatch<RemixDevToolsActions>;
13+
}>({ state: initialState, dispatch: () => null });
14+
15+
RDTContext.displayName = "RDTContext";
16+
17+
interface ContextProps {
18+
children: React.ReactNode;
19+
}
20+
21+
export const REMIX_DEV_TOOLS = "remixDevTools";
22+
23+
export const RDTContextProvider = ({ children }: ContextProps) => {
24+
const existingState = sessionStorage.getItem(REMIX_DEV_TOOLS);
25+
const settings = localStorage.getItem(REMIX_DEV_TOOLS);
26+
27+
const [state, dispatch] = useReducer(rdtReducer, {
28+
...initialState,
29+
...(existingState ? JSON.parse(existingState) : {}),
30+
settings: settings
31+
? { ...initialState.settings, ...JSON.parse(settings) }
32+
: initialState.settings,
33+
});
34+
35+
const value = useMemo(() => ({ state, dispatch }), [state, dispatch]);
36+
return <RDTContext.Provider value={value}>{children}</RDTContext.Provider>;
37+
};

0 commit comments

Comments
 (0)