Skip to content

Commit

Permalink
feat: add button tooltips, double click to scroll to element
Browse files Browse the repository at this point in the history
  • Loading branch information
maitrungduc1410 committed Dec 18, 2023
1 parent 7700e76 commit b041c50
Show file tree
Hide file tree
Showing 13 changed files with 6,533 additions and 330 deletions.
7 changes: 4 additions & 3 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"plugin:import/recommended",
"plugin:jsx-a11y/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
Expand All @@ -21,15 +20,17 @@
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint", "react-hooks", "import", "jsx-a11y", "prettier"],
"plugins": ["react", "@typescript-eslint", "react-hooks", "import", "prettier"],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"react/react-in-jsx-scope": "off",
"import/no-unresolved": "off"
"import/no-unresolved": "off",
"@typescript-eslint/no-explicit-any": "off",
"react-hooks/exhaustive-deps": "off"
},
"globals": {
"chrome": "readonly"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "konva-inspector",
"version": "0.0.17",
"version": "0.0.18",
"description": "Devtools for your Konva App",
"license": "MIT",
"repository": {
Expand Down Expand Up @@ -31,6 +31,7 @@
"lodash-es": "^4.17.21",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-tooltip": "^5.25.0",
"webextension-polyfill": "0.10.0"
},
"devDependencies": {
Expand All @@ -54,7 +55,6 @@
"eslint-config-airbnb-typescript": "17.1.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-import": "2.29.0",
"eslint-plugin-jsx-a11y": "6.8.0",
"eslint-plugin-prettier": "5.0.1",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "4.6.0",
Expand Down
115 changes: 43 additions & 72 deletions src/pages/panel/components/Attributes.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useState } from "react";
import { bridge } from "..";
import CopyToClipboard from "./icons/CopyToClipboard";
import { IAttr } from "./constants";
import DownArrow from "./icons/DownArrow";
import RightArrow from "./icons/RightArrow";
import Delete from "./icons/Delete";
import { useState } from 'react';
import { bridge } from '..';
import CopyToClipboard from './icons/CopyToClipboard';
import { IAttr } from './constants';
import DownArrow from './icons/DownArrow';
import RightArrow from './icons/RightArrow';
import Delete from './icons/Delete';
import { Tooltip } from 'react-tooltip';

interface IProps {
attrSearch?: string;
Expand All @@ -16,7 +17,7 @@ interface IProps {
showCopyToClipboard?: boolean;
showDelete?: boolean;
showExpandIcon?: boolean;
updateAttr: (attrName: string, val: any) => Promise<void>;
updateAttr: (attrName: string, val: string | boolean | number) => Promise<void>;
onRemove?: () => void;
}

Expand All @@ -37,49 +38,43 @@ export default function Attributes({

const copyToClipBoard = () => {
bridge(
`window.copy(window.__KONVA_DEVTOOLS_GLOBAL_HOOK__ && window.__KONVA_DEVTOOLS_GLOBAL_HOOK__.selection.selected().attrs)`
`window.copy(window.__KONVA_DEVTOOLS_GLOBAL_HOOK__ && window.__KONVA_DEVTOOLS_GLOBAL_HOOK__.selection.selected().attrs)`,
);
};

const renderArrow = () => {
return (
<div
className={`expand-collapse-toggle ${showExpandIcon ? "" : "hidden"}`}
onClick={() => setExpanded((v) => !v)}
>
<div className={`expand-collapse-toggle ${showExpandIcon ? '' : 'hidden'}`} onClick={() => setExpanded(v => !v)}>
{expanded ? <DownArrow /> : <RightArrow />}
</div>
);
};

const filteredAttrs = attrSearch
? attrs.filter((item) =>
item.name.toLowerCase().startsWith(attrSearch.toLowerCase())
)
? attrs.filter(item => item.name.toLowerCase().startsWith(attrSearch.toLowerCase()))
: attrs;

return (
<div className={`attributes ${borderDashed ? "dashed" : ""}`}>
<div className={`attributes ${borderDashed ? 'dashed' : ''}`}>
<div className="header-row">
{renderArrow()}
<div className="header">{title}</div>
{showCopyToClipboard && (
<button
className="button"
title="Copy Attributes to Clipboard"
onClick={() => copyToClipBoard()}
>
<span className="button-content" tabIndex={-1}>
<CopyToClipboard />
</span>
</button>
<>
<Tooltip id="copy-attrs-to-clipboard" />
<button
className="button"
data-tooltip-id="copy-attrs-to-clipboard"
data-tooltip-content="Copy Attributes to Clipboard"
onClick={() => copyToClipBoard()}>
<span className="button-content" tabIndex={-1}>
<CopyToClipboard />
</span>
</button>
</>
)}
{showDelete && (
<button
className="button"
title="Remove entry"
onClick={() => onRemove && onRemove()}
>
<button className="button" title="Remove entry" onClick={() => onRemove && onRemove()}>
<span className="button-content" tabIndex={-1}>
<Delete />
</span>
Expand All @@ -88,11 +83,11 @@ export default function Attributes({
</div>
{expanded && (
<div className="attr-list">
{filteredAttrs.map((item) => {
{filteredAttrs.map(item => {
let input;

switch (item.type) {
case "boolean": {
case 'boolean': {
input = (
<input
type="checkbox"
Expand All @@ -103,27 +98,23 @@ export default function Attributes({
: true
: nodeAttrs[item.name]
}
onChange={(e) => updateAttr(item.name, e.target.checked)}
onChange={e => updateAttr(item.name, e.target.checked)}
/>
);
break;
}
case "number": {
case 'number': {
input = (
<input
value={
nodeAttrs[item.name] !== undefined
? nodeAttrs[item.name]
: ""
}
value={nodeAttrs[item.name] !== undefined ? nodeAttrs[item.name] : ''}
type="number"
placeholder="<default>"
onChange={(e) =>
onChange={e =>
updateAttr(
item.name,
isNaN(e.target.valueAsNumber)
? null // JSON.stringify will not preserve undefined, so we have to use null here
: e.target.valueAsNumber
: e.target.valueAsNumber,
)
}
min={item.min}
Expand All @@ -133,32 +124,21 @@ export default function Attributes({
);
break;
}
case "json": {
case 'json': {
input = (
<textarea
value={
nodeAttrs[item.name] !== undefined
? JSON.stringify(nodeAttrs[item.name])
: ""
}
value={nodeAttrs[item.name] !== undefined ? JSON.stringify(nodeAttrs[item.name]) : ''}
placeholder="<default>"
onChange={(e) =>
updateAttr(item.name, JSON.parse(e.target.value))
}
onChange={e => updateAttr(item.name, JSON.parse(e.target.value))}
/>
);
break;
}
case "select": {
case 'select': {
input = (
<select
onChange={(e) => updateAttr(item.name, e.target.value)}
>
<select onChange={e => updateAttr(item.name, e.target.value)}>
{item.options.map((option, index) => (
<option
key={`${option.label}-${index}`}
value={option.value}
>
<option key={`${option.label}-${index}`} value={option.value}>
{option.label}
</option>
))}
Expand All @@ -169,29 +149,20 @@ export default function Attributes({
default: {
input = (
<input
value={
nodeAttrs[item.name] !== undefined
? nodeAttrs[item.name]
: ""
}
value={nodeAttrs[item.name] !== undefined ? nodeAttrs[item.name] : ''}
type="text"
placeholder="<default>"
onChange={(e) => updateAttr(item.name, e.target.value)}
onChange={e => updateAttr(item.name, e.target.value)}
/>
);
}
}
return (
<div className="attr-item" key={item.name}>
<span
className="item-name"
style={{ color: keyColor || "inherit" }}
>
<span className="item-name" style={{ color: keyColor || 'inherit' }}>
{attrSearch?.length ? (
<>
<mark className="current-highlight">
{item.name.slice(0, attrSearch.length)}
</mark>
<mark className="current-highlight">{item.name.slice(0, attrSearch.length)}</mark>
<span>{item.name.slice(attrSearch.length)}</span>
</>
) : (
Expand Down
60 changes: 27 additions & 33 deletions src/pages/panel/components/Element.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState } from "react";
import DownArrow from "./icons/DownArrow";
import RightArrow from "./icons/RightArrow";
import { OutlineNode } from "../types";
import { bridge } from "..";
import { useState } from 'react';
import DownArrow from './icons/DownArrow';
import RightArrow from './icons/RightArrow';
import { OutlineNode } from '../types';
import { bridge } from '..';

interface IProps {
searchText: string;
Expand All @@ -28,53 +28,48 @@ export default function Element({
const renderArrow = () => {
return (
<div
className={`expand-collapse-toggle ${
!node.children?.length ? "hidden" : ""
}`}
onClick={() => setExpanded((v) => !v)}
>
className={`expand-collapse-toggle ${!node.children?.length ? 'hidden' : ''}`}
onClick={() => setExpanded(v => !v)}>
{expanded ? <DownArrow /> : <RightArrow />}
</div>
);
};

const shouldHighlight =
searchText.length &&
node.className.toLowerCase().startsWith(searchText.toLowerCase());
const shouldHighlight = searchText.length && node.className.toLowerCase().startsWith(searchText.toLowerCase());

const select = async (scrollToElement = false) => {
const data = await bridge<OutlineNode>(
`window.__KONVA_DEVTOOLS_GLOBAL_HOOK__ && window.__KONVA_DEVTOOLS_GLOBAL_HOOK__.selection.select(${node._id}, ${stageIndex}, ${scrollToElement})`,
);

onSelectNode(data);
};
return (
<>
<div
id={node._id.toString()}
className={`element ${
selectedNode?._id === node._id ? "selected" : ""
} ${activeNode?._id === node._id ? "active" : ""}`}
className={`element ${selectedNode?._id === node._id ? 'selected' : ''} ${
activeNode?._id === node._id ? 'active' : ''
}`}
style={{ paddingLeft: indent * 15 }}
onClick={async () => {
const data = await bridge<OutlineNode>(
`window.__KONVA_DEVTOOLS_GLOBAL_HOOK__ && window.__KONVA_DEVTOOLS_GLOBAL_HOOK__.selection.select(${node._id}, ${stageIndex})`
);

onSelectNode(data);
}}
onClick={() => select()}
onDoubleClick={() => select(true)}
onMouseEnter={() => {
bridge(
`window.__KONVA_DEVTOOLS_GLOBAL_HOOK__ && window.__KONVA_DEVTOOLS_GLOBAL_HOOK__.selection.activate(${node._id}, ${stageIndex})`
`window.__KONVA_DEVTOOLS_GLOBAL_HOOK__ && window.__KONVA_DEVTOOLS_GLOBAL_HOOK__.selection.activate(${node._id}, ${stageIndex})`,
);
}}
>
title="Double click to scroll to element">
{renderArrow()}
{shouldHighlight ? (
<>
<mark className="current-highlight">
{node.className.slice(0, searchText.length)}
</mark>
<mark className="current-highlight">{node.className.slice(0, searchText.length)}</mark>
<span>{node.className.slice(searchText.length)}</span>
</>
) : (
<>{node.className}</>
)}
&nbsp;<span style={{ color: "var(--color-id-key)" }}>_id</span>=
<span className="key-value">{node._id}</span>
&nbsp;<span style={{ color: 'var(--color-id-key)' }}>_id</span>=<span className="key-value">{node._id}</span>
{node.attrs.name && (
<span title={node.attrs.name}>
&nbsp;<span className="key-name">name</span>=
Expand All @@ -83,13 +78,12 @@ export default function Element({
)}
{node.attrs.id && (
<span title={node.attrs.id}>
&nbsp;<span className="key-name">id</span>=
<span className="key-value">&quot;{node.attrs.id}&quot;</span>
&nbsp;<span className="key-name">id</span>=<span className="key-value">&quot;{node.attrs.id}&quot;</span>
</span>
)}
</div>
{expanded &&
node.children?.map((item) => (
node.children?.map(item => (
<Element
key={item._id}
searchText={searchText}
Expand Down
Loading

0 comments on commit b041c50

Please sign in to comment.