Skip to content

Commit

Permalink
fix(storage): highlights and collapses leaking out to other subscript…
Browse files Browse the repository at this point in the history
…ions

Closes #7
  • Loading branch information
voliva committed Dec 13, 2024
1 parent 9f18d77 commit 700c3bf
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 52 deletions.
12 changes: 8 additions & 4 deletions src/codec-components/EditCodec/CStruct.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { ExpandBtn } from "@/components/Expand"
import { getFinalType } from "@/utils/shape"
import { EditStruct } from "@polkadot-api/react-builder"
import { useStateObservable } from "@react-rxjs/core"
import React from "react"
import React, { useContext } from "react"
import { twMerge as clsx, twMerge } from "tailwind-merge"
import { Marker } from "../common/Markers"
import { useSubtreeFocus } from "../common/SubtreeFocus"
import {
isActive$,
isCollapsed$,
PathsRoot,
setHovered,
toggleCollapsed,
} from "../common/paths.state"
Expand All @@ -19,6 +20,7 @@ const StructItem: React.FC<{
path: string[]
type?: string
}> = ({ name, children, path, type }) => {
const pathsRootId = useContext(PathsRoot)
const pathStr = path.join(".")
const isActive = useStateObservable(isActive$(pathStr))
const isExpanded = !useStateObservable(isCollapsed$(pathStr))
Expand All @@ -29,12 +31,14 @@ const StructItem: React.FC<{
"flex flex-col transition-all duration-300",
isActive && "bg-secondary/20",
)}
onMouseEnter={() => setHovered({ id: pathStr, hover: true })}
onMouseLeave={() => setHovered({ id: pathStr, hover: false })}
onMouseEnter={() => setHovered(pathsRootId, { id: pathStr, hover: true })}
onMouseLeave={() =>
setHovered(pathsRootId, { id: pathStr, hover: false })
}
>
<Marker id={path} />
<span
onClick={() => toggleCollapsed(pathStr)}
onClick={() => toggleCollapsed(pathsRootId, pathStr)}
className="cursor-pointer flex select-none items-center py-1 gap-1"
>
<ExpandBtn expanded={isExpanded} />
Expand Down
10 changes: 7 additions & 3 deletions src/codec-components/ViewCodec/CStruct.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Marker } from "../common/Markers"
import {
isActive$,
isCollapsed$,
PathsRoot,
setHovered,
toggleCollapsed,
} from "../common/paths.state"
Expand All @@ -25,6 +26,7 @@ const StructItem: React.FC<{
field: Var
value: unknown
}> = ({ name, children, path, field, value }) => {
const pathsRootId = useContext(PathsRoot)
const pathStr = path.join(".")
const isActive = useStateObservable(isActive$(pathStr))
const isExpanded = !useStateObservable(isCollapsed$(pathStr))
Expand All @@ -35,7 +37,7 @@ const StructItem: React.FC<{

const title = isComplexShape ? (
<span
onClick={() => toggleCollapsed(pathStr)}
onClick={() => toggleCollapsed(pathsRootId, pathStr)}
className="cursor-pointer flex items-center py-1 gap-1"
>
{hasParentTitle && <ItemMarker />}
Expand Down Expand Up @@ -68,8 +70,10 @@ const StructItem: React.FC<{
"flex flex-col transition-all duration-300",
isActive && "bg-secondary/80",
)}
onMouseEnter={() => setHovered({ id: pathStr, hover: true })}
onMouseLeave={() => setHovered({ id: pathStr, hover: false })}
onMouseEnter={() => setHovered(pathsRootId, { id: pathStr, hover: true })}
onMouseLeave={() =>
setHovered(pathsRootId, { id: pathStr, hover: false })
}
>
<ChildProvider titleElement={titleElement}>
<Marker id={path} />
Expand Down
12 changes: 8 additions & 4 deletions src/codec-components/common/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { ExpandBtn } from "@/components/Expand"
import { useStateObservable } from "@react-rxjs/core"
import { Dot, Trash2 } from "lucide-react"
import { ReactNode, useContext } from "react"
import { twMerge as clsx, twMerge } from "tailwind-merge"
import { Marker } from "./Markers"
import {
isActive$,
isCollapsed$,
PathsRoot,
setHovered,
toggleCollapsed,
} from "./paths.state"
import { ReactNode } from "react"

export const ListItem: React.FC<{
idx: number
Expand All @@ -19,6 +20,7 @@ export const ListItem: React.FC<{
actions?: ReactNode
inline?: boolean
}> = ({ idx, onDelete, children, path, actions, inline }) => {
const pathsRootId = useContext(PathsRoot)
const pathStr = path.join(".")
const isActive = useStateObservable(isActive$(pathStr))
const isCollapsed = useStateObservable(isCollapsed$(pathStr))
Expand Down Expand Up @@ -46,7 +48,7 @@ export const ListItem: React.FC<{
<Marker id={path} />
<span
className="cursor-pointer flex items-center py-1 gap-1"
onClick={() => toggleCollapsed(pathStr)}
onClick={() => toggleCollapsed(pathsRootId, pathStr)}
>
<ExpandBtn expanded={!isCollapsed} />
Item {idx + 1}.
Expand All @@ -66,8 +68,10 @@ export const ListItem: React.FC<{
return (
<li
className={twMerge("flex flex-col mb-1", isActive && "bg-secondary/80")}
onMouseEnter={() => setHovered({ id: pathStr, hover: true })}
onMouseLeave={() => setHovered({ id: pathStr, hover: false })}
onMouseEnter={() => setHovered(pathsRootId, { id: pathStr, hover: true })}
onMouseLeave={() =>
setHovered(pathsRootId, { id: pathStr, hover: false })
}
>
{title}
{inline ? null : (
Expand Down
62 changes: 36 additions & 26 deletions src/codec-components/common/paths.state.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
import { createSignal } from "@react-rxjs/utils"
import { state } from "@react-rxjs/core"
import { defer, map, scan } from "rxjs"
import { createContextState } from "@/lib/contextState"
import { createKeyedSignal } from "@react-rxjs/utils"
import { createContext, useContext } from "react"
import { map, scan } from "rxjs"

export const [collapsedToggle$, toggleCollapsed] = createSignal<string>()
export const PathsRoot = createContext<string>("")

const collapsedPaths$ = state(
defer(() =>
collapsedToggle$.pipe(
const pathsState = createContextState(() => useContext(PathsRoot))

export const [collapsedToggle$, toggleCollapsed] = createKeyedSignal<
string,
string
>()

const collapsedPaths$ = pathsState(
(id) =>
collapsedToggle$(id).pipe(
scan((acc, v) => {
if (acc.has(v)) acc.delete(v)
else acc.add(v)
return acc
}, new Set<string>()),
),
),
new Set(),
new Set<string>(),
)

export const isCollapsed$ = state(
(path: string) => collapsedPaths$.pipe(map((v) => v.has(path))),
export const isCollapsed$ = pathsState(
(path: string, id: string) =>
collapsedPaths$(id).pipe(map((v) => v.has(path))),
false,
)

/**
* Returns true if it's a collapsed root.
* Same as `isCollapsed`, but returns `false` if a parent path is also collapsed.
*/
export const isCollapsedRoot$ = state(
(path: string) =>
collapsedPaths$.pipe(
export const isCollapsedRoot$ = pathsState(
(path: string, id: string) =>
collapsedPaths$(id).pipe(
map((collapsedPaths) => {
if (!collapsedPaths.has(path)) return false

Expand All @@ -40,27 +48,29 @@ export const isCollapsedRoot$ = state(
false,
)

export const [hoverChange$, setHovered] = createSignal<{
id: string
hover: boolean
}>()
export const [hoverChange$, setHovered] = createKeyedSignal<
string,
{
id: string
hover: boolean
}
>()

const hoverPaths$ = state(
defer(() =>
hoverChange$.pipe(
const hoverPaths$ = pathsState(
(id: string) =>
hoverChange$(id).pipe(
scan((acc, v) => {
if (v.hover) acc.add(v.id)
else acc.delete(v.id)
return acc
}, new Set<string>()),
),
),
new Set(),
new Set<string>(),
)

export const isActive$ = state(
(path: string) =>
hoverPaths$.pipe(
export const isActive$ = pathsState(
(path: string, id: string) =>
hoverPaths$(id).pipe(
map((hoverPaths) => {
if (!hoverPaths.has(path)) return false

Expand Down
18 changes: 18 additions & 0 deletions src/lib/contextState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DefaultedStateObservable, state } from "@react-rxjs/core"
import { Observable } from "rxjs"

type LastOptional<T> = T extends [...infer R, any] ? T | R : T

export function createContextState<Ctx>(hook: () => Ctx) {
return function _state<A extends [Ctx, ...any[]], O>(
getObservable: (...args: A) => Observable<O>,
defaultValue: O | ((...args: A) => O),
): (...args: LastOptional<A>) => DefaultedStateObservable<O> {
const state$: (...args: any[]) => any = state(getObservable, defaultValue)

return (...args: any[]) =>
args.length < getObservable.length
? state$(...args, hook())
: state$(...args)
}
}
5 changes: 4 additions & 1 deletion src/pages/RuntimeCalls/RuntimeCallResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
runtimeCallResult$,
runtimeCallResultKeys$,
} from "./runtimeCalls.state"
import { PathsRoot } from "@/codec-components/common/paths.state"

export const RuntimeCallResults: FC = () => {
const keys = useStateObservable(runtimeCallResultKeys$)
Expand Down Expand Up @@ -63,7 +64,9 @@ const RuntimeCallResultBox: FC<{ subscription: string }> = ({
</button>
</div>
</div>
<ResultDisplay runtimeCallResult={runtimeCallResult} mode={mode} />
<PathsRoot.Provider value={subscription}>
<ResultDisplay runtimeCallResult={runtimeCallResult} mode={mode} />
</PathsRoot.Provider>
</li>
)
}
Expand Down
38 changes: 24 additions & 14 deletions src/pages/Storage/StorageSubscriptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
stringifyArg,
toggleSubscriptionPause,
} from "./storage.state"
import { PathsRoot } from "@/codec-components/common/paths.state"

export const StorageSubscriptions: FC = () => {
const keys = useStateObservable(storageSubscriptionKeys$)
Expand Down Expand Up @@ -81,28 +82,35 @@ const StorageSubscriptionBox: FC<{ subscription: string }> = ({
</button>
</div>
</div>
<ResultDisplay storageSubscription={storageSubscription} mode={mode} />
<ResultDisplay
storageSubscription={storageSubscription}
subscriptionKey={subscription}
mode={mode}
/>
</li>
)
}

const ResultDisplay: FC<{
storageSubscription: StorageSubscription
subscriptionKey: string
mode: "json" | "decoded"
}> = ({ storageSubscription, mode }) => {
}> = ({ storageSubscription, subscriptionKey, mode }) => {
if (!("result" in storageSubscription)) {
return <div className="text-sm text-foreground/50">Loading…</div>
}

if (storageSubscription.single) {
return (
<div className="max-h-[60svh] overflow-auto">
<ValueDisplay
mode={mode}
type={storageSubscription.type}
value={storageSubscription.result}
title={"Result"}
/>
<PathsRoot.Provider value={subscriptionKey}>
<ValueDisplay
mode={mode}
type={storageSubscription.type}
value={storageSubscription.result}
title={"Result"}
/>
</PathsRoot.Provider>
</div>
)
}
Expand All @@ -119,12 +127,14 @@ const ResultDisplay: FC<{
.join(", ")
return (
<div key={title} className={itemClasses}>
<ValueDisplay
mode={mode}
title={title}
value={value}
type={storageSubscription.type}
/>
<PathsRoot.Provider value={`${subscriptionKey}-${title}`}>
<ValueDisplay
mode={mode}
title={title}
value={value}
type={storageSubscription.type}
/>
</PathsRoot.Provider>
</div>
)
}
Expand Down

0 comments on commit 700c3bf

Please sign in to comment.