diff --git a/client/components/Button.jsx b/client/components/Button.jsx index 9f2ed889..99bff88c 100644 --- a/client/components/Button.jsx +++ b/client/components/Button.jsx @@ -1,9 +1,22 @@ import cls from "classnames"; +import PropTypes from "prop-types"; import PureComponent from "../lib/PureComponent.jsx"; import * as styles from "./Button.css"; export default class Button extends PureComponent { + static propTypes = { + className: PropTypes.string, + + active: PropTypes.bool, + toggle: PropTypes.bool, + disabled: PropTypes.bool, + + onClick: PropTypes.func.isRequired, + + children: PropTypes.node, + }; + render({ active, className, children, ...props }) { const classes = cls(className, { [styles.button]: true, @@ -25,12 +38,15 @@ export default class Button extends PureComponent { } get disabled() { - const { props } = this; - return props.disabled || (props.active && !props.toggle); + const { disabled, active, toggle } = this.props; + return disabled || (active && !toggle); } handleClick = (event) => { - this.elem.blur(); + if (this.elem) { + this.elem.blur(); + } + this.props.onClick(event); }; diff --git a/client/components/Checkbox.jsx b/client/components/Checkbox.jsx index be2c1ed3..2e64366c 100644 --- a/client/components/Checkbox.jsx +++ b/client/components/Checkbox.jsx @@ -1,9 +1,20 @@ import cls from "classnames"; import { Component } from "preact"; +import PropTypes from "prop-types"; import * as styles from "./Checkbox.css"; export default class Checkbox extends Component { + static propTypes = { + className: PropTypes.string, + + checked: PropTypes.bool, + + onChange: PropTypes.func.isRequired, + + children: PropTypes.node, + }; + render() { const { checked, className, children } = this.props; @@ -15,7 +26,7 @@ export default class Checkbox extends Component { checked={checked} onChange={this.handleChange} /> - {children} + {children && {children}} ); } diff --git a/client/components/CheckboxList.jsx b/client/components/CheckboxList.jsx index 984c0b5e..e737bb40 100644 --- a/client/components/CheckboxList.jsx +++ b/client/components/CheckboxList.jsx @@ -1,10 +1,21 @@ +import PropTypes from "prop-types"; import PureComponent from "../lib/PureComponent.jsx"; import * as styles from "./CheckboxList.css"; import CheckboxListItem from "./CheckboxListItem.jsx"; +import { ViewerDataType } from "./types.js"; const ALL_ITEM = Symbol("ALL_ITEM"); export default class CheckboxList extends PureComponent { + static propTypes = { + label: PropTypes.string.isRequired, + renderLabel: PropTypes.func.isRequired, + items: PropTypes.oneOfType([ViewerDataType, PropTypes.symbol]), + checkedItems: PropTypes.oneOfType([ViewerDataType, PropTypes.symbol]), + + onChange: PropTypes.func.isRequired, + }; + static ALL_ITEM = ALL_ITEM; constructor(props) { diff --git a/client/components/CheckboxListItem.jsx b/client/components/CheckboxListItem.jsx index eb36b9c9..626e7150 100644 --- a/client/components/CheckboxListItem.jsx +++ b/client/components/CheckboxListItem.jsx @@ -1,10 +1,21 @@ import { Component } from "preact"; +import PropTypes from "prop-types"; import Checkbox from "./Checkbox.jsx"; import * as styles from "./CheckboxList.css"; import CheckboxList from "./CheckboxList.jsx"; +import { ViewerDataItemType } from "./types.js"; export default class CheckboxListItem extends Component { + static propTypes = { + item: PropTypes.oneOfType([ViewerDataItemType, PropTypes.symbol]) + .isRequired, + + onChange: PropTypes.func.isRequired, + + children: PropTypes.func, + }; + render() { return (
@@ -17,7 +28,6 @@ export default class CheckboxListItem extends Component { renderLabel() { const { children, item } = this.props; - if (children) { return children(item); } diff --git a/client/components/ContextMenu.jsx b/client/components/ContextMenu.jsx index 8228dfb0..1db649fa 100644 --- a/client/components/ContextMenu.jsx +++ b/client/components/ContextMenu.jsx @@ -1,11 +1,25 @@ import cls from "classnames"; +import PropTypes from "prop-types"; import PureComponent from "../lib/PureComponent.jsx"; import { store } from "../store.js"; import { elementIsOutside } from "../utils.js"; import * as styles from "./ContextMenu.css"; import ContextMenuItem from "./ContextMenuItem.jsx"; +import { ViewerDataItemType } from "./types.js"; export default class ContextMenu extends PureComponent { + static propTypes = { + visible: PropTypes.bool, + + chunk: ViewerDataItemType, + coords: PropTypes.shape({ + x: PropTypes.number, + y: PropTypes.number, + }).isRequired, + + onHide: PropTypes.func, + }; + componentDidMount() { this.boundingRect = this.node.getBoundingClientRect(); } @@ -68,24 +82,25 @@ export default class ContextMenu extends PureComponent { const filteredChunks = store.selectedChunks.filter( (chunk) => chunk.label !== selectedChunk.label, ); - store.selectedChunks = filteredChunks; + store.setSelectedChunks(filteredChunks); } this.hide(); }; handleClickFilterToChunk = () => { const { chunk: selectedChunk } = this.props; + if (selectedChunk && selectedChunk.label) { const filteredChunks = store.allChunks.filter( (chunk) => chunk.label === selectedChunk.label, ); - store.selectedChunks = filteredChunks; + store.setSelectedChunks(filteredChunks); } this.hide(); }; handleClickShowAllChunks = () => { - store.selectedChunks = store.allChunks; + store.setSelectedChunks(store.allChunks); this.hide(); }; diff --git a/client/components/ContextMenuItem.jsx b/client/components/ContextMenuItem.jsx index d44f398b..3508e9d3 100644 --- a/client/components/ContextMenuItem.jsx +++ b/client/components/ContextMenuItem.jsx @@ -1,4 +1,5 @@ import cls from "classnames"; +import PropTypes from "prop-types"; import * as styles from "./ContextMenuItem.css"; @@ -18,3 +19,11 @@ export default function ContextMenuItem({ children, disabled, onClick }) { ); } + +ContextMenuItem.propTypes = { + disabled: PropTypes.bool, + + children: PropTypes.node.isRequired, + + onClick: PropTypes.func, +}; diff --git a/client/components/Dropdown.jsx b/client/components/Dropdown.jsx index db306e11..7a415e41 100644 --- a/client/components/Dropdown.jsx +++ b/client/components/Dropdown.jsx @@ -1,9 +1,16 @@ import { createRef } from "preact"; +import PropTypes from "prop-types"; import PureComponent from "../lib/PureComponent.jsx"; import * as styles from "./Dropdown.css"; export default class Dropdown extends PureComponent { + static propTypes = { + label: PropTypes.string.isRequired, + options: PropTypes.arrayOf(PropTypes.string).isRequired, + onSelectionChange: PropTypes.func.isRequired, + }; + input = createRef(); state = { diff --git a/client/components/Icon.jsx b/client/components/Icon.jsx index d91930af..fbc59b1d 100644 --- a/client/components/Icon.jsx +++ b/client/components/Icon.jsx @@ -1,4 +1,5 @@ import cls from "classnames"; +import PropTypes from "prop-types"; import iconArrowRight from "../assets/icon-arrow-right.svg"; import iconMoon from "../assets/icon-moon.svg"; import iconPin from "../assets/icon-pin.svg"; @@ -27,6 +28,14 @@ const ICONS = { }; export default class Icon extends PureComponent { + static propTypes = { + className: PropTypes.string, + + name: PropTypes.string.isRequired, + size: PropTypes.number, + rotate: PropTypes.number, + }; + render({ className }) { return ; } diff --git a/client/components/ModuleItem.jsx b/client/components/ModuleItem.jsx index 6871622f..189a362d 100644 --- a/client/components/ModuleItem.jsx +++ b/client/components/ModuleItem.jsx @@ -2,11 +2,22 @@ import cls from "classnames"; import escapeRegExp from "escape-string-regexp"; import { filesize } from "filesize"; import { escape } from "html-escaper"; +import PropTypes from "prop-types"; import PureComponent from "../lib/PureComponent.jsx"; - import * as styles from "./ModuleItem.css"; +import { ModuleType, SizeType } from "./types.js"; export default class ModuleItem extends PureComponent { + static propTypes = { + module: ModuleType.isRequired, + showSize: SizeType.isRequired, + highlightedText: PropTypes.instanceOf(RegExp), + + isVisible: PropTypes.func.isRequired, + + onClick: PropTypes.func.isRequired, + }; + state = { visible: true, }; diff --git a/client/components/ModulesList.jsx b/client/components/ModulesList.jsx index 50b605a6..1404d2ec 100644 --- a/client/components/ModulesList.jsx +++ b/client/components/ModulesList.jsx @@ -1,10 +1,22 @@ import cls from "classnames"; +import PropTypes from "prop-types"; import PureComponent from "../lib/PureComponent.jsx"; import ModuleItem from "./ModuleItem.jsx"; - import * as styles from "./ModulesList.css"; +import { ModuleType, SizeType } from "./types.js"; export default class ModulesList extends PureComponent { + static propTypes = { + className: PropTypes.string, + + modules: PropTypes.arrayOf(ModuleType).isRequired, + showSize: SizeType.isRequired, + highlightedText: PropTypes.instanceOf(RegExp), + + isModuleVisible: PropTypes.func.isRequired, + onModuleClick: PropTypes.func.isRequired, + }; + render({ modules, showSize, highlightedText, isModuleVisible, className }) { return (
diff --git a/client/components/ModulesTreemap.jsx b/client/components/ModulesTreemap.jsx index 81de5f38..7a2a9833 100644 --- a/client/components/ModulesTreemap.jsx +++ b/client/components/ModulesTreemap.jsx @@ -173,7 +173,9 @@ class ModulesTreemap extends Component { onGroupSecondaryClick={this.handleTreemapGroupSecondaryClick} onResize={this.handleResize} /> - {tooltipContent} + {tooltipContent && ( + {tooltipContent} + )} { if (!selected) { - store.selectedChunks = store.allChunks; + store.setSelectedChunks(store.allChunks); return; } - store.selectedChunks = store.allChunks.filter( - (chunk) => chunk.isInitialByEntrypoint[selected] ?? false, + store.setSelectedChunks( + store.allChunks.filter( + (chunk) => chunk.isInitialByEntrypoint[selected] ?? false, + ), ); }; @@ -314,15 +318,15 @@ class ModulesTreemap extends Component { }; handleSizeSwitch = (sizeSwitchItem) => { - store.selectedSize = sizeSwitchItem.prop; + store.setSelectedSize(sizeSwitchItem.prop); }; handleQueryChange = (query) => { - store.searchQuery = query; + store.setSearchQuery(query); }; handleSelectedChunksChange = (selectedChunks) => { - store.selectedChunks = selectedChunks; + store.setSelectedSize(selectedChunks); }; handleMouseLeaveTreemap = () => { diff --git a/client/components/Search.jsx b/client/components/Search.jsx index ff207b7b..4aaf9263 100644 --- a/client/components/Search.jsx +++ b/client/components/Search.jsx @@ -1,12 +1,24 @@ // TODO: switch to a more modern debounce package once we drop Node.js 10 support import debounce from "debounce"; +import PropTypes from "prop-types"; import PureComponent from "../lib/PureComponent.jsx"; import Button from "./Button.jsx"; import * as styles from "./Search.css"; export default class Search extends PureComponent { + static propTypes = { + className: PropTypes.string, + + label: PropTypes.string.isRequired, + query: PropTypes.string.isRequired, + + autofocus: PropTypes.bool, + + onQueryChange: PropTypes.func.isRequired, + }; + componentDidMount() { if (this.props.autofocus) { this.focus(); diff --git a/client/components/Sidebar.jsx b/client/components/Sidebar.jsx index f859aaa9..db5351c6 100644 --- a/client/components/Sidebar.jsx +++ b/client/components/Sidebar.jsx @@ -1,6 +1,6 @@ import cls from "classnames"; import { Component } from "preact"; - +import PropTypes from "prop-types"; import Button from "./Button.jsx"; import Icon from "./Icon.jsx"; import * as styles from "./Sidebar.css"; @@ -9,6 +9,17 @@ import ThemeToggle from "./ThemeToggle.jsx"; const toggleTime = Number.parseInt(styles.toggleTime, 10); export default class Sidebar extends Component { + static propTypes = { + pinned: PropTypes.bool.isRequired, + position: PropTypes.string, + + onToggle: PropTypes.func.isRequired, + onResize: PropTypes.func.isRequired, + onPinStateChange: PropTypes.func.isRequired, + + children: PropTypes.node.isRequired, + }; + static defaultProps = { pinned: false, position: "left", diff --git a/client/components/Switcher.jsx b/client/components/Switcher.jsx index f3ed7834..6b9c0227 100644 --- a/client/components/Switcher.jsx +++ b/client/components/Switcher.jsx @@ -1,8 +1,19 @@ +import PropTypes from "prop-types"; import PureComponent from "../lib/PureComponent.jsx"; import * as styles from "./Switcher.css"; import SwitcherItem from "./SwitcherItem.jsx"; +import { SwitcherItemType } from "./types.js"; export default class Switcher extends PureComponent { + static propTypes = { + label: PropTypes.string.isRequired, + + items: PropTypes.arrayOf(SwitcherItemType).isRequired, + activeItem: SwitcherItemType.isRequired, + + onSwitch: PropTypes.func.isRequired, + }; + render() { const { label, items, activeItem, onSwitch } = this.props; diff --git a/client/components/SwitcherItem.jsx b/client/components/SwitcherItem.jsx index 8f32c704..80b70941 100644 --- a/client/components/SwitcherItem.jsx +++ b/client/components/SwitcherItem.jsx @@ -1,7 +1,17 @@ +import PropTypes from "prop-types"; import PureComponent from "../lib/PureComponent.jsx"; import Button from "./Button.jsx"; +import { SwitcherItemType } from "./types.js"; export default class SwitcherItem extends PureComponent { + static propTypes = { + active: PropTypes.bool.isRequired, + + item: SwitcherItemType.isRequired, + + onClick: PropTypes.func.isRequired, + }; + render({ item, ...props }) { return (