Skip to content

Commit

Permalink
fix: add horizontal scrollbar for syntax tree
Browse files Browse the repository at this point in the history
This is an ugly hack that uses JavaScript to calculate related widths.

I've opened an issue to request horizontal scrollbar for NTree:
tusen-ai/naive-ui#2875
  • Loading branch information
ouuan committed May 1, 2022
1 parent 4803a6a commit c649ebf
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 22 deletions.
6 changes: 3 additions & 3 deletions src/components/CodeAndTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<code-editor
ref="editor"
:code="code"
class="editor-tree-height"
class="editor-height"
read-only
/>
</n-gi>
Expand All @@ -19,7 +19,7 @@
:guess-root="guessRoot"
:correct-root="correctRoot"
:mark-range="editor?.markRange"
class="editor-tree-height"
:max-height="maxHeight"
/>
</n-gi>
</n-grid>
Expand Down Expand Up @@ -54,7 +54,7 @@ const guessRoot = computedAsync(async () => {
</script>

<style scoped>
.editor-tree-height {
.editor-height {
max-height: v-bind(maxHeight);
}
</style>
98 changes: 79 additions & 19 deletions src/components/SyntaxTree.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
<template>
<n-tree
:key="renderCount"
block-line
:data="rootTreeOption ? [rootTreeOption] : []"
:default-expanded-keys="defaultExpandedKeys"
:render-label="renderLabel"
:render-switcher-icon="renderSwitcherIcon"
:selectable="false"
virtual-scroll
/>
<div ref="rootRef">
<n-scrollbar
x-scrollable
@scroll="onScroll"
>
<div class="tree-outer-wrapper">
<div class="tree-inner-wrapper">
<n-tree
:key="renderCount"
block-line
:data="rootTreeOption ? [rootTreeOption] : []"
:default-expanded-keys="defaultExpandedKeys"
:render-label="renderLabel"
:render-switcher-icon="renderSwitcherIcon"
:selectable="false"
virtual-scroll
class="syntax-tree"
/>
</div>
</div>
</n-scrollbar>
</div>
</template>

<script setup lang="ts">
import {
computed,
h,
onMounted,
ref,
watch,
} from 'vue';
import {
NScrollbar,
NTree,
TreeOption,
} from 'naive-ui';
import { SyntaxNode } from 'web-tree-sitter';
import { useElementSize } from '@vueuse/core';
import SyntaxTreeNode from './SyntaxTreeNode.vue';
Expand All @@ -40,6 +55,7 @@ const props = defineProps<{
correctRoot: SyntaxNode | null,
markRange?: MarkRange,
globalRootTreeOption: boolean,
maxHeight: string,
}>();
const rootTreeOption = props.globalRootTreeOption ? globalRootTreeOption : ref<TreeOptionEx>();
Expand Down Expand Up @@ -119,23 +135,67 @@ function generateTreeOption(
};
}
const maxRenderedDepth = ref(0);
function renderLabel({ option }: {option: TreeOption}) {
if (!isTreeOptionEx(option)) throw new Error('Non-ex TreeOption passed to renderLabel');
maxRenderedDepth.value = Math.max(maxRenderedDepth.value, option.depth);
return h(SyntaxTreeNode, {
option,
markRange: props.markRange,
// eslint-disable-next-line no-param-reassign
onModified: (modification) => { option.modification = modification; },
});
}
const rootRef = ref<HTMLDivElement>();
const outerWrapperWidth = computed(() => {
if (!rootRef.value) return '500px';
const nodes = rootRef.value.getElementsByClassName('syntax-tree-node-button');
let maxWidth = 0;
for (const node of nodes) {
maxWidth = Math.max(maxWidth, node.getBoundingClientRect().width);
}
return `${Math.ceil(maxRenderedDepth.value * 16 + maxWidth + 30)}px`;
});
// to keep the vertical scrollbar visible
const rootWidth = useElementSize(rootRef, { width: 500, height: 1000 }).width;
const scrollbarPos = ref(0);
function onScroll({ target }: Event) {
if (target instanceof HTMLElement) {
scrollbarPos.value = target.scrollLeft;
}
}
const innerWrapperWidth = computed(() => {
const width = rootWidth.value + scrollbarPos.value;
return `${width}px`;
});
const renderCount = ref(0);
async function update() {
if (!props.correctRoot || !props.guessRoot) return;
maxRenderedDepth.value = 0;
scrollbarPos.value = 0;
defaultExpandedKeys.value = [];
rootTreeOption.value = generateTreeOption(props.guessRoot, 'correct', props.correctRoot, 0);
renderCount.value += 1;
}
onMounted(update);
watch(props, update);
</script>

function renderLabel({ option }: {option: TreeOption}) {
if (!isTreeOptionEx(option)) throw new Error('Non-ex TreeOption passed to renderLabel');
return h(SyntaxTreeNode, {
option,
markRange: props.markRange,
// eslint-disable-next-line no-param-reassign
onModified: (modification) => { option.modification = modification; },
});
<style scoped>
.tree-outer-wrapper {
min-width: v-bind(outerWrapperWidth);
}
</script>
.tree-inner-wrapper {
max-width: v-bind(innerWrapperWidth);
}
.syntax-tree {
max-height: v-bind(maxHeight);
margin-bottom: 8px;
}
</style>
1 change: 1 addition & 0 deletions src/components/SyntaxTreeDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
:global-root-tree-option="false"
:guess-root="guessRoot"
:correct-root="targetRoot"
max-height="60vh"
/>
</n-space>
</n-card>
Expand Down
1 change: 1 addition & 0 deletions src/components/SyntaxTreeNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
quaternary
size="small"
:type="labelType"
class="syntax-tree-node-button"
@click="showCode = true"
@mouseover="markNode"
@mouseleave="clearMark"
Expand Down

0 comments on commit c649ebf

Please sign in to comment.