-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(explorer): write observer (#3169)
Co-authored-by: Karolis Ramanauskas <[email protected]>
- Loading branch information
Showing
39 changed files
with
590 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
--- | ||
"@latticexyz/explorer": patch | ||
--- | ||
|
||
World Explorer package now exports an `observer` Viem decorator that can be used to get visibility into contract writes initiated from your app. You can watch these writes stream in on the new "Observe" tab of the World Explorer. | ||
|
||
```ts | ||
import { createClient, publicActions, walletActions } from "viem"; | ||
import { observer } from "@latticexyz/explorer/observer"; | ||
|
||
const client = createClient({ ... }) | ||
.extend(publicActions) | ||
.extend(walletActions) | ||
.extend(observer()); | ||
``` | ||
|
||
By default, the `observer` action assumes the World Explorer is running at `http://localhost:13690`, but this can be customized with the `explorerUrl` option. | ||
|
||
```ts | ||
observer({ | ||
explorerUrl: "http://localhost:4444", | ||
}); | ||
``` | ||
|
||
If you want to measure the timing of transaction-to-state-change, you can also pass in a `waitForStateChange` function that takes a transaction hash and returns a partial [`TransactionReceipt`](https://viem.sh/docs/glossary/types#transactionreceipt) with `blockNumber`, `status`, and `transactionHash`. This mirrors the `waitForTransaction` function signature returned by `syncTo...` helper in `@latticexyz/store-sync`. | ||
|
||
```ts | ||
observer({ | ||
async waitForStateChange(hash) { | ||
return await waitForTransaction(hash); | ||
}, | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/usr/bin/env node | ||
// workaround for https://github.com/pnpm/pnpm/issues/1801 | ||
import "../dist/bin/explorer.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
31 changes: 31 additions & 0 deletions
31
packages/explorer/src/app/(explorer)/worlds/[worldAddress]/observe/Write.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"use client"; | ||
|
||
import { type Write } from "../../../../../observer/store"; | ||
import { msPerViewportWidth } from "./common"; | ||
|
||
export type Props = Write; | ||
|
||
export function Write({ functionSignature, time: start, events }: Props) { | ||
return ( | ||
<div className="pr-[10vw]"> | ||
<div className="font-bold opacity-40 group-hover/write:opacity-100"> | ||
{functionSignature} <span className="opacity-50">{new Date(start).toLocaleTimeString()}</span> | ||
</div> | ||
<div className="inline-grid bg-cyan-400/10 text-cyan-400"> | ||
{events.map((event) => ( | ||
<div key={event.type} className="col-start-1 flex"> | ||
<div | ||
className="pointer-events-none shrink-0" | ||
style={{ width: `${((event.time - start) / msPerViewportWidth) * 100}vw` }} | ||
/> | ||
<div className="h-[1.25em] shrink-0 whitespace-nowrap border-l-2 border-current"> | ||
<div className="pointer-events-none invisible absolute px-1 leading-[1.25em] opacity-70 group-hover/write:visible"> | ||
{event.type} <span className="text-white/60">{event.time - start}ms</span> | ||
</div> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
} |
26 changes: 26 additions & 0 deletions
26
packages/explorer/src/app/(explorer)/worlds/[worldAddress]/observe/Writes.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
"use client"; | ||
|
||
import { useStore } from "zustand"; | ||
import { KeepInView } from "../../../../../components/KeepInView"; | ||
import { store } from "../../../../../observer/store"; | ||
import { Write } from "./Write"; | ||
|
||
export function Writes() { | ||
const writes = useStore(store, (state) => Object.values(state.writes)); | ||
|
||
return ( | ||
// TODO: replace with h-full once container is stretched to full height | ||
<div className="relative h-[80vh] overflow-auto"> | ||
<KeepInView> | ||
<div className="flex flex-col gap-4 pb-10 text-xs leading-tight"> | ||
{writes.length === 0 ? <>Waiting for transactions…</> : null} | ||
{writes.map((write) => ( | ||
<div key={write.writeId} className="group/write flex gap-2"> | ||
<Write {...write} /> | ||
</div> | ||
))} | ||
</div> | ||
</KeepInView> | ||
</div> | ||
); | ||
} |
1 change: 1 addition & 0 deletions
1
packages/explorer/src/app/(explorer)/worlds/[worldAddress]/observe/common.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const msPerViewportWidth = 1000 * 60 * 1; |
5 changes: 5 additions & 0 deletions
5
packages/explorer/src/app/(explorer)/worlds/[worldAddress]/observe/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Writes } from "./Writes"; | ||
|
||
export default function ObservePage() { | ||
return <Writes />; | ||
} |
2 changes: 1 addition & 1 deletion
2
packages/explorer/src/app/(explorer)/worlds/[worldAddress]/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { redirect } from "next/navigation"; | ||
|
||
export default async function WorldPage({ params }: { params: { worldAddress: string } }) { | ||
return redirect(`/worlds/${params.worldAddress}/explorer`); | ||
return redirect(`/worlds/${params.worldAddress}/explore`); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export default function Layout({ children }: { children: React.ReactNode }) { | ||
return ( | ||
<html lang="en"> | ||
<body>{children}</body> | ||
</html> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
"use client"; | ||
|
||
import { useEffect } from "react"; | ||
import { createRelay } from "../../../observer/relay"; | ||
|
||
export function Relay() { | ||
useEffect(createRelay, []); | ||
return null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Relay } from "./Relay"; | ||
|
||
export default function ObserverRelayPage() { | ||
return <Relay />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { ReactNode, useRef } from "react"; | ||
|
||
export type Props = { | ||
className?: string; | ||
children: ReactNode; | ||
enabled?: boolean; | ||
}; | ||
|
||
export function KeepInView({ className, children, enabled = true }: Props) { | ||
const containerRef = useRef<HTMLDivElement>(null); | ||
const hoveredRef = useRef(false); | ||
const scrollBehaviorRef = useRef<ScrollBehavior>("auto"); | ||
|
||
// Intentionally not in a `useEffect` so this triggers on every render. | ||
if (!hoveredRef.current && enabled) { | ||
containerRef.current?.scrollIntoView({ | ||
behavior: scrollBehaviorRef.current, | ||
block: "end", | ||
inline: "end", | ||
}); | ||
} | ||
scrollBehaviorRef.current = "smooth"; | ||
|
||
return ( | ||
<div | ||
ref={containerRef} | ||
onMouseEnter={() => { | ||
hoveredRef.current = true; | ||
}} | ||
onMouseLeave={() => { | ||
hoveredRef.current = false; | ||
}} | ||
className={className} | ||
> | ||
{children} | ||
</div> | ||
); | ||
} |
Oops, something went wrong.