mirror of
https://github.com/filecoin-project/slate.git
synced 2024-12-28 11:32:46 +03:00
Merge pull request #504 from filecoin-project/feat/dragAndSelect
feat: drag and select behavior
This commit is contained in:
commit
8487c9098f
@ -16,6 +16,7 @@ import { CheckBox } from "~/components/system/components/CheckBox";
|
||||
import { Table } from "~/components/core/Table";
|
||||
import { FileTypeIcon } from "~/components/core/FileTypeIcon";
|
||||
import { ButtonPrimary, ButtonWarning } from "~/components/system/components/Buttons";
|
||||
import { GroupSelectable, Selectable } from "~/components/core/Selectable/";
|
||||
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
import FilePreviewBubble from "~/components/core/FilePreviewBubble";
|
||||
@ -283,6 +284,31 @@ export default class DataView extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
_addSelectedItemsOnDrag = (e) => {
|
||||
let selectedItems = {};
|
||||
for (const i of e) {
|
||||
selectedItems[i] = true;
|
||||
}
|
||||
this.setState({ checked: { ...this.state.checked, ...selectedItems } });
|
||||
};
|
||||
|
||||
_removeSelectedItemsOnDrag = (e) => {
|
||||
const selectedItems = { ...this.state.checked };
|
||||
for (const i in selectedItems) {
|
||||
selectedItems[i] = selectedItems[i] && !e.includes(+i);
|
||||
if (!selectedItems[i]) delete selectedItems[i];
|
||||
}
|
||||
this.setState({ checked: selectedItems, ...selectedItems });
|
||||
};
|
||||
|
||||
_handleDragAndSelect = (e, { isAltDown }) => {
|
||||
if (isAltDown) {
|
||||
this._removeSelectedItemsOnDrag(e);
|
||||
return;
|
||||
}
|
||||
this._addSelectedItemsOnDrag(e);
|
||||
};
|
||||
|
||||
_handleKeyUp = (e) => {
|
||||
if (e.keyCode === 16 && this.isShiftDown) {
|
||||
this.isShiftDown = false;
|
||||
@ -379,6 +405,14 @@ export default class DataView extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
_handleCheckBoxMouseEnter = (i) => {
|
||||
this.setState({ hover: i });
|
||||
};
|
||||
|
||||
_handleCheckBoxMouseLeave = (i) => {
|
||||
this.setState({ hover: null });
|
||||
};
|
||||
|
||||
_handleCopy = (e, value) => {
|
||||
e.stopPropagation();
|
||||
this._handleHide();
|
||||
@ -491,135 +525,138 @@ export default class DataView extends React.Component {
|
||||
if (this.props.view === 0) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div css={STYLES_IMAGE_GRID} ref={this.gridWrapperEl}>
|
||||
{this.props.items.slice(0, this.state.viewLimit).map((each, i) => {
|
||||
const cid = each.cid;
|
||||
return (
|
||||
<div
|
||||
key={each.id}
|
||||
css={STYLES_IMAGE_BOX}
|
||||
style={{
|
||||
width: this.state.imageSize,
|
||||
height: this.state.imageSize,
|
||||
boxShadow: numChecked
|
||||
? `0px 0px 0px 1px ${Constants.system.lightBorder} inset,
|
||||
<GroupSelectable onSelection={this._handleDragAndSelect}>
|
||||
<div css={STYLES_IMAGE_GRID} ref={this.gridWrapperEl}>
|
||||
{this.props.items.slice(0, this.state.viewLimit).map((each, i) => {
|
||||
const cid = each.cid;
|
||||
return (
|
||||
<Selectable
|
||||
key={each.id}
|
||||
selectableKey={i}
|
||||
css={STYLES_IMAGE_BOX}
|
||||
style={{
|
||||
width: this.state.imageSize,
|
||||
height: this.state.imageSize,
|
||||
boxShadow: numChecked
|
||||
? `0px 0px 0px 1px ${Constants.system.lightBorder} inset,
|
||||
0 0 40px 0 ${Constants.system.shadow}`
|
||||
: "",
|
||||
}}
|
||||
onClick={() => this._handleSelect(i)}
|
||||
onMouseEnter={() => this.setState({ hover: i })}
|
||||
onMouseLeave={() => this.setState({ hover: null })}
|
||||
>
|
||||
<SlateMediaObjectPreview
|
||||
blurhash={each.blurhash}
|
||||
url={Strings.getCIDGatewayURL(each.cid)}
|
||||
title={each.file || each.name}
|
||||
type={each.type}
|
||||
coverImage={each.coverImage}
|
||||
dataView={true}
|
||||
/>
|
||||
<span css={STYLES_MOBILE_HIDDEN}>
|
||||
{numChecked || this.state.hover === i || this.state.menu === each.id ? (
|
||||
<React.Fragment>
|
||||
<div
|
||||
css={STYLES_ICON_BOX_BACKGROUND}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({
|
||||
menu: this.state.menu === each.id ? null : each.id,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SVG.MoreHorizontal height="24px" />
|
||||
{this.state.menu === each.id ? (
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={this._handleHide}
|
||||
>
|
||||
{this.props.isOwner ? (
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
top: "32px",
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: "Copy CID",
|
||||
onClick: (e) => this._handleCopy(e, cid),
|
||||
},
|
||||
{
|
||||
text: "Copy link",
|
||||
onClick: (e) =>
|
||||
this._handleCopy(e, Strings.getCIDGatewayURL(cid)),
|
||||
},
|
||||
{
|
||||
text: "Delete",
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ menu: null }, () =>
|
||||
this._handleDelete(cid, each.id)
|
||||
);
|
||||
: "",
|
||||
}}
|
||||
onClick={() => this._handleSelect(i)}
|
||||
onMouseEnter={() => this._handleCheckBoxMouseEnter(i)}
|
||||
onMouseLeave={() => this._handleCheckBoxMouseLeave(i)}
|
||||
>
|
||||
<SlateMediaObjectPreview
|
||||
blurhash={each.blurhash}
|
||||
url={Strings.getCIDGatewayURL(each.cid)}
|
||||
title={each.file || each.name}
|
||||
type={each.type}
|
||||
coverImage={each.coverImage}
|
||||
dataView={true}
|
||||
/>
|
||||
<span css={STYLES_MOBILE_HIDDEN} style={{ pointerEvents: "auto" }}>
|
||||
{numChecked || this.state.hover === i || this.state.menu === each.id ? (
|
||||
<React.Fragment>
|
||||
<div
|
||||
css={STYLES_ICON_BOX_BACKGROUND}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({
|
||||
menu: this.state.menu === each.id ? null : each.id,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SVG.MoreHorizontal height="24px" />
|
||||
{this.state.menu === each.id ? (
|
||||
<Boundary
|
||||
captureResize={true}
|
||||
captureScroll={false}
|
||||
enabled
|
||||
onOutsideRectEvent={this._handleHide}
|
||||
>
|
||||
{this.props.isOwner ? (
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
top: "32px",
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: "Copy CID",
|
||||
onClick: (e) => this._handleCopy(e, cid),
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
top: "32px",
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: "Copy CID",
|
||||
onClick: (e) => this._handleCopy(e, cid),
|
||||
},
|
||||
{
|
||||
text: "Copy link",
|
||||
onClick: (e) =>
|
||||
this._handleCopy(e, Strings.getCIDGatewayURL(cid)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Boundary>
|
||||
) : null}
|
||||
</div>
|
||||
{
|
||||
text: "Copy link",
|
||||
onClick: (e) =>
|
||||
this._handleCopy(e, Strings.getCIDGatewayURL(cid)),
|
||||
},
|
||||
{
|
||||
text: "Delete",
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ menu: null }, () =>
|
||||
this._handleDelete(cid, each.id)
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<PopoverNavigation
|
||||
style={{
|
||||
top: "32px",
|
||||
right: "0px",
|
||||
}}
|
||||
navigation={[
|
||||
{
|
||||
text: "Copy CID",
|
||||
onClick: (e) => this._handleCopy(e, cid),
|
||||
},
|
||||
{
|
||||
text: "Copy link",
|
||||
onClick: (e) =>
|
||||
this._handleCopy(e, Strings.getCIDGatewayURL(cid)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Boundary>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div onClick={(e) => this._handleCheckBox(e, i)}>
|
||||
<CheckBox
|
||||
name={i}
|
||||
value={!!this.state.checked[i]}
|
||||
boxStyle={{
|
||||
height: 24,
|
||||
width: 24,
|
||||
backgroundColor: this.state.checked[i]
|
||||
? Constants.system.brand
|
||||
: "rgba(255, 255, 255, 0.75)",
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
css={STYLES_IMAGE_BOX}
|
||||
style={{ boxShadow: "none", cursor: "default" }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div onClick={(e) => this._handleCheckBox(e, i)}>
|
||||
<CheckBox
|
||||
name={i}
|
||||
value={!!this.state.checked[i]}
|
||||
boxStyle={{
|
||||
height: 24,
|
||||
width: 24,
|
||||
backgroundColor: this.state.checked[i]
|
||||
? Constants.system.brand
|
||||
: "rgba(255, 255, 255, 0.75)",
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
</span>
|
||||
</Selectable>
|
||||
);
|
||||
})}
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
css={STYLES_IMAGE_BOX}
|
||||
style={{ boxShadow: "none", cursor: "default" }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</GroupSelectable>
|
||||
{footer}
|
||||
<input
|
||||
ref={(c) => {
|
||||
@ -688,14 +725,16 @@ export default class DataView extends React.Component {
|
||||
</div>
|
||||
),
|
||||
name: (
|
||||
<FilePreviewBubble url={cid} type={each.type}>
|
||||
<div css={STYLES_CONTAINER_HOVER} onClick={() => this._handleSelect(index)}>
|
||||
<div css={STYLES_ICON_BOX_HOVER} style={{ paddingLeft: 0, paddingRight: 18 }}>
|
||||
<FileTypeIcon type={each.type} height="24px" />
|
||||
<Selectable key={each.id} selectableKey={index}>
|
||||
<FilePreviewBubble url={cid} type={each.type}>
|
||||
<div css={STYLES_CONTAINER_HOVER} onClick={() => this._handleSelect(index)}>
|
||||
<div css={STYLES_ICON_BOX_HOVER} style={{ paddingLeft: 0, paddingRight: 18 }}>
|
||||
<FileTypeIcon type={each.type} height="24px" />
|
||||
</div>
|
||||
<div css={STYLES_LINK}>{each.file || each.name}</div>
|
||||
</div>
|
||||
<div css={STYLES_LINK}>{each.file || each.name}</div>
|
||||
</div>
|
||||
</FilePreviewBubble>
|
||||
</FilePreviewBubble>
|
||||
</Selectable>
|
||||
),
|
||||
size: <div css={STYLES_VALUE}>{Strings.bytesToSize(each.size)}</div>,
|
||||
more: (
|
||||
@ -752,22 +791,32 @@ export default class DataView extends React.Component {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Table
|
||||
data={data}
|
||||
rowStyle={{
|
||||
padding: "10px 16px",
|
||||
textAlign: "left",
|
||||
backgroundColor: Constants.system.white,
|
||||
}}
|
||||
topRowStyle={{
|
||||
padding: "0px 16px",
|
||||
textAlign: "left",
|
||||
backgroundColor: Constants.system.white,
|
||||
}}
|
||||
onMouseEnter={(i) => this.setState({ hover: i })}
|
||||
onMouseLeave={() => this.setState({ hover: null })}
|
||||
isShiftDown={this.isShiftDown}
|
||||
/>
|
||||
<GroupSelectable enabled={true} onSelection={this._handleDragAndSelect}>
|
||||
{({ isSelecting }) => (
|
||||
<Table
|
||||
data={data}
|
||||
rowStyle={{
|
||||
padding: "10px 16px",
|
||||
textAlign: "left",
|
||||
backgroundColor: Constants.system.white,
|
||||
}}
|
||||
topRowStyle={{
|
||||
padding: "0px 16px",
|
||||
textAlign: "left",
|
||||
backgroundColor: Constants.system.white,
|
||||
}}
|
||||
onMouseEnter={(i) => {
|
||||
if (isSelecting) return;
|
||||
this._handleCheckBoxMouseEnter(i);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (isSelecting) return;
|
||||
this._handleCheckBoxMouseEnter();
|
||||
}}
|
||||
isShiftDown={this.isShiftDown}
|
||||
/>
|
||||
)}
|
||||
</GroupSelectable>
|
||||
{footer}
|
||||
<input
|
||||
ref={(c) => {
|
||||
|
41
components/core/Selectable/doObjectsCollide.js
Normal file
41
components/core/Selectable/doObjectsCollide.js
Normal file
@ -0,0 +1,41 @@
|
||||
const getBoundsForNode = (node) => {
|
||||
const rect = node.getBoundingClientRect();
|
||||
return {
|
||||
top: rect.top + document.body.scrollTop,
|
||||
left: rect.left + document.body.scrollLeft,
|
||||
offsetWidth: node.offsetWidth,
|
||||
offsetHeight: node.offsetHeight,
|
||||
};
|
||||
};
|
||||
|
||||
const coordsCollide = (aTop, aLeft, bTop, bLeft, aWidth, aHeight, bWidth, bHeight, tolerance) => {
|
||||
return !(
|
||||
// 'a' bottom doesn't touch 'b' top
|
||||
(
|
||||
aTop + aHeight - tolerance < bTop ||
|
||||
// 'a' top doesn't touch 'b' bottom
|
||||
aTop + tolerance > bTop + bHeight ||
|
||||
// 'a' right doesn't touch 'b' left
|
||||
aLeft + aWidth - tolerance < bLeft ||
|
||||
// 'a' left doesn't touch 'b' right
|
||||
aLeft + tolerance > bLeft + bWidth
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default (a, b, tolerance = 0) => {
|
||||
const aObj = a instanceof HTMLElement ? getBoundsForNode(a) : a;
|
||||
const bObj = b instanceof HTMLElement ? getBoundsForNode(b) : b;
|
||||
|
||||
return coordsCollide(
|
||||
aObj.top,
|
||||
aObj.left,
|
||||
bObj.top,
|
||||
bObj.left,
|
||||
aObj.offsetWidth,
|
||||
aObj.offsetHeight,
|
||||
bObj.offsetWidth,
|
||||
bObj.offsetHeight,
|
||||
tolerance
|
||||
);
|
||||
};
|
225
components/core/Selectable/groupSelectable.js
Normal file
225
components/core/Selectable/groupSelectable.js
Normal file
@ -0,0 +1,225 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
import doObjectsCollide from "./doObjectsCollide";
|
||||
|
||||
const Context = React.createContext();
|
||||
|
||||
export const useSelectable = () => {
|
||||
return React.useContext(Context);
|
||||
};
|
||||
|
||||
const GROUP_WRAPPER = css`
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
`;
|
||||
|
||||
const SELECTION_BOX_WRAPPER = css`
|
||||
z-index: 9000;
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
`;
|
||||
|
||||
const SELECTION_BOX_INNER = css`
|
||||
background-color: rgba(255, 255, 255, 0.45);
|
||||
border: 1px dashed ${Constants.system.border};
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
float: left;
|
||||
`;
|
||||
|
||||
export default function GroupSelectable({
|
||||
onSelection,
|
||||
onSelectionStarted,
|
||||
children,
|
||||
enabled: enabledProp = true,
|
||||
...props
|
||||
}) {
|
||||
const ref = React.useRef();
|
||||
const selectBoxRef = React.useRef();
|
||||
|
||||
const isShiftDown = useKeyDown(16);
|
||||
const isAltDown = useKeyDown(18);
|
||||
const enabled = enabledProp && (isShiftDown || isAltDown);
|
||||
|
||||
const { _registerSelectable, _unregisterUnselectable, registery } = useRegistery();
|
||||
const { isBoxSelecting, boxLeft, boxTop, boxWidth, boxHeight } = useGroupSelectable({
|
||||
ref,
|
||||
selectBoxRef,
|
||||
enabled,
|
||||
onSelection: (e) => onSelection(e, { isShiftDown, isAltDown }),
|
||||
onSelectionStarted,
|
||||
registery,
|
||||
});
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
register: _registerSelectable,
|
||||
unregister: _unregisterUnselectable,
|
||||
enabled,
|
||||
}}
|
||||
>
|
||||
<div css={GROUP_WRAPPER} ref={ref} {...props}>
|
||||
{isBoxSelecting ? (
|
||||
<div
|
||||
css={SELECTION_BOX_WRAPPER}
|
||||
style={{ left: boxLeft, top: boxTop, width: boxWidth, height: boxHeight }}
|
||||
ref={selectBoxRef}
|
||||
>
|
||||
<span css={SELECTION_BOX_INNER} />
|
||||
</div>
|
||||
) : null}
|
||||
<div>{typeof children === "function" ? children({ isSelecting: enabled }) : children}</div>
|
||||
</div>
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const useKeyDown = (id) => {
|
||||
const [isKeyDown, setKeyDownBool] = React.useState(false);
|
||||
const _handleKeyDown = (e) => {
|
||||
if (e.keyCode === id) setKeyDownBool(true);
|
||||
};
|
||||
const _handleKeyUp = (e) => {
|
||||
if (e.keyCode === id) setKeyDownBool(false);
|
||||
};
|
||||
React.useEffect(() => {
|
||||
window.addEventListener("keydown", _handleKeyDown);
|
||||
window.addEventListener("keyup", _handleKeyUp);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", _handleKeyDown);
|
||||
window.removeEventListener("keyup", _handleKeyUp);
|
||||
};
|
||||
}, []);
|
||||
return isKeyDown;
|
||||
};
|
||||
|
||||
const useRegistery = () => {
|
||||
const data = React.useRef({ registery: [] });
|
||||
const _registerSelectable = (key, domNode) => {
|
||||
data.current.registery.push({ key, domNode });
|
||||
};
|
||||
const _unregisterUnselectable = (key) =>
|
||||
data.current.registery.filter((item) => item.key !== key);
|
||||
return { _registerSelectable, _unregisterUnselectable, registery: data.current.registery };
|
||||
};
|
||||
|
||||
const _getInitialCoords = (element) => {
|
||||
const style = window.getComputedStyle(document.body);
|
||||
const t = style.getPropertyValue("margin-top");
|
||||
const l = style.getPropertyValue("margin-left");
|
||||
const mLeft = parseInt(l.slice(0, l.length - 2), 10);
|
||||
const mTop = parseInt(t.slice(0, t.length - 2), 10);
|
||||
|
||||
const bodyRect = document.body.getBoundingClientRect();
|
||||
const elemRect = element.getBoundingClientRect();
|
||||
return {
|
||||
x: Math.round(elemRect.left - bodyRect.left + mLeft),
|
||||
y: Math.round(elemRect.top - bodyRect.top + mTop),
|
||||
};
|
||||
};
|
||||
|
||||
const useGroupSelectable = ({
|
||||
ref,
|
||||
selectBoxRef,
|
||||
enabled,
|
||||
onSelection,
|
||||
onSelectionStarted,
|
||||
registery,
|
||||
}) => {
|
||||
const [state, setState] = React.useState({
|
||||
isBoxSelecting: false,
|
||||
boxHeight: 0,
|
||||
boxWidth: 0,
|
||||
});
|
||||
|
||||
const data = React.useRef({
|
||||
mouseDataDown: null,
|
||||
rect: null,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
_applyMouseDown(enabled);
|
||||
data.current.rect = _getInitialCoords(ref.current);
|
||||
|
||||
return () => {
|
||||
if (!ref.current) return;
|
||||
_applyMouseDown(false);
|
||||
};
|
||||
}, [enabled]);
|
||||
|
||||
const _applyMouseDown = (enabled) => {
|
||||
const fncName = enabled ? "addEventListener" : "removeEventListener";
|
||||
ref.current[fncName]("mousedown", _mousedown);
|
||||
};
|
||||
|
||||
const _drawBox = (e) => {
|
||||
const w = Math.abs(data.current.mouseDataDown.initialW - e.pageX + data.current.rect.x);
|
||||
const h = Math.abs(data.current.mouseDataDown.initialH - e.pageY + data.current.rect.y);
|
||||
|
||||
setState({
|
||||
isBoxSelecting: true,
|
||||
boxWidth: w,
|
||||
boxHeight: h,
|
||||
boxLeft: Math.min(e.pageX - data.current.rect.x, data.current.mouseDataDown.initialW),
|
||||
boxTop: Math.min(e.pageY - data.current.rect.y, data.current.mouseDataDown.initialH),
|
||||
});
|
||||
};
|
||||
|
||||
const _selectElements = (e) => {
|
||||
const currentItems = [];
|
||||
const _selectbox = selectBoxRef.current;
|
||||
if (!_selectbox) return;
|
||||
registery.forEach((item) => {
|
||||
if (
|
||||
item.domNode &&
|
||||
doObjectsCollide(_selectbox, item.domNode) &&
|
||||
!currentItems.includes(item.key)
|
||||
) {
|
||||
currentItems.push(item.key);
|
||||
}
|
||||
});
|
||||
onSelection(currentItems, e);
|
||||
};
|
||||
|
||||
const _mousedown = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (typeof onSelectionStarted === "function") onSelectionStarted(e);
|
||||
|
||||
window.addEventListener("mouseup", _mouseUp);
|
||||
|
||||
// Right clicks
|
||||
if (e.which === 3 || e.button === 2) return;
|
||||
|
||||
data.current.rect = _getInitialCoords(ref.current);
|
||||
data.current.mouseDataDown = {
|
||||
boxLeft: e.pageX - data.current.rect.x,
|
||||
boxTop: e.pageY - data.current.rect.y,
|
||||
initialW: e.pageX - data.current.rect.x,
|
||||
initialH: e.pageY - data.current.rect.y,
|
||||
};
|
||||
window.addEventListener("mousemove", _drawBox);
|
||||
};
|
||||
|
||||
const _mouseUp = (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
window.removeEventListener("mousemove", _drawBox);
|
||||
window.removeEventListener("mouseup", _mouseUp);
|
||||
|
||||
if (!data.current.mouseDataDown) return;
|
||||
_selectElements(e, true);
|
||||
data.current.mouseDataDown = null;
|
||||
setState({
|
||||
isBoxSelecting: false,
|
||||
boxWidth: 0,
|
||||
boxHeight: 0,
|
||||
});
|
||||
};
|
||||
|
||||
return state;
|
||||
};
|
2
components/core/Selectable/index.js
Normal file
2
components/core/Selectable/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as GroupSelectable } from "./groupSelectable";
|
||||
export { default as Selectable } from "./selectable";
|
26
components/core/Selectable/selectable.js
Normal file
26
components/core/Selectable/selectable.js
Normal file
@ -0,0 +1,26 @@
|
||||
import * as React from "react";
|
||||
import { useSelectable } from "./groupSelectable";
|
||||
|
||||
export default function Selectable({ children, selectableKey, style, ...props }) {
|
||||
const ref = React.useRef();
|
||||
const selectable = useSelectable();
|
||||
React.useEffect(() => {
|
||||
if (selectable) {
|
||||
selectable.register(selectableKey, ref.current);
|
||||
return () => selectable.unregister(selectableKey);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={{
|
||||
cursor: selectable?.enabled ? "default" : "pointer",
|
||||
pointerEvents: selectable?.enabled ? "none" : "auto",
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user