Skip to content

Commit

Permalink
[studio] feature: New ast tree (codemod-com#1089)
Browse files Browse the repository at this point in the history
[studio] feature: New ast tree
  • Loading branch information
grzim authored Jul 17, 2024
1 parent 2d3dc24 commit b35cdae
Show file tree
Hide file tree
Showing 16 changed files with 382 additions and 294 deletions.
16 changes: 0 additions & 16 deletions apps/frontend/app/(website)/studio/components.json

This file was deleted.

15 changes: 8 additions & 7 deletions apps/frontend/app/(website)/studio/main/5PaneSetup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { KnownEngines } from "@codemod-com/utilities";
import { useTheme } from "@context/useTheme";
import { getCodeDiff } from "@studio/api/getCodeDiff";
import Panel from "@studio/components/Panel";
import { BoundResizePanel } from "@studio/components/ResizePanel/BoundResizePanel";
import ResizeHandle from "@studio/components/ResizePanel/ResizeHandler";
import InsertExampleButton from "@studio/components/button/InsertExampleButton";
import {
Expand All @@ -13,6 +14,7 @@ import {
SelectTrigger,
SelectValue,
} from "@studio/components/ui/select";
import { VisibilityIcon } from "@studio/icons";
import { TestTabsComponent } from "@studio/main/PageBottomPane/TestTabsComponent";
import { AssistantTab } from "@studio/main/PaneLayout";
import { LoginWarningModal } from "@studio/main/PaneLayout/LoginWarningModal";
Expand All @@ -25,11 +27,9 @@ import Codemod from "./Codemod";
import { Header } from "./Header/Header";
import Layout from "./Layout";
import {
BoundResizePanel,
CodeSnippets,
type PanelsRefs,
ResizablePanelsIndices,
ShowPanelTile,
} from "./PageBottomPane";
import { useSnippetsPanels } from "./PageBottomPane/hooks";

Expand Down Expand Up @@ -133,24 +133,25 @@ const Main = () => {
const beforeAfterBottomPanels = (
<>
<CodeSnippets
key="before-and-after-panels"
className="before-and-after-panels"
codeDiff={codeDiff}
onlyAfterHidden={onlyAfterHidden}
panelRefs={panelRefs}
panels={[beforePanel, afterPanel]}
>
{onlyAfterHidden && (
<ShowPanelTile
header="After"
panel={afterPanel}
<div
className="hidden_panel_indicator"
onClick={() => {
afterPanel.visibilityOptions?.show();
panelRefs.current[ResizablePanelsIndices.AFTER_SNIPPET]?.resize(
50,
);
}}
/>
>
<VisibilityIcon visibilityOptions={afterPanel.visibilityOptions} />
<span className="hidden_panel_indicator_text">After</span>
</div>
)}
</CodeSnippets>
</>
Expand Down
73 changes: 0 additions & 73 deletions apps/frontend/app/(website)/studio/main/ASTViewer.tsx

This file was deleted.

144 changes: 144 additions & 0 deletions apps/frontend/app/(website)/studio/main/ASTViewer/AboristViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { cn } from "@/utils";
import type { Node } from "@babel/types";
import Text from "@studio/components/Text";
import { useScrollNodeIntoView } from "@studio/main/ASTViewer/useScrollNodeIntoView";
import {
useSelectFirstTreeNodeForSnippet,
useSnippetsStore,
} from "@studio/store/snippets";
import { useRangesOnTarget } from "@studio/store/utils/useRangesOnTarget";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { type NodeRendererProps, Tree } from "react-arborist";
import { flushSync } from "react-dom";
import useResizeObserver from "use-resize-observer";

type TreeNode = {
id: string;
actualNode: Node;
label: string;
children?: TreeNode[];
start: number;
end: number;
};

type EditorType = "before" | "after" | "output";

interface Props {
type: EditorType;
}

const transformTreeData = (node: TreeNode): TreeNode => ({
...node,
children: node.children?.length
? node.children.map(transformTreeData)
: undefined,
});

export const ASTViewer: React.FC<Props> = ({ type }) => {
const ASTTreeRef = useRef<HTMLDivElement>(null);
const { width = 0, height = 0 } = useResizeObserver({ ref: ASTTreeRef });
const getFirstTreeNode = useSelectFirstTreeNodeForSnippet();
const [firstNode, setFirstNode] = useState<TreeNode | null>(null);
const { getSelectedEditors } = useSnippetsStore();
const {
[type]: { rootNode },
} = getSelectedEditors();

const setRangesOnTarget = useRangesOnTarget();
const scrollNodeIntoView = useScrollNodeIntoView(type);

const handleNodeClick = useCallback(
(node: TreeNode = rootNode) => {
scrollNodeIntoView(node, ASTTreeRef);

flushSync(() => {
setFirstNode(node);
setRangesOnTarget({
target: `${type.toUpperCase()}_INPUT`,
ranges: [node],
});
const setRange = getSelectedEditors().setSelection(type);
setRange({
kind: "FIND_CLOSEST_PARENT",
ranges: [node],
});
});
},
[rootNode, scrollNodeIntoView, setRangesOnTarget, type, getSelectedEditors],
);

useEffect(() => {
const firstTreeNode = getFirstTreeNode(type);
if (firstTreeNode) {
scrollNodeIntoView(firstTreeNode, ASTTreeRef);
setFirstNode(firstTreeNode);
}
}, [scrollNodeIntoView, getFirstTreeNode, type]);

const NodeComponent = React.memo<NodeRendererProps<TreeNode>>(
({ node, style, dragHandle }) => {
const isSelected = firstNode?.id === node.data.id;
return (
<div style={style} ref={dragHandle}>
<Text
className="cursor-pointer whitespace-nowrap"
color={isSelected ? "text-cyan-500" : undefined}
>
{!node.isLeaf && (
<strong
onClick={() => node.toggle()}
className={cn(
node.isOpen ? "text-red-600 " : "text-green-500",
)}
>
{node.isOpen ? "- " : "+ "}
</strong>
)}
<span
onClick={(e) => {
e.stopPropagation();
handleNodeClick(node.data);
}}
className="inline-block"
id={`${node.data.id}`}
>
{node.data.label}
</span>
</Text>
</div>
);
},
);

NodeComponent.displayName = "NodeComponent";

if (!rootNode) {
return (
<div className="flex h-full flex-col w-full overflow-hidden p-2">
<Text>
Please provide a snippet to render an Abstract Syntax Tree for it.
</Text>
</div>
);
}

const transformedRootNode = transformTreeData(rootNode);

return (
<div
className="flex h-full flex-col w-full overflow-hidden p-2"
ref={ASTTreeRef}
>
<Tree
data={transformedRootNode.children || []}
openByDefault={true}
width={width}
height={height}
indent={12}
rowHeight={22}
>
{NodeComponent}
</Tree>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { BoundResizePanel } from "@studio/components/ResizePanel/BoundResizePanel";
import ResizeHandle from "@studio/components/ResizePanel/ResizeHandler";
import { ASTViewer } from "@studio/main/ASTViewer/AboristViewer";
import type {
PanelData,
PanelsRefs,
} from "@studio/main/PageBottomPane/utils/types";
import { isVisible } from "@studio/utils/visibility";
import React, { memo } from "react";

const AstSectionBase = ({
panels,
panelRefs,
}: {
panels: PanelData[];
panelRefs: PanelsRefs;
}) => {
return panels.filter(isVisible).map((panel, i, { length }) => (
<React.Fragment key={panel.relatedAST}>
<BoundResizePanel
className="h-full"
panelRefs={panelRefs}
key={panel.relatedAST}
defaultSize={100 / panels.length}
panelRefIndex={panel.relatedAST}
boundedIndex={
panel.boundIndex === panel.relatedAST ? panel.snippedIndex : undefined
}
{...panel}
>
<ASTViewer type={panel.type} />
</BoundResizePanel>
{i !== length - 1 && isVisible(panels[i + 1]) && (
<ResizeHandle key={`resize-handle-${i}`} direction="horizontal" />
)}
</React.Fragment>
));
};

export const AstSection = memo(AstSectionBase);
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { TreeNode } from "@studio/components/Tree";
import type React from "react";
import { useState } from "react";

const useScrollNodeIntoView = () => {
export const useScrollNodeIntoView = () => {
const [selectedElem, setSelectedElem] = useState();
const scrollIntoView = async (
node: TreeNode | null,
treeRef: React.RefObject<HTMLDivElement | null>,
Expand All @@ -11,15 +13,19 @@ const useScrollNodeIntoView = () => {
}
// delay to make the animation smoother
await delay(200);
const foundElem = document.getElementById(
`${node.label}-${node.start}-${node.end}`,
const foundElem = document.getElementById(`${node.id}`);

console.log(
node.id,
{
foundElem,
},
treeRef,
treeRef.current,
);

if (foundElem && treeRef.current) {
treeRef.current.scrollTo({
top: foundElem.offsetTop - treeRef.current.offsetTop,
behavior: "smooth",
});
foundElem.scrollIntoView({ behavior: "smooth", block: "center" });
}
};
return scrollIntoView;
Expand All @@ -29,5 +35,3 @@ const delay = async (ms: number) =>
new Promise((resolve) => {
setTimeout(resolve, ms);
});

export default useScrollNodeIntoView;
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from "./Snippets/CodeSnippets";
export * from "./WarningTexts";
export * from "./SnippedHeader";
export * from "./side-components";
Loading

0 comments on commit b35cdae

Please sign in to comment.