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 a0a92cd
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 61 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
11 changes: 8 additions & 3 deletions src/codec-components/EditCodec/Tree/CEnum.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PathsRoot, setHovered } from "@/codec-components/common/paths.state"
import { Enum } from "@/components/Icons"
import { isComplex } from "@/utils/shape"
import { EditEnum, NOTIN } from "@polkadot-api/react-builder"
Expand All @@ -11,7 +12,6 @@ import {
TitleContext,
useReportBinaryStatus,
} from "./codec-components"
import { setHovered } from "@/codec-components/common/paths.state"

export const CEnum: EditEnum = ({
value,
Expand All @@ -23,6 +23,7 @@ export const CEnum: EditEnum = ({
onValueChanged,
decode,
}) => {
const pathId = useContext(PathsRoot)
const className =
"text-slate-500 hover:text-polkadot-500 cursor-pointer whitespace-nowrap [&:not(:first-child)]:ml-1"

Expand All @@ -48,8 +49,12 @@ export const CEnum: EditEnum = ({
<Portal node={titleElement}>
<span
onClick={() => scrollToMarker(innerPath)}
onMouseEnter={() => setHovered({ id: pathStr, hover: true })}
onMouseLeave={() => setHovered({ id: pathStr, hover: false })}
onMouseEnter={() =>
setHovered(pathId, { id: pathStr, hover: true })
}
onMouseLeave={() =>
setHovered(pathId, { id: pathStr, hover: false })
}
>
/ {value.type}
</span>
Expand Down
7 changes: 4 additions & 3 deletions src/codec-components/EditCodec/Tree/codec-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
useState,
} from "react"
import { twMerge } from "tailwind-merge"
import { isActive$, setHovered } from "../../common/paths.state"
import { isActive$, PathsRoot, setHovered } from "../../common/paths.state"

export const CVoid: EditVoid = () => null

Expand Down Expand Up @@ -137,6 +137,7 @@ export const ItemTitle: FC<
}) => {
const [binaryOpen, setBinaryOpen] = useState(false)
const isActive = useStateObservable(isActive$(path))
const pathId = useContext(PathsRoot)

return (
<>
Expand All @@ -147,8 +148,8 @@ export const ItemTitle: FC<
className,
)}
data-marker={`marker-${path}`}
onMouseEnter={() => setHovered({ id: path, hover: true })}
onMouseLeave={() => setHovered({ id: path, hover: false })}
onMouseEnter={() => setHovered(pathId, { id: path, hover: true })}
onMouseLeave={() => setHovered(pathId, { id: path, hover: false })}
>
<span
className={twMerge(
Expand Down
7 changes: 4 additions & 3 deletions src/codec-components/ViewCodec/CEnum.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useContext, useState } from "react"
import { Portal } from "react-portal"
import { twMerge } from "tailwind-merge"
import { Marker } from "../common/Markers"
import { isActive$, setHovered } from "../common/paths.state"
import { isActive$, PathsRoot, setHovered } from "../common/paths.state"
import { useSubtreeFocus } from "../common/SubtreeFocus"
import { useAppendTitle } from "../EditCodec/Tree/CEnum"
import { CopyBinary, useReportBinary } from "./CopyBinary"
Expand All @@ -17,6 +17,7 @@ export const CEnum: ViewEnum = ({ value, inner, path, encodedValue }) => {
const [newElement, setNewElement] = useState<HTMLElement | null>(null)
const pathStr = path.join(".")
const isActive = useStateObservable(isActive$(pathStr))
const pathId = useContext(PathsRoot)
useReportBinary(encodedValue)
const sub = focus.getNextPath(path)
if (sub) {
Expand All @@ -37,8 +38,8 @@ export const CEnum: ViewEnum = ({ value, inner, path, encodedValue }) => {
return (
<div
className={twMerge("flex flex-col")}
onMouseEnter={() => setHovered({ id: pathStr, hover: true })}
onMouseLeave={() => setHovered({ id: pathStr, hover: false })}
onMouseEnter={() => setHovered(pathId, { id: pathStr, hover: true })}
onMouseLeave={() => setHovered(pathId, { id: pathStr, hover: false })}
>
<Marker id={[...path, value.type]} />
<div className="flex gap-2 overflow-hidden justify-between">
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
Loading

0 comments on commit a0a92cd

Please sign in to comment.