Skip to content

Commit

Permalink
Merge pull request #125 from iddan/refactor/selected-range
Browse files Browse the repository at this point in the history
Replace selected data structure to range from set
  • Loading branch information
iddan committed May 10, 2021
2 parents 4062c13 + 05e32b1 commit 8e52686
Show file tree
Hide file tree
Showing 18 changed files with 3,452 additions and 4,080 deletions.
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
dist/
dist/
coverage/
10 changes: 10 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
import "./index.css";

export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@
"devDependencies": {
"@babel/core": "^7.12.13",
"@babel/plugin-syntax-flow": "^7.12.13",
"@storybook/addon-essentials": "^6.1.17",
"@storybook/addon-links": "^6.1.17",
"@storybook/react": "^6.1.17",
"@storybook/addon-actions": "^6.2.9",
"@storybook/addon-essentials": "^6.2.9",
"@storybook/addon-links": "^6.2.9",
"@storybook/react": "^6.2.9",
"@types/jest": "^26.0.15",
"@types/node": "^14.14.7",
"@types/react": "^16.9.56",
Expand Down
3 changes: 2 additions & 1 deletion src/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classnames from "classnames";
import { connect } from "unistore/react";
import * as PointSet from "./point-set";
import * as PointMap from "./point-map";
import * as PointRange from "./point-range";
import * as Matrix from "./matrix";
import * as Types from "./types";
import * as Actions from "./actions";
Expand Down Expand Up @@ -104,7 +105,7 @@ function mapStateToProps<Data extends Types.CellBase>(

return {
active: cellIsActive,
selected: PointSet.has(selected, point),
selected: selected ? PointRange.has(selected, point) : false,
copied: PointMap.has(point, copied),
mode: cellIsActive ? mode : "view",
data: Matrix.get(row, column, data),
Expand Down
28 changes: 23 additions & 5 deletions src/Selected.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as React from "react";
import { connect } from "unistore/react";
import * as Types from "./types";
import * as PointSet from "./point-set";
import FloatingRect, {
Props as FloatingRectProps,
StateProps,
mapStateToProps as mapStateToFloatingRectProps,
} from "./FloatingRect";
import * as PointRange from "./point-range";
import { getRangeDimensions } from "./util";

type Props = Omit<FloatingRectProps, "variant">;

Expand All @@ -15,11 +15,29 @@ const Selected: React.FC<Props> = (props) => (
);

export default connect<{}, {}, Types.StoreState, StateProps>((state) => {
const cells = state.selected;
const props = mapStateToFloatingRectProps(state, cells);
const props = mapStateToProps(state, state.selected);
return {
...props,
hidden: props.hidden || PointSet.size(cells) === 1,
hidden:
props.hidden ||
Boolean(state.selected && PointRange.size(state.selected) === 1),
dragging: state.dragging,
};
})(Selected);

/** @todo move to floating rect */
function mapStateToProps(
state: Types.StoreState,
range: PointRange.PointRange | null
): Types.Dimensions & { hidden: boolean } {
const dimensions = (range && getRangeDimensions(state, range)) || {
width: 0,
height: 0,
left: 0,
top: 0,
};
return {
...dimensions,
hidden: !range,
};
}
13 changes: 3 additions & 10 deletions src/Spreadsheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import {
readTextFromClipboard,
writeTextToClipboard,
getComputedValue,
getSelectedCSV,
} from "./util";
import * as PointSet from "./point-set";
import * as Matrix from "./matrix";
import * as Actions from "./actions";
import "./Spreadsheet.css";
Expand Down Expand Up @@ -78,17 +78,10 @@ class Spreadsheet<CellType extends Types.CellBase> extends React.PureComponent<
> {
formulaParser = this.props.formulaParser || new FormulaParser();

clip = (event: ClipboardEvent) => {
clip = (event: ClipboardEvent): void => {
const { store } = this.props;
const { data, selected } = store.getState();
const startPoint = PointSet.min(selected);
const endPoint = PointSet.max(selected);
const slicedMatrix = Matrix.slice(startPoint, endPoint, data);
const valueMatrix = Matrix.map(
(cell): string => (cell && cell.value) || "",
slicedMatrix
);
const csv = Matrix.join(valueMatrix);
const csv = getSelectedCSV(selected, data);
writeTextToClipboard(event, csv);
};

Expand Down
9 changes: 6 additions & 3 deletions src/SpreadsheetStateProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import createStore, { Store } from "unistore";
import devtools from "unistore/devtools";
import { Provider } from "unistore/react";
import * as Types from "./types";
import * as PointSet from "./point-set";
import * as PointRange from "./point-range";
import * as Actions from "./actions";
import * as PointMap from "./point-map";
import * as Matrix from "./matrix";
Expand Down Expand Up @@ -69,7 +69,7 @@ export default class SpreadsheetStateProvider<
const state: Types.StoreState<CellType> = {
...INITIAL_STATE,
data: this.props.data,
selected: PointSet.from([]),
selected: null,
copied: PointMap.from([]),
bindings: PointMap.from([]),
lastCommit: null,
Expand Down Expand Up @@ -106,7 +106,10 @@ export default class SpreadsheetStateProvider<
onModeChange(state.mode);
}
if (state.selected !== prevState.selected) {
onSelect(PointSet.toArray(state.selected));
const points = state.selected
? Array.from(PointRange.iterate(state.selected))
: [];
onSelect(points);
}
if (state.active !== prevState.active && state.active) {
onActivate(state.active);
Expand Down
114 changes: 61 additions & 53 deletions src/actions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import * as PointSet from "./point-set";
import * as PointMap from "./point-map";
import * as PointRange from "./point-range";
import * as Matrix from "./matrix";
import * as Types from "./types";
import { isActive, updateData } from "./util";
import { isActive, normalizeSelected, updateData } from "./util";

enum Direction {
Left = "Left",
Right = "Right",
Top = "Top",
Down = "Down",
}

export const setData = <Cell extends Types.CellBase>(
state: Types.StoreState<Cell>,
Expand All @@ -12,10 +20,7 @@ export const setData = <Cell extends Types.CellBase>(
state.active && Matrix.has(state.active.row, state.active.column, data)
? state.active
: null;
const nextSelected = PointSet.filter(
(point) => Matrix.has(point.row, point.column, data),
state.selected
);
const nextSelected = normalizeSelected(state.selected, data);
const nextBindings = PointMap.map(
(bindings) =>
PointSet.filter(
Expand All @@ -41,12 +46,7 @@ export const select = (
): Partial<Types.StoreState> | null => {
if (state.active && !isActive(state.active, cellPointer)) {
return {
selected: PointSet.from(
Matrix.inclusiveRange(
{ row: cellPointer.row, column: cellPointer.column },
{ row: state.active.row, column: state.active.column }
)
),
selected: PointRange.create(cellPointer, state.active),
mode: "view",
};
}
Expand All @@ -57,7 +57,7 @@ export const activate = (
state: Types.StoreState,
cellPointer: Types.Point
): Partial<Types.StoreState> | null => ({
selected: PointSet.from([cellPointer]),
selected: PointRange.create(cellPointer, cellPointer),
active: cellPointer,
mode: isActive(state.active, cellPointer) ? "edit" : "view",
});
Expand Down Expand Up @@ -114,15 +114,14 @@ export function setCellDimensions(
export function copy<Cell extends Types.CellBase>(
state: Types.StoreState<Cell>
): Partial<Types.StoreState<Cell>> {
const selectedPoints = state.selected
? Array.from(PointRange.iterate(state.selected))
: [];
return {
copied: PointSet.reduce(
(acc, point) => {
const value = Matrix.get(point.row, point.column, state.data);
return value === undefined ? acc : PointMap.set(point, value, acc);
},
state.selected,
PointMap.from<Cell>([])
),
copied: selectedPoints.reduce((acc, point) => {
const value = Matrix.get(point.row, point.column, state.data);
return value === undefined ? acc : PointMap.set(point, value, acc);
}, PointMap.from<Cell>([])),
cut: false,
hasPasted: false,
};
Expand Down Expand Up @@ -150,14 +149,14 @@ export async function paste<Cell extends Types.CellBase>(

type Accumulator = {
data: typeof state.data;
selected: typeof state.selected;
commit: typeof state.lastCommit;
};

const requiredRows = active.row + Matrix.getSize(copiedMatrix).rows;
const copiedSize = Matrix.getSize(copiedMatrix);
const requiredRows = active.row + copiedSize.rows;
const paddedData = Matrix.padRows(state.data, requiredRows);

const { data, selected, commit } = PointMap.reduce(
const { data, commit } = PointMap.reduce(
(acc: Accumulator, value, { row, column }): Accumulator => {
let commit = acc.commit || [];
const nextRow = row - minPoint.row + active.row;
Expand All @@ -172,7 +171,7 @@ export async function paste<Cell extends Types.CellBase>(
}

if (!Matrix.has(nextRow, nextColumn, paddedData)) {
return { data: nextData, selected: acc.selected, commit };
return { data: nextData, commit };
}
const currentValue = Matrix.get(nextRow, nextColumn, nextData) || null;

Expand All @@ -191,20 +190,18 @@ export async function paste<Cell extends Types.CellBase>(
{ ...currentValue, ...value },
nextData
),
selected: PointSet.add(acc.selected, {
row: nextRow,
column: nextColumn,
}),

commit,
};
},
copied,
{ data: paddedData, selected: PointSet.from([]), commit: [] }
{ data: paddedData, commit: [] }
);
return {
data,
selected,
selected: PointRange.create(active, {
row: active.row + copiedSize.rows - 1,
column: active.column + copiedSize.columns - 1,
}),
cut: false,
hasPasted: true,
mode: "view",
Expand All @@ -231,21 +228,23 @@ export const clear = <Cell extends Types.CellBase>(
if (!state.active) {
return null;
}
const changes = PointSet.toArray(state.selected).map((point) => {
const selectedPoints = state.selected
? Array.from(PointRange.iterate(state.selected))
: [];
const changes = selectedPoints.map((point) => {
const cell = Matrix.get(point.row, point.column, state.data);
return {
prevCell: cell || null,
nextCell: null,
};
});
return {
data: PointSet.reduce(
data: selectedPoints.reduce(
(acc, point) =>
updateData<Cell>(acc, {
...point,
data: undefined,
}),
state.selected,
state.data
),
...commit(state, changes),
Expand All @@ -272,35 +271,44 @@ export const go = (rowDelta: number, columnDelta: number): KeyDownHandler => (
}
return {
active: nextActive,
selected: PointSet.from([nextActive]),
selected: PointRange.create(nextActive, nextActive),
mode: "view",
};
};

export const modifyEdge = (field: keyof Types.Point, delta: number) => (
state: Types.StoreState,
event: unknown
export const modifyEdge = (edge: Direction) => (
state: Types.StoreState
): Partial<Types.StoreState> | null => {
const { active } = state;
if (!active) {
const { active, selected } = state;

if (!active || !selected) {
return null;
}

const edgeOffsets = PointSet.has(state.selected, {
...active,
const field =
edge === Direction.Left || edge === Direction.Right ? "column" : "row";

const key =
edge === Direction.Left || edge === Direction.Top ? "start" : "end";
const delta = key === "start" ? -1 : 1;

const edgeOffsets = PointRange.has(selected, {
...active,
[field]: active[field] + delta * -1,
});

const nextSelected = edgeOffsets
? PointSet.shrinkEdge(state.selected, field, delta * -1)
: PointSet.extendEdge(state.selected, field, delta);
const keyToModify = edgeOffsets ? (key === "start" ? "end" : "start") : key;

const nextSelected = {
...selected,
[keyToModify]: {
...selected[keyToModify],
[field]: selected[keyToModify][field] + delta,
},
};

return {
selected: PointSet.filter(
(point) => Matrix.has(point.row, point.column, state.data),
nextSelected
),
selected: normalizeSelected(nextSelected, state.data),
};
};

Expand Down Expand Up @@ -337,10 +345,10 @@ const editShiftKeyDownHandlers: KeyDownHandlers = {
};

const shiftKeyDownHandlers: KeyDownHandlers = {
ArrowUp: modifyEdge("row", -1),
ArrowDown: modifyEdge("row", 1),
ArrowLeft: modifyEdge("column", -1),
ArrowRight: modifyEdge("column", 1),
ArrowUp: modifyEdge(Direction.Top),
ArrowDown: modifyEdge(Direction.Down),
ArrowLeft: modifyEdge(Direction.Left),
ArrowRight: modifyEdge(Direction.Right),
Tab: go(0, -1),
};

Expand Down

0 comments on commit 8e52686

Please sign in to comment.