Skip to content
Draft
10 changes: 10 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@
"noUnusedVariables": {
"level": "warn",
"fix": "unsafe"
},
"useExhaustiveDependencies": {
"level": "error",
"options": {
"hooks": [
{ "name": "useLazyRef", "stableResult": true },
{ "name": "useSignal", "stableResult": true },
{ "name": "useComputed", "stableResult": true }
]
}
}
},
"suspicious": {
Expand Down
21 changes: 12 additions & 9 deletions packages/scan/src/web/components/copy-to-clipboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useSignal, useSignalEffect } from '@preact/signals';
import { memo } from 'preact/compat';
import { useCallback, useEffect, useState } from 'preact/hooks';
import { useCallback } from 'preact/hooks';
import { cn } from '~web/utils/helpers';
import { Icon } from '../icon';

Expand All @@ -22,16 +23,18 @@ export const CopyToClipboard = memo(
className,
iconSize = 14,
}: CopyToClipboardProps): JSX.Element => {
const [isCopied, setIsCopied] = useState(false);
const isCopied = useSignal(false);

useEffect(() => {
if (isCopied) {
const timeout = setTimeout(() => setIsCopied(false), 600);
useSignalEffect(() => {
if (isCopied.value) {
const timeout = setTimeout(() => {
isCopied.value = false;
}, 600);
return () => {
clearTimeout(timeout);
};
}
}, [isCopied]);
});

const copyToClipboard = useCallback(
(e: MouseEvent) => {
Expand All @@ -40,7 +43,7 @@ export const CopyToClipboard = memo(

navigator.clipboard.writeText(text).then(
() => {
setIsCopied(true);
isCopied.value = true;
onCopy?.(true, text);
},
() => {
Expand All @@ -66,9 +69,9 @@ export const CopyToClipboard = memo(
)}
>
<Icon
name={`icon-${isCopied ? 'check' : 'copy'}`}
name={`icon-${isCopied.value ? 'check' : 'copy'}`}
size={[iconSize]}
className={cn(isCopied && 'text-green-500')}
className={cn(isCopied.value && 'text-green-500')}
/>
</button>
);
Expand Down
21 changes: 13 additions & 8 deletions packages/scan/src/web/hooks/use-merged-refs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Ref, RefCallback } from 'preact';
import { type MutableRefObject, useCallback } from 'preact/compat';
import type { MutableRefObject } from 'preact/compat';
import { useMemo } from 'react';

type PossibleRef<T> = Ref<T> | undefined;

Expand All @@ -11,16 +12,20 @@ const assignRef = <T>(ref: PossibleRef<T>, value: T) => {
}
};

const mergeRefs = <T>(...refs: PossibleRef<T>[]) => {
return (node: T) => {
for (const ref of refs) {
function assignRefs<T>(this: PossibleRef<T>[], node: T | null) {
if (node) {
for (const ref of this) {
if (ref) {
assignRef(ref, node);
}
}
};
};
}
}

function mergeRefs<T>(this: PossibleRef<T>[]): RefCallback<T> {
return (assignRefs<T>).bind(this);
}

export const useMergedRefs = <T>(...refs: PossibleRef<T>[]) => {
return useCallback(mergeRefs(...refs), [...refs]) as RefCallback<T>;
export const useMergedRefs = <T>(...refs: PossibleRef<T>[]): RefCallback<T> => {
return useMemo((mergeRefs<T>).bind(refs), refs);
};
16 changes: 7 additions & 9 deletions packages/scan/src/web/views/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,13 @@ interface ContentViewProps {
const ContentView = ({ isOpen, children }: ContentViewProps) => {
return (
<div
className={useComputed(() =>
cn(
'flex-1',
'opacity-0',
'overflow-y-auto overflow-x-hidden',
'transition-opacity delay-0',
'pointer-events-none',
isOpen.value && 'opacity-100 delay-150 pointer-events-auto',
),
className={cn(
'flex-1',
'opacity-0',
'overflow-y-auto overflow-x-hidden',
'transition-opacity delay-0',
'pointer-events-none',
isOpen.value && 'opacity-100 delay-150 pointer-events-auto',
)}
>
<div className="absolute inset-0 flex">{children}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
saveLocalStorage,
} from '~web/utils/helpers';
import { getFiberPath } from '~web/utils/pin';
import { createSet } from '../factories';
import { inspectorUpdateSignal } from '../states';
import {
type InspectableElement,
Expand Down Expand Up @@ -396,7 +397,7 @@ export const ComponentsTree = () => {
const refResizeHandle = useRef<HTMLDivElement>(null);

const [flattenedNodes, setFlattenedNodes] = useState<FlattenedNode[]>([]);
const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set());
const [collapsedNodes, setCollapsedNodes] = useState(createSet<string>);
const [selectedIndex, setSelectedIndex] = useState<number | undefined>(
undefined,
);
Expand Down
29 changes: 15 additions & 14 deletions packages/scan/src/web/views/inspector/diff-value.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@ const TreeNode = ({
isNegative: boolean;
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const canExpand = value !== null &&
typeof value === 'object' &&
!(value instanceof Date);
const canExpand =
value !== null && typeof value === 'object' && !(value instanceof Date);

if (!canExpand) {
return (
Expand Down Expand Up @@ -81,7 +80,9 @@ const TreeNode = ({
<span className="text-gray-500">{path}:</span>
{!isExpanded && (
<span className="truncate">
{value instanceof Date ? formatValuePreview(value) : `{${Object.keys(value).join(', ')}}`}
{value instanceof Date
? formatValuePreview(value)
: `{${Object.keys(value).join(', ')}}`}
</span>
)}
</div>
Expand Down Expand Up @@ -180,16 +181,16 @@ export const DiffValueView = ({
{!expanded ? (
<span>{formatValuePreview(safeValue)}</span>
) : (
<div className="pl-2 border-l border-[#333] mt-0.5 ml-1 flex flex-col gap-0.5">
{Object.entries(safeValue as object).map(([key, val]) => (
<TreeNode
key={key}
value={val}
path={key}
isNegative={isNegative}
/>
))}
</div>
<div className="pl-2 border-l border-[#333] mt-0.5 ml-1 flex flex-col gap-0.5">
{Object.entries(safeValue as object).map(([key, val]) => (
<TreeNode
key={key}
value={val}
path={key}
isNegative={isNegative}
/>
))}
</div>
)}
</div>
<CopyToClipboard
Expand Down
7 changes: 7 additions & 0 deletions packages/scan/src/web/views/inspector/factories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function createSet<T>(): Set<T> {
return new Set<T>();
}

export function createMap<K, V>(): Map<K, V> {
return new Map<K, V>();
}
73 changes: 37 additions & 36 deletions packages/scan/src/web/views/inspector/overlay/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,43 @@ export const OVERLAY_DPR =

export const currentLockIconRect: LockIconRect | null = null;


const drawLockIcon = (
ctx: CanvasRenderingContext2D,
x: number,
y: number,
size: number,
) => {
ctx.save();
ctx.strokeStyle = 'white';
ctx.fillStyle = 'white';
ctx.lineWidth = 1.5;

const shackleWidth = size * 0.6;
const shackleHeight = size * 0.5;
const shackleX = x + (size - shackleWidth) / 2;
const shackleY = y;

ctx.beginPath();
ctx.arc(
shackleX + shackleWidth / 2,
shackleY + shackleHeight / 2,
shackleWidth / 2,
Math.PI,
0,
false,
);
ctx.stroke();

const bodyWidth = size * 0.8;
const bodyHeight = size * 0.5;
const bodyX = x + (size - bodyWidth) / 2;
const bodyY = y + shackleHeight / 2;

ctx.fillRect(bodyX, bodyY, bodyWidth, bodyHeight);
ctx.restore();
};

export const ScanOverlay = () => {
const refCanvas = useRef<HTMLCanvasElement>(null);
const refEventCatcher = useRef<HTMLDivElement>(null);
Expand All @@ -57,42 +94,6 @@ export const ScanOverlay = () => {
const refIsFadingOut = useRef(false);
const refLastFrameTime = useRef<number>(0);

const drawLockIcon = (
ctx: CanvasRenderingContext2D,
x: number,
y: number,
size: number,
) => {
ctx.save();
ctx.strokeStyle = 'white';
ctx.fillStyle = 'white';
ctx.lineWidth = 1.5;

const shackleWidth = size * 0.6;
const shackleHeight = size * 0.5;
const shackleX = x + (size - shackleWidth) / 2;
const shackleY = y;

ctx.beginPath();
ctx.arc(
shackleX + shackleWidth / 2,
shackleY + shackleHeight / 2,
shackleWidth / 2,
Math.PI,
0,
false,
);
ctx.stroke();

const bodyWidth = size * 0.8;
const bodyHeight = size * 0.5;
const bodyX = x + (size - bodyWidth) / 2;
const bodyY = y + shackleHeight / 2;

ctx.fillRect(bodyX, bodyY, bodyWidth, bodyHeight);
ctx.restore();
};

const drawStatsPill = (
ctx: CanvasRenderingContext2D,
rect: Rect,
Expand Down
Loading