mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Scrollbars (#602)
This commit is contained in:
parent
2791866a9e
commit
50de05cee4
10 changed files with 297 additions and 56 deletions
|
@ -90,10 +90,6 @@ $border-radius: 0.25rem;
|
|||
overflow: visible;
|
||||
}
|
||||
|
||||
.with-eye-dropper {
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
.mode-selector {
|
||||
display: flex;
|
||||
margin-right: calc(2 * $grid-unit);
|
||||
|
|
|
@ -5,6 +5,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import PaperCanvas from '../../containers/paper-canvas.jsx';
|
||||
import ScrollableCanvas from '../../containers/scrollable-canvas.jsx';
|
||||
|
||||
import BitBrushMode from '../../containers/bit-brush-mode.jsx';
|
||||
import BitLineMode from '../../containers/bit-line-mode.jsx';
|
||||
|
@ -200,11 +201,10 @@ const PaintEditorComponent = props => (
|
|||
|
||||
<div>
|
||||
{/* Canvas */}
|
||||
<div
|
||||
className={classNames(
|
||||
styles.canvasContainer,
|
||||
{[styles.withEyeDropper]: props.isEyeDropping}
|
||||
)}
|
||||
<ScrollableCanvas
|
||||
canvas={props.canvas}
|
||||
hideCursor={props.isEyeDropping}
|
||||
style={styles.canvasContainer}
|
||||
>
|
||||
<PaperCanvas
|
||||
canvasRef={props.setCanvas}
|
||||
|
@ -231,7 +231,7 @@ const PaintEditorComponent = props => (
|
|||
</Box>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
</ScrollableCanvas>
|
||||
<div className={styles.canvasControls}>
|
||||
{isVector(props.format) ?
|
||||
<Button
|
||||
|
|
36
src/components/scrollable-canvas/scrollable-canvas.css
Normal file
36
src/components/scrollable-canvas/scrollable-canvas.css
Normal file
|
@ -0,0 +1,36 @@
|
|||
$scrollbar-size: 6px;
|
||||
$scrollbar-padding: 1px;
|
||||
|
||||
.vertical-scrollbar, .horizontal-scrollbar {
|
||||
position: absolute;
|
||||
background: #BEBEBECD;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.vertical-scrollbar-wrapper {
|
||||
position: absolute;
|
||||
width: $scrollbar-size;
|
||||
right: $scrollbar-padding;
|
||||
top: $scrollbar-padding;
|
||||
height: calc(100% - $scrollbar-size - $scrollbar-padding);
|
||||
}
|
||||
|
||||
.horizontal-scrollbar-wrapper {
|
||||
position: absolute;
|
||||
height: $scrollbar-size;
|
||||
left: $scrollbar-padding;
|
||||
bottom: $scrollbar-padding;
|
||||
width: calc(100% - $scrollbar-size - $scrollbar-padding);
|
||||
}
|
||||
|
||||
.vertical-scrollbar {
|
||||
width: $scrollbar-size;
|
||||
}
|
||||
|
||||
.horizontal-scrollbar {
|
||||
height: $scrollbar-size;
|
||||
}
|
||||
|
||||
.hide-cursor {
|
||||
cursor: none;
|
||||
}
|
54
src/components/scrollable-canvas/scrollable-canvas.jsx
Normal file
54
src/components/scrollable-canvas/scrollable-canvas.jsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './scrollable-canvas.css';
|
||||
|
||||
const ScrollableCanvasComponent = props => (
|
||||
<div
|
||||
className={classNames(
|
||||
props.style,
|
||||
{[styles.hideCursor]: props.hideCursor}
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
<div className={styles.horizontalScrollbarWrapper}>
|
||||
<div
|
||||
className={styles.horizontalScrollbar}
|
||||
style={{
|
||||
width: `${props.horizontalScrollLengthPercent}%`,
|
||||
left: `${props.horizontalScrollStartPercent}%`,
|
||||
visibility: `${props.hideCursor ||
|
||||
Math.abs(props.horizontalScrollLengthPercent - 100) < 1e-8 ? 'hidden' : 'visible'}`
|
||||
}}
|
||||
onMouseDown={props.onHorizontalScrollbarMouseDown}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.verticalScrollbarWrapper}>
|
||||
<div
|
||||
className={styles.verticalScrollbar}
|
||||
style={{
|
||||
height: `${props.verticalScrollLengthPercent}%`,
|
||||
top: `${props.verticalScrollStartPercent}%`,
|
||||
visibility: `${props.hideCursor ||
|
||||
Math.abs(props.verticalScrollLengthPercent - 100) < 1e-8 ? 'hidden' : 'visible'}`
|
||||
}}
|
||||
onMouseDown={props.onVerticalScrollbarMouseDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
ScrollableCanvasComponent.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
hideCursor: PropTypes.bool,
|
||||
horizontalScrollLengthPercent: PropTypes.number,
|
||||
horizontalScrollStartPercent: PropTypes.number,
|
||||
onHorizontalScrollbarMouseDown: PropTypes.func.isRequired,
|
||||
onVerticalScrollbarMouseDown: PropTypes.func.isRequired,
|
||||
style: PropTypes.string,
|
||||
verticalScrollLengthPercent: PropTypes.number,
|
||||
verticalScrollStartPercent: PropTypes.number
|
||||
};
|
||||
|
||||
export default ScrollableCanvasComponent;
|
|
@ -100,8 +100,11 @@ class PaintEditor extends React.Component {
|
|||
this.startEyeDroppingLoop();
|
||||
} else if (!this.props.isEyeDropping && prevProps.isEyeDropping) {
|
||||
this.stopEyeDroppingLoop();
|
||||
} else if (this.props.isEyeDropping && this.props.viewBounds !== prevProps.viewBounds) {
|
||||
this.props.previousTool.activate();
|
||||
this.props.onDeactivateEyeDropper();
|
||||
this.stopEyeDroppingLoop();
|
||||
}
|
||||
|
||||
if (this.props.format === Formats.VECTOR && isBitmap(prevProps.format)) {
|
||||
this.isSwitchingFormats = false;
|
||||
convertToVector(this.props.clearSelectedItems, this.handleUpdateImage);
|
||||
|
@ -329,7 +332,6 @@ class PaintEditor extends React.Component {
|
|||
this.props.previousTool.activate();
|
||||
this.props.onDeactivateEyeDropper();
|
||||
this.stopEyeDroppingLoop();
|
||||
this.setState({colorInfo: null});
|
||||
}
|
||||
}
|
||||
startEyeDroppingLoop () {
|
||||
|
@ -367,6 +369,7 @@ class PaintEditor extends React.Component {
|
|||
}
|
||||
stopEyeDroppingLoop () {
|
||||
clearInterval(this.intervalId);
|
||||
this.setState({colorInfo: null});
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
|
@ -442,7 +445,8 @@ PaintEditor.propTypes = {
|
|||
stack: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
pointer: PropTypes.number.isRequired
|
||||
}),
|
||||
updateViewBounds: PropTypes.func.isRequired
|
||||
updateViewBounds: PropTypes.func.isRequired,
|
||||
viewBounds: PropTypes.instanceOf(paper.Matrix).isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
@ -455,7 +459,8 @@ const mapStateToProps = state => ({
|
|||
previousTool: state.scratchPaint.color.eyeDropper.previousTool,
|
||||
selectedItems: state.scratchPaint.selectedItems,
|
||||
textEditing: state.scratchPaint.textEditTarget !== null,
|
||||
undoState: state.scratchPaint.undo
|
||||
undoState: state.scratchPaint.undo,
|
||||
viewBounds: state.scratchPaint.viewBounds
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onKeyPress: event => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.paper-canvas {
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
margin: auto;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
|
|
@ -14,13 +14,12 @@ import {isGroup, ungroupItems} from '../helper/group';
|
|||
import {clearRaster, getRaster, setupLayers} from '../helper/layer';
|
||||
import {deleteSelection, getSelectedLeafItems} from '../helper/selection';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, pan, resetZoom, zoomOnFixedPoint} from '../helper/view';
|
||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, resetZoom} from '../helper/view';
|
||||
import {ensureClockwise, scaleWithStrokes} from '../helper/math';
|
||||
import {clearHoveredItem} from '../reducers/hover';
|
||||
import {clearPasteOffset} from '../reducers/clipboard';
|
||||
import {updateViewBounds} from '../reducers/view-bounds';
|
||||
import {changeFormat} from '../reducers/format';
|
||||
|
||||
import {updateViewBounds} from '../reducers/view-bounds';
|
||||
import styles from './paper-canvas.css';
|
||||
|
||||
class PaperCanvas extends React.Component {
|
||||
|
@ -30,7 +29,6 @@ class PaperCanvas extends React.Component {
|
|||
'setCanvas',
|
||||
'importSvg',
|
||||
'handleKeyDown',
|
||||
'handleWheel',
|
||||
'switchCostume'
|
||||
]);
|
||||
}
|
||||
|
@ -214,38 +212,6 @@ class PaperCanvas extends React.Component {
|
|||
this.props.canvasRef(canvas);
|
||||
}
|
||||
}
|
||||
handleWheel (event) {
|
||||
// Multiplier variable, so that non-pixel-deltaModes are supported. Needed for Firefox.
|
||||
// See #529 (or LLK/scratch-blocks#1190).
|
||||
const multiplier = event.deltaMode === 0x1 ? 15 : 1;
|
||||
const deltaX = event.deltaX * multiplier;
|
||||
const deltaY = event.deltaY * multiplier;
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
// Zoom keeping mouse location fixed
|
||||
const canvasRect = this.canvas.getBoundingClientRect();
|
||||
const offsetX = event.clientX - canvasRect.left;
|
||||
const offsetY = event.clientY - canvasRect.top;
|
||||
const fixedPoint = paper.project.view.viewToProject(
|
||||
new paper.Point(offsetX, offsetY)
|
||||
);
|
||||
zoomOnFixedPoint(-deltaY / 100, fixedPoint);
|
||||
this.props.updateViewBounds(paper.view.matrix);
|
||||
this.props.setSelectedItems(this.props.format);
|
||||
} else if (event.shiftKey && event.deltaX === 0) {
|
||||
// Scroll horizontally (based on vertical scroll delta)
|
||||
// This is needed as for some browser/system combinations which do not set deltaX.
|
||||
// See #156.
|
||||
const dx = deltaY / paper.project.view.zoom;
|
||||
pan(dx, 0);
|
||||
this.props.updateViewBounds(paper.view.matrix);
|
||||
} else {
|
||||
const dx = deltaX / paper.project.view.zoom;
|
||||
const dy = deltaY / paper.project.view.zoom;
|
||||
pan(dx, dy);
|
||||
this.props.updateViewBounds(paper.view.matrix);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<canvas
|
||||
|
@ -253,7 +219,6 @@ class PaperCanvas extends React.Component {
|
|||
height="360px"
|
||||
ref={this.setCanvas}
|
||||
width="480px"
|
||||
onWheel={this.handleWheel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
174
src/containers/scrollable-canvas.jsx
Normal file
174
src/containers/scrollable-canvas.jsx
Normal file
|
@ -0,0 +1,174 @@
|
|||
import paper from '@scratch/paper';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import ScrollableCanvasComponent from '../components/scrollable-canvas/scrollable-canvas.jsx';
|
||||
|
||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, clampViewBounds, pan, zoomOnFixedPoint} from '../helper/view';
|
||||
import {updateViewBounds} from '../reducers/view-bounds';
|
||||
import {redrawSelectionBox} from '../reducers/selected-items';
|
||||
|
||||
import {getEventXY} from '../lib/touch-utils';
|
||||
import bindAll from 'lodash.bindall';
|
||||
|
||||
class ScrollableCanvas extends React.Component {
|
||||
static get ZOOM_INCREMENT () {
|
||||
return 0.5;
|
||||
}
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleHorizontalScrollbarMouseDown',
|
||||
'handleHorizontalScrollbarMouseMove',
|
||||
'handleHorizontalScrollbarMouseUp',
|
||||
'handleVerticalScrollbarMouseDown',
|
||||
'handleVerticalScrollbarMouseMove',
|
||||
'handleVerticalScrollbarMouseUp',
|
||||
'handleWheel'
|
||||
]);
|
||||
}
|
||||
componentDidMount () {
|
||||
if (this.props.canvas) {
|
||||
this.props.canvas.addEventListener('wheel', this.handleWheel);
|
||||
}
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.canvas) {
|
||||
if (this.props.canvas) {
|
||||
this.props.canvas.removeEventListener('wheel', this.handleWheel);
|
||||
}
|
||||
nextProps.canvas.addEventListener('wheel', this.handleWheel);
|
||||
}
|
||||
}
|
||||
handleHorizontalScrollbarMouseDown (event) {
|
||||
this.initialMouseX = getEventXY(event).x;
|
||||
this.initialScreenX = paper.view.matrix.tx;
|
||||
window.addEventListener('mousemove', this.handleHorizontalScrollbarMouseMove);
|
||||
window.addEventListener('mouseup', this.handleHorizontalScrollbarMouseUp);
|
||||
event.preventDefault();
|
||||
}
|
||||
handleHorizontalScrollbarMouseMove (event) {
|
||||
const dx = this.initialMouseX - getEventXY(event).x;
|
||||
paper.view.matrix.tx = this.initialScreenX + (dx * paper.view.zoom * 2);
|
||||
clampViewBounds();
|
||||
this.props.updateViewBounds(paper.view.matrix);
|
||||
event.preventDefault();
|
||||
}
|
||||
handleHorizontalScrollbarMouseUp () {
|
||||
window.removeEventListener('mousemove', this.handleHorizontalScrollbarMouseMove);
|
||||
window.removeEventListener('mouseup', this.handleHorizontalScrollbarMouseUp);
|
||||
this.initialMouseX = null;
|
||||
this.initialScreenX = null;
|
||||
event.preventDefault();
|
||||
}
|
||||
handleVerticalScrollbarMouseDown (event) {
|
||||
this.initialMouseY = getEventXY(event).y;
|
||||
this.initialScreenY = paper.view.matrix.ty;
|
||||
window.addEventListener('mousemove', this.handleVerticalScrollbarMouseMove);
|
||||
window.addEventListener('mouseup', this.handleVerticalScrollbarMouseUp);
|
||||
event.preventDefault();
|
||||
}
|
||||
handleVerticalScrollbarMouseMove (event) {
|
||||
const dy = this.initialMouseY - getEventXY(event).y;
|
||||
paper.view.matrix.ty = this.initialScreenY + (dy * paper.view.zoom * 2);
|
||||
clampViewBounds();
|
||||
this.props.updateViewBounds(paper.view.matrix);
|
||||
event.preventDefault();
|
||||
}
|
||||
handleVerticalScrollbarMouseUp (event) {
|
||||
window.removeEventListener('mousemove', this.handleVerticalScrollbarMouseMove);
|
||||
window.removeEventListener('mouseup', this.handleVerticalScrollbarMouseUp);
|
||||
this.initialMouseY = null;
|
||||
this.initialScreenY = null;
|
||||
event.preventDefault();
|
||||
}
|
||||
handleWheel (event) {
|
||||
// Multiplier variable, so that non-pixel-deltaModes are supported. Needed for Firefox.
|
||||
// See #529 (or LLK/scratch-blocks#1190).
|
||||
const multiplier = event.deltaMode === 0x1 ? 15 : 1;
|
||||
const deltaX = event.deltaX * multiplier;
|
||||
const deltaY = event.deltaY * multiplier;
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
// Zoom keeping mouse location fixed
|
||||
const canvasRect = this.props.canvas.getBoundingClientRect();
|
||||
const offsetX = event.clientX - canvasRect.left;
|
||||
const offsetY = event.clientY - canvasRect.top;
|
||||
const fixedPoint = paper.view.viewToProject(
|
||||
new paper.Point(offsetX, offsetY)
|
||||
);
|
||||
zoomOnFixedPoint(-deltaY / 1000, fixedPoint);
|
||||
this.props.updateViewBounds(paper.view.matrix);
|
||||
this.props.redrawSelectionBox(); // Selection handles need to be resized after zoom
|
||||
} else if (event.shiftKey && event.deltaX === 0) {
|
||||
// Scroll horizontally (based on vertical scroll delta)
|
||||
// This is needed as for some browser/system combinations which do not set deltaX.
|
||||
// See #156.
|
||||
const dx = deltaY / paper.view.zoom;
|
||||
pan(dx, 0);
|
||||
this.props.updateViewBounds(paper.view.matrix);
|
||||
} else {
|
||||
const dx = deltaX / paper.view.zoom;
|
||||
const dy = deltaY / paper.view.zoom;
|
||||
pan(dx, dy);
|
||||
this.props.updateViewBounds(paper.view.matrix);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
render () {
|
||||
let widthPercent = 0;
|
||||
let heightPercent = 0;
|
||||
let topPercent = 0;
|
||||
let leftPercent = 0;
|
||||
if (paper.project) {
|
||||
const {x, y, width, height} = paper.view.bounds;
|
||||
widthPercent = Math.min(100, 100 * width / ART_BOARD_WIDTH);
|
||||
heightPercent = Math.min(100, 100 * height / ART_BOARD_HEIGHT);
|
||||
const centerX = (x + (width / 2)) / ART_BOARD_WIDTH;
|
||||
const centerY = (y + (height / 2)) / ART_BOARD_HEIGHT;
|
||||
topPercent = Math.max(0, (100 * centerY) - (heightPercent / 2));
|
||||
leftPercent = Math.max(0, (100 * centerX) - (widthPercent / 2));
|
||||
}
|
||||
return (
|
||||
<ScrollableCanvasComponent
|
||||
hideCursor={this.props.hideCursor}
|
||||
horizontalScrollLengthPercent={widthPercent}
|
||||
horizontalScrollStartPercent={leftPercent}
|
||||
style={this.props.style}
|
||||
verticalScrollLengthPercent={heightPercent}
|
||||
verticalScrollStartPercent={topPercent}
|
||||
onHorizontalScrollbarMouseDown={this.handleHorizontalScrollbarMouseDown}
|
||||
onVerticalScrollbarMouseDown={this.handleVerticalScrollbarMouseDown}
|
||||
>
|
||||
{this.props.children}
|
||||
</ScrollableCanvasComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ScrollableCanvas.propTypes = {
|
||||
canvas: PropTypes.instanceOf(Element),
|
||||
children: PropTypes.node.isRequired,
|
||||
hideCursor: PropTypes.bool,
|
||||
redrawSelectionBox: PropTypes.func.isRequired,
|
||||
style: PropTypes.string,
|
||||
updateViewBounds: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
viewBounds: state.scratchPaint.viewBounds
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
redrawSelectionBox: () => {
|
||||
dispatch(redrawSelectionBox());
|
||||
},
|
||||
updateViewBounds: matrix => {
|
||||
dispatch(updateViewBounds(matrix));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScrollableCanvas);
|
|
@ -9,7 +9,7 @@ const SVG_ART_BOARD_HEIGHT = 360;
|
|||
const ART_BOARD_WIDTH = 480 * 2;
|
||||
const ART_BOARD_HEIGHT = 360 * 2;
|
||||
|
||||
const _clampViewBounds = () => {
|
||||
const clampViewBounds = () => {
|
||||
const {left, right, top, bottom} = paper.project.view.bounds;
|
||||
if (left < 0) {
|
||||
paper.project.view.scrollBy(new paper.Point(-left, 0));
|
||||
|
@ -37,7 +37,7 @@ const zoomOnFixedPoint = (deltaZoom, fixedPoint) => {
|
|||
.subtract(preZoomCenter);
|
||||
view.zoom = newZoom;
|
||||
view.translate(postZoomOffset.multiply(-1));
|
||||
_clampViewBounds();
|
||||
clampViewBounds();
|
||||
};
|
||||
|
||||
// Zoom keeping the selection center (if any) fixed.
|
||||
|
@ -62,12 +62,12 @@ const zoomOnSelection = deltaZoom => {
|
|||
|
||||
const resetZoom = () => {
|
||||
paper.project.view.zoom = .5;
|
||||
_clampViewBounds();
|
||||
clampViewBounds();
|
||||
};
|
||||
|
||||
const pan = (dx, dy) => {
|
||||
paper.project.view.scrollBy(new paper.Point(dx, dy));
|
||||
_clampViewBounds();
|
||||
clampViewBounds();
|
||||
};
|
||||
|
||||
export {
|
||||
|
@ -75,6 +75,7 @@ export {
|
|||
ART_BOARD_WIDTH,
|
||||
SVG_ART_BOARD_WIDTH,
|
||||
SVG_ART_BOARD_HEIGHT,
|
||||
clampViewBounds,
|
||||
pan,
|
||||
resetZoom,
|
||||
zoomOnSelection,
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import log from '../log/log';
|
||||
const CHANGE_SELECTED_ITEMS = 'scratch-paint/select/CHANGE_SELECTED_ITEMS';
|
||||
const REDRAW_SELECTION_BOX = 'scratch-paint/select/REDRAW_SELECTION_BOX';
|
||||
const initialState = [];
|
||||
|
||||
const reducer = function (state, action) {
|
||||
if (typeof state === 'undefined') state = initialState;
|
||||
switch (action.type) {
|
||||
case REDRAW_SELECTION_BOX:
|
||||
if (state.length > 0) return state.slice(0); // Sends an update even though the items haven't changed
|
||||
return state;
|
||||
case CHANGE_SELECTED_ITEMS:
|
||||
if (!action.selectedItems || !(action.selectedItems instanceof Array)) {
|
||||
log.warn(`No selected items or wrong format provided: ${action.selectedItems}`);
|
||||
|
@ -44,9 +48,15 @@ const clearSelectedItems = function () {
|
|||
selectedItems: []
|
||||
};
|
||||
};
|
||||
const redrawSelectionBox = function () {
|
||||
return {
|
||||
type: REDRAW_SELECTION_BOX
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
reducer as default,
|
||||
redrawSelectionBox,
|
||||
setSelectedItems,
|
||||
clearSelectedItems,
|
||||
CHANGE_SELECTED_ITEMS
|
||||
|
|
Loading…
Reference in a new issue