Skip to content

Commit

Permalink
fix(client): tooltip animation
Browse files Browse the repository at this point in the history
  • Loading branch information
nonzzz committed May 28, 2024
1 parent cd9cf23 commit 35dc708
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 46 deletions.
62 changes: 41 additions & 21 deletions src/client/application.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,48 @@
import { useCallback, useRef, useState } from 'react'
import type { RefObject } from 'react'
import { ComposeContextProvider } from 'foxact/compose-context-provider'
import type { FoamDataObject } from '@carrotsearch/foamtree'
import { Tooltip } from './components/tooltip'
import { Text } from './components/text'
import { Spacer } from './components/spacer'
import { ApplicationProvider, TreemapProvider, useApplicationContext } from './context'
import { ApplicationProvider, TreemapProvider } from './context'
import { Sidebar, SidebarProvider } from './components/side-bar'
import { Treemap } from './components/treemap'
import type { TreemapInstance } from './components/treemap'
import type { PaintEvent, SquarifiedModule, TreemapInstance } from './components/treemap'
import { TreeMap } from './components/tree-map'
import { convertBytes } from './shared'

import { ModuleSize, TreeMap } from './components/tree-map'
interface ModuleSizeProps {
module: SquarifiedModule
}

function ModuleSize(props: ModuleSizeProps) {
const { module: { node } } = props
if (!node) return null

return (
<div stylex={{ display: 'inline-flex', whiteSpace: 'nowrap', width: '100%' }}>
<Text b p font="14px" mr={0.3}>Size:</Text>
<Text font="14px">{convertBytes(node.size)}</Text>
</div>
)
}

export function App() {
const treeMapRef = useRef<TreemapInstance>()
const [tooltipVisible, setTooltipVisible] = useState<boolean>(false)
const [tooltipContent, setTooltipContent] = useState<FoamDataObject | null>(null)
const [tooltipContent, setTooltipContent] = useState<SquarifiedModule | null>(Object.create(null))

const contexts = [
<ApplicationProvider key="app" />,
<TreemapProvider key="treemap" value={{ treemap: treeMapRef as RefObject<TreemapInstance> }} />
]

const handleGroupHover = useCallback((group: FoamDataObject | null) => {
setTooltipVisible(!!group)
setTooltipContent(() => group ? group.node : null)
const handleMousemove = useCallback((event: PaintEvent<MouseEvent>) => {
const { module } = event
setTooltipVisible(!!module)
setTooltipContent(() => module)
}, [])

const { sizes } = useApplicationContext()

return (
<ComposeContextProvider contexts={contexts}>
<div
Expand All @@ -41,20 +55,26 @@ export function App() {
<SidebarProvider>
<Sidebar onVisibleChange={(s) => setTooltipVisible(!s)} />
</SidebarProvider>
<Treemap ref={(instance: any) => treeMapRef.current = instance} onGroupHover={handleGroupHover} />
{/* <TreeMap ref={(instance: any) => treeMapRef.current = instance} onGroupHover={handleGroupHover} /> */}
<Treemap ref={(instance: any) => treeMapRef.current = instance} onMousemove={handleMousemove} />
{/* <TreeMap ref={(instance: any) => treeMapRef.current = instance} /> */}
<Tooltip visible={tooltipVisible}>
{tooltipContent && (
{tooltipContent?.node && (
<>
<Text p b font="14px">{tooltipContent.label}</Text>
<div stylex={{ display: 'inline-flex', whiteSpace: 'nowrap', width: '100%' }}>
<Text b font="14px" mr={0.3}>Id:</Text>
<Text font="14px">
{tooltipContent.node.label}
</Text>
</div>
<Spacer h={0.5} />
<ModuleSize module={tooltipContent} />
<Spacer h={0.5} />
<ModuleSize module={tooltipContent} sizes="statSize" checkedSizes={sizes} />
<ModuleSize module={tooltipContent} sizes="parsedSize" checkedSizes={sizes} />
<ModuleSize module={tooltipContent} sizes="gzipSize" checkedSizes={sizes} />
<Text p font="12px">
path:
{tooltipContent.filename}
</Text>
<div stylex={{ display: 'inline-flex', whiteSpace: 'nowrap', width: '100%' }}>
<Text b font="14px" mr={0.3}>Path:</Text>
<Text font="14px">
{tooltipContent.node.filename}
</Text>
</div>
</>
)}
</Tooltip>
Expand Down
2 changes: 1 addition & 1 deletion src/client/components/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function Tooltip(props: TooltipProps) {
opacity: 0.9,
whiteSpace: 'nowrap',
visibility: 'visible',
transition: 'opacity .2s ease, visibility .2s ease',
...(visible && { transition: 'opacity .2s ease, visibility .2s ease' }),
...(!visible && { opacity: 0, visibility: 'hidden' })
}}
>
Expand Down
29 changes: 19 additions & 10 deletions src/client/components/treemap/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { ForwardedRef } from 'react'
import { noop } from 'foxact/noop'
import { useApplicationContext } from '../../context'
import type { Sizes } from '../../interface'
import { createTreemap } from './treemap'
import type { Module, SquarifiedModule } from './interface'
import { PaintEvent, createTreemap } from './treemap'
import type { Module } from './interface'
import { sortChildrenBySize } from './shared'

function handleModule(data: Module, size: Sizes) {
Expand All @@ -15,28 +15,28 @@ function handleModule(data: Module, size: Sizes) {
}

interface TreemapProps {
onGroupHover?: (group: SquarifiedModule | null) => void
onMousemove?: (event: PaintEvent<MouseEvent>) => void
}

export type TreemapInstance = ReturnType<typeof createTreemap>

export const Treemap = forwardRef((props: TreemapProps, ref: ForwardedRef<TreemapInstance>) => {
const { onGroupHover = noop } = props
const { onMousemove = noop } = props
const treemapInstance = useRef<TreemapInstance | null>(null)
const containerRef = useRef<HTMLDivElement>(null)
const { analyzeModule, sizes, scence } = useApplicationContext()

useImperativeHandle(ref, () => treemapInstance.current!)

const handleGroupHover = useCallback((group: SquarifiedModule | null) => {
onGroupHover(group)
}, [onGroupHover])
const handleMousemove = useCallback((event: PaintEvent<MouseEvent>) => {
onMousemove(event)
}, [onMousemove])

const visibleChunks = useMemo(() => {
return analyzeModule.filter(m => scence.has(m.label))
.map(m => {
m.groups = sizes === 'statSize' ? m.stats : m.source
return handleModule(m, sizes)
return handleModule(m as Module, sizes)
})
}, [analyzeModule, sizes, scence])

Expand All @@ -53,7 +53,16 @@ export const Treemap = forwardRef((props: TreemapProps, ref: ForwardedRef<Treema
treemapInstance.current = treemap
treemapInstance.current.mount(containerRef.current!)
treemapInstance.current.setup({
onGroupHover: handleGroupHover
onMousemove: handleMousemove,
onClick: function onClick(event) {
event.nativeEvent.preventDefault()
if (!event.module) return
handleMousemove({ ...event, module: null })
this.zoom(event.module)
},
onMouseWheel: function onMouseWheel() {
//
}
})
window.addEventListener('resize', resize)
}
Expand All @@ -63,7 +72,7 @@ export const Treemap = forwardRef((props: TreemapProps, ref: ForwardedRef<Treema
treemapInstance.current?.dispose()
treemapInstance.current = null
}
}, [visibleChunks, handleGroupHover])
}, [visibleChunks, handleMousemove])

return <div ref={containerRef} stylex={{ height: '100%', width: '100%', position: 'relative' }} />
})
2 changes: 2 additions & 0 deletions src/client/components/treemap/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './component'
export type { SquarifiedModule } from './interface'
export type { PaintEvent } from './treemap'
3 changes: 2 additions & 1 deletion src/client/components/treemap/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { Module as _Module } from '../../interface'
export interface Module extends _Module {
layout?: [number, number, number, number]
groups: Module[]
size: number
[prop: string]: any
}

export interface SquarifiedModule {
node: Omit<_Module, 'groups'>
node: Omit<Module, 'groups'>
layout: [number, number, number, number]
children: SquarifiedModule[]
}
3 changes: 2 additions & 1 deletion src/client/components/treemap/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export const STYLES = {
HEAD_HEIGHT: 20,
INSET_X: 10,
INSET_Y: 20 + 5,
DOT_CHAR_CODE: 46
DOT_CHAR_CODE: 46,
ANIMATION_DURATION: 300
}

export function charCodeWidth(c: CanvasRenderingContext2D, ch: number) {
Expand Down
42 changes: 30 additions & 12 deletions src/client/components/treemap/treemap.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-use-before-define */
// According the bundle analyzed size. I deiced to implement the treemap component to replace Moduletree component.
// Alough the Moduletree component is more powerful and has more features, but it's too large to be used in the project.
// The treemap component is a simple component that can be used to display the data in a treemap format.
Expand All @@ -14,8 +15,15 @@ interface Shape {
height: number
}

export interface PaintEvent<T> {
nativeEvent: T
module: SquarifiedModule | null
}

export interface PaintOptions {
onGroupHover?: (group: SquarifiedModule | null) => void
onMousemove?: (this: Paint, event: PaintEvent<MouseEvent>) => void
onClick?: (this: Paint, event: PaintEvent<MouseEvent>) => void
onMouseWheel?: (this: Paint, event: PaintEvent<WheelEvent>) => void
}

const defaultShape = { width: 0, height: 0 }
Expand Down Expand Up @@ -64,11 +72,12 @@ export class Paint {
if (h > STYLES.HEAD_HEIGHT) {
this.context.fillStyle = '#000'
const maxWidth = w - STYLES.INSET_X
const textY = y + Math.round(STYLES.INSET_Y / 2)
const textY = y + Math.round(STYLES.INSET_Y / 2) + 2
const ellipsisWidth = 3 * charCodeWidth(this.context, STYLES.DOT_CHAR_CODE)
const [text, width] = textOverflowEllipsis(this.context, node.node.label, maxWidth, ellipsisWidth)
const textX = x + Math.round((w - width) / 2)
if (text) {
this.context.font = '14px sans-serif'
this.context.globalAlpha = 0.5
if (node.children.length) {
this.context.fillText(text, textX, textY)
Expand All @@ -95,18 +104,15 @@ export class Paint {
}
}

private mouseMoveHandler(e: MouseEvent) {
private eventHandler(e: MouseEvent | WheelEvent, handler: (event: PaintEvent<MouseEvent>) => void) {
const layout = findRelativeNode(this.canvas, e, this.layoutNodes)
if (layout) {
this.canvas.style.cursor = 'pointer'
if (this.options.onGroupHover) {
this.options.onGroupHover(layout)
}
const event = {
nativeEvent: e,
module: layout
}
handler(event)
}

private mouseOutHandler(e: MouseEvent) {}

private updateColorMapping() {
const colorMapping: Record<string, string> = {}
const defaultSweepAngle = Math.PI * 2
Expand Down Expand Up @@ -136,6 +142,10 @@ export class Paint {
this.colorMapping = colorMapping
}

zoom(node: SquarifiedModule) {
//
}

dispose() {
if (!this.mainEl) return
this.mainEl.removeChild(this.canvas)
Expand Down Expand Up @@ -175,8 +185,16 @@ export class Paint {
this.updateColorMapping()
this.resize()
element.appendChild(this.canvas)
this.canvas.onmousemove = (e) => this.mouseMoveHandler(e)
this.canvas.onmouseout = (e) => this.mouseOutHandler(e)
this.canvas.onmousemove = (e) =>
this.eventHandler(e, (evt) => {
this.canvas.style.cursor = 'pointer'
this.options.onMousemove?.call(this, evt)
})
this.canvas.onclick = (e) =>
this.eventHandler(e, (evt) => {
this.canvas.style.cursor = 'default'
this.options.onClick?.call(this, evt)
})
}

setup(options: PaintOptions) {
Expand Down

0 comments on commit 35dc708

Please sign in to comment.