Skip to content
This repository has been archived by the owner on Nov 16, 2024. It is now read-only.

Commit

Permalink
feat: complete the foldable function
Browse files Browse the repository at this point in the history
  • Loading branch information
aiktb committed Nov 1, 2024
1 parent a6abca4 commit 03f4a00
Show file tree
Hide file tree
Showing 16 changed files with 131 additions and 384 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
## Features

- [x] Highlight the results of Vue SFC compiler parse.
- [ ] Display Vue AST nodes in a tree view.
- [x] Display Vue AST nodes in a tree view.

## Contribution

Expand Down
36 changes: 30 additions & 6 deletions app/assets/example.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
</script>

<template>
<div>
<span class="count">{{ count }}</span>
Expand All @@ -12,12 +6,42 @@ const count = ref(0);
<template>Fragment</template>
<Transition>
<button v-if="count > 0" @click="count--">Subtract</button>
<button v-else>Add</button>
</Transition>
<!-- Nesting -->
<div>
<div>
<div>Hello World!</div>
</div>
</div>
<component is="div">
TESTING
</component>
</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
</script>

<script lang="ts">
export default {
name: "Example",
};
</script>

<style scoped>
.count {
color: green;
}
</style>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
</style>
58 changes: 0 additions & 58 deletions app/components/ast-json-preview.tsx

This file was deleted.

80 changes: 57 additions & 23 deletions app/components/collapsible-tree-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,22 @@ const CollapsibleTreeView = ({ ast }: CollapsibleTreeViewProps) => {
{ast.descriptor.template && <TemplateTreeRootNode instance={ast.descriptor.template} />}
{ast.descriptor.scriptSetup && (
<CollapsibleButton node={ast.descriptor.scriptSetup}>
{ast.descriptor.scriptSetup.type}
<div className="i-radix-icons-dot-filled size-6 text-black dark:text-white" />
<div className="uppercase">{ast.descriptor.scriptSetup.type}</div>
<div className="ml-1.5 text-blue-500 group-data-[selected=true]:text-blue-200">setup</div>
</CollapsibleButton>
)}
{ast.descriptor.script && (
<CollapsibleButton node={ast.descriptor.script}>
{ast.descriptor.script.type}
<div className="i-radix-icons-dot-filled size-6 text-black dark:text-white" />
<div className="uppercase">{ast.descriptor.script.type}</div>
</CollapsibleButton>
)}
{ast.descriptor.styles.map((style) => (
<CollapsibleButton node={style} key={style.loc.start.offset}>
{style.type}
<div className="i-radix-icons-dot-filled size-6 text-black dark:text-white" />

<div className="uppercase">{style.type}</div>
</CollapsibleButton>
))}
</div>
Expand All @@ -35,17 +40,20 @@ export default CollapsibleTreeView;
interface CollapsibleButtonProps {
children: React.ReactNode;
node: Node | SFCBlock;
depth?: number;
}

const CollapsibleButton = ({ children, node }: CollapsibleButtonProps) => {
const CollapsibleButton = ({ children, node, depth = 1 }: CollapsibleButtonProps) => {
const { updateSelectedNode, node: selectedNode } = useSelectedNodeStore();
return (
<button
type="button"
onClick={() => updateSelectedNode(node)}
className={cn("flex w-full items-start uppercase", {
"text-blue-500": node === selectedNode,
})}
style={{
paddingLeft: `${depth * 1}rem`,
}}
data-selected={node === selectedNode}
className="group mb-1.5 flex w-full items-center rounded text-green-500 transition hover:bg-green-500 hover:text-white data-[selected=true]:bg-green-500 data-[selected=true]:text-white"
>
{children}
</button>
Expand All @@ -57,39 +65,65 @@ interface TemplateTreeRootNodeProps {
}

const TemplateTreeRootNode = ({ instance }: TemplateTreeRootNodeProps) => {
const [collapsed, setCollapsed] = useState(true);
return (
<div>
<CollapsibleButton node={instance}>{instance.type}</CollapsibleButton>
<div>
{instance.ast?.children.map((child) => (
<TemplateTreeNode instance={child} key={child.loc.start.offset} />
))}
</div>
<CollapsibleButton node={instance}>
<button
type="button"
className="i-radix-icons-triangle-right size-6 text-black dark:text-white"
onClick={() => setCollapsed((prev) => !prev)}
/>
<div className="uppercase">{instance.type}</div>
</CollapsibleButton>
{collapsed && (
<div>
{instance.ast?.children.map((child) => (
<TemplateTreeNode instance={child} key={child.loc.start.offset} />
))}
</div>
)}
</div>
);
};

interface TemplateTreeNodeProps {
instance: TemplateChildNode;
depth?: number;
}

const TemplateTreeNode = ({ instance }: TemplateTreeNodeProps) => {
const TemplateTreeNode = ({ instance, depth = 2 }: TemplateTreeNodeProps) => {
// COMPOUND_EXPRESSION is a node that is only generated when the compiler optimizes.
// Impossible to appear in the use case. It's excluded here just to eliminate TSC warning.
const hasChildren = instance.type !== NodeTypes.COMPOUND_EXPRESSION && "children" in instance;
const [collapsed, setCollapsed] = useState(depth <= 3);

return (
<div>
<div className="ml-3">
<CollapsibleButton node={instance}>{NodeTypes[instance.type]}</CollapsibleButton>
{hasChildren && (
<div>
{instance.children.map((child) => (
<TemplateTreeNode instance={child} key={child.loc.start.offset} />
))}
</div>
<CollapsibleButton node={instance} depth={depth}>
{hasChildren ? (
<button
type="button"
className={cn("i-radix-icons-triangle-right size-6 text-black dark:text-white", {
"rotate-90": !collapsed,
})}
onClick={() => setCollapsed((prev) => !prev)}
/>
) : (
<div className="i-radix-icons-dot-filled size-6 text-black dark:text-white" />
)}
</div>
<div>{NodeTypes[instance.type]}</div>
{instance.type === NodeTypes.ELEMENT && (
<div className="ml-1.5 text-purple-400 group-data-[selected=true]:text-purple-200">{`<${instance.tag}>`}</div>
)}
</CollapsibleButton>
{collapsed && hasChildren && (
<div>
{instance.children.map((child) => (
<TemplateTreeNode instance={child} key={child.loc.start.offset} depth={depth + 1} />
))}
</div>
)}
</div>
);
};
53 changes: 0 additions & 53 deletions app/components/global-options.tsx

This file was deleted.

3 changes: 0 additions & 3 deletions app/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { version } from "@vue/compiler-sfc";
import Logo from "~/assets/logo.svg?react";

import { repository } from "@/package.json";

import GlobalOptions from "~/components/global-options";
import ThemesSwitcher from "~/components/themes-switcher";
import { cn } from "~/lib/utils";

Expand Down Expand Up @@ -45,7 +43,6 @@ const Header = ({ className }: { className?: string }) => {
<span className="sr-only">GitHub</span>
</Link>
<ThemesSwitcher />
<GlobalOptions />
</div>
</header>
);
Expand Down
32 changes: 29 additions & 3 deletions app/components/selected-node-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,37 @@ const SelectedNodeView = () => {
},
}).then(setHighlightedAstJson);
return (
<div className="h-full overflow-scroll">
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki has escaped html. */}
<div dangerouslySetInnerHTML={{ __html: highlightedAstJson }} />
<div className="relative h-full">
<div className="h-full overflow-scroll">
<CopyTextButton text={jsonCode} className="absolute top-0 right-0" />
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki has escaped html. */}
<div dangerouslySetInnerHTML={{ __html: highlightedAstJson }} />
</div>
</div>
);
};

export default SelectedNodeView;

const CopyTextButton = ({ text, className }: { text: string; className: string }) => {
const [copied, setCopied] = useState(false);
return (
<Button
size="icon"
variant="outline"
className={className}
onClick={() => {
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
}}
>
<span className="sr-only">Copy JSON code</span>
{copied ? (
<span className="i-lucide-check-check size-4 animate-pulse" />
) : (
<span className="i-radix-icons-copy size-4" />
)}
</Button>
);
};
24 changes: 0 additions & 24 deletions app/components/ui/input.tsx

This file was deleted.

Loading

0 comments on commit 03f4a00

Please sign in to comment.