react-editable-photo-grid 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +3 -0
- package/LICENSE +675 -0
- package/README.md +1 -0
- package/dist/index.js +1 -0
- package/package.json +49 -0
- package/src/PhotoGrid.tsx +94 -0
- package/src/components/PhotoControls.tsx +65 -0
- package/src/components/RowControls.tsx +35 -0
- package/src/index.js +3 -0
- package/src/styles.css +89 -0
- package/src/types.ts +48 -0
- package/src/utils.tsx +366 -0
- package/tsconfig.json +18 -0
- package/webpack.config.js +39 -0
package/src/utils.tsx
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { PhotoItem, PhotoRows, PhotoIdAndRowKey, PhotoGridProps } from './types';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sorts the photos in a row by column number
|
|
6
|
+
* @param row
|
|
7
|
+
*/
|
|
8
|
+
export const sortRow = (row: PhotoItem[]) => {
|
|
9
|
+
return row.sort((a, b) => Math.floor(a.column) - Math.floor(b.column))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get a photo from a row via id
|
|
14
|
+
* @param row
|
|
15
|
+
* @param id
|
|
16
|
+
*/
|
|
17
|
+
const getPhotoForId = (row: PhotoItem[], id: string): PhotoItem => {
|
|
18
|
+
const photoResult = row.find(photo => photo.id === id);
|
|
19
|
+
|
|
20
|
+
if (photoResult === undefined) {
|
|
21
|
+
throw new TypeError('No photo was found in this row for that id');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return photoResult;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get a photo from a row by column number
|
|
29
|
+
* @param row
|
|
30
|
+
* @param column
|
|
31
|
+
*/
|
|
32
|
+
const getPhotoForColumn = (row: PhotoItem[], column: number): PhotoItem => {
|
|
33
|
+
const photoResult = row.find(photo => photo.column === column);
|
|
34
|
+
|
|
35
|
+
if (photoResult === undefined) {
|
|
36
|
+
throw new TypeError('No photo was found in this row for that column');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return photoResult;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Deletes a photo from a row by column number
|
|
44
|
+
* @param row
|
|
45
|
+
* @param column
|
|
46
|
+
*/
|
|
47
|
+
const deletePhotoFromRowByColumn = (row: PhotoItem[], column: number): PhotoItem[] => {
|
|
48
|
+
return row.filter((photo: PhotoItem) => photo.column !== column);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Adds a photo to a row
|
|
53
|
+
* @param row
|
|
54
|
+
* @param photo
|
|
55
|
+
* @param column
|
|
56
|
+
*/
|
|
57
|
+
const addPhotoToRow = (row: PhotoItem[] = [], photo: PhotoItem, column: number): PhotoItem[] => {
|
|
58
|
+
photo.column = column;
|
|
59
|
+
row.push(photo);
|
|
60
|
+
return row;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Swaps two photos around in the row
|
|
65
|
+
* @param row
|
|
66
|
+
* @param firstPhoto
|
|
67
|
+
* @param secondPhoto
|
|
68
|
+
*/
|
|
69
|
+
const swapPhotosAround = (
|
|
70
|
+
row: PhotoItem[],
|
|
71
|
+
firstPhoto: PhotoItem,
|
|
72
|
+
secondPhoto: PhotoItem): PhotoItem[] => {
|
|
73
|
+
const firstColumn = firstPhoto.column,
|
|
74
|
+
secondColumn = secondPhoto.column;
|
|
75
|
+
|
|
76
|
+
row = deletePhotoFromRowByColumn(row, firstColumn);
|
|
77
|
+
row = deletePhotoFromRowByColumn(row, secondColumn);
|
|
78
|
+
row = addPhotoToRow(row, firstPhoto, secondColumn);
|
|
79
|
+
row = addPhotoToRow(row, secondPhoto, firstColumn);
|
|
80
|
+
|
|
81
|
+
return row;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Casts a the rowKey to a number if necessary
|
|
86
|
+
* @param rowKey
|
|
87
|
+
*/
|
|
88
|
+
export const castRowKey = (rowKey: string | number): number => {
|
|
89
|
+
if (typeof rowKey == 'string') {
|
|
90
|
+
rowKey = parseInt(rowKey);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return rowKey;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Extract the row key and photo id from the clicked element
|
|
98
|
+
* @param e
|
|
99
|
+
*/
|
|
100
|
+
const getPhotoIdAndRowKey = (e: React.MouseEvent<HTMLButtonElement>): PhotoIdAndRowKey => {
|
|
101
|
+
const button = e.currentTarget as HTMLButtonElement,
|
|
102
|
+
id = button.dataset.id,
|
|
103
|
+
rowKey = button.dataset.row;
|
|
104
|
+
|
|
105
|
+
if (!id || !rowKey) {
|
|
106
|
+
throw new TypeError('id or row key missing from photo control');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
id: id,
|
|
111
|
+
rowKey: parseInt(rowKey)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Moves a photo one column to the left
|
|
117
|
+
* @param e
|
|
118
|
+
* @param props
|
|
119
|
+
*/
|
|
120
|
+
export const movePhotoLeft = (e: React.MouseEvent<HTMLButtonElement>, props: PhotoGridProps) => {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
|
|
123
|
+
const { id, rowKey } = getPhotoIdAndRowKey(e);
|
|
124
|
+
|
|
125
|
+
let rowsCopy = { ...props.rows },
|
|
126
|
+
thisRow = sortRow(rowsCopy[rowKey]);
|
|
127
|
+
|
|
128
|
+
const thisPhoto = getPhotoForId(thisRow, id);
|
|
129
|
+
|
|
130
|
+
if (thisPhoto.column === 1) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const beforePhoto = getPhotoForColumn(thisRow, thisPhoto.column - 1);
|
|
135
|
+
thisRow = swapPhotosAround(thisRow, beforePhoto, thisPhoto);
|
|
136
|
+
|
|
137
|
+
delete rowsCopy[rowKey];
|
|
138
|
+
rowsCopy[rowKey] = thisRow;
|
|
139
|
+
|
|
140
|
+
props.updateRows(rowsCopy);
|
|
141
|
+
props.increaseChanges();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Move a photo one column to the right
|
|
146
|
+
* @param e
|
|
147
|
+
* @param props
|
|
148
|
+
*/
|
|
149
|
+
export const movePhotoRight = (e: React.MouseEvent<HTMLButtonElement>, props: PhotoGridProps) => {
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
|
|
152
|
+
const { id, rowKey } = getPhotoIdAndRowKey(e);
|
|
153
|
+
|
|
154
|
+
let rowsCopy = { ...props.rows },
|
|
155
|
+
thisRow = sortRow(rowsCopy[rowKey]);
|
|
156
|
+
|
|
157
|
+
const thisPhoto = getPhotoForId(thisRow, id);
|
|
158
|
+
|
|
159
|
+
if (thisPhoto.column === thisRow.length) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const afterPhoto = getPhotoForColumn(thisRow, thisPhoto.column + 1);
|
|
164
|
+
thisRow = swapPhotosAround(thisRow, thisPhoto, afterPhoto);
|
|
165
|
+
|
|
166
|
+
delete rowsCopy[rowKey];
|
|
167
|
+
rowsCopy[rowKey] = thisRow;
|
|
168
|
+
|
|
169
|
+
props.updateRows(rowsCopy);
|
|
170
|
+
props.increaseChanges();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* All photos in the row move back a column (to the left)
|
|
175
|
+
* @param row
|
|
176
|
+
* @param start
|
|
177
|
+
* @param end
|
|
178
|
+
*/
|
|
179
|
+
const shufflePhotosBackOneColumn = (row: PhotoItem[], start: number, end: number): PhotoItem[] => {
|
|
180
|
+
for (let x = start; x <= end; x++) {
|
|
181
|
+
const selectedPhoto = getPhotoForColumn(row, x);
|
|
182
|
+
row = deletePhotoFromRowByColumn(row, x);
|
|
183
|
+
row = addPhotoToRow(row, selectedPhoto, selectedPhoto.column - 1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return row;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* All the photos in the the row move forward a column (to the right)
|
|
191
|
+
* @param row
|
|
192
|
+
* @param start
|
|
193
|
+
*/
|
|
194
|
+
const shufflePhotosForwardOneColumn = (row: PhotoItem[], start: number): PhotoItem[] => {
|
|
195
|
+
for (let x = start; x > 0; x--) {
|
|
196
|
+
const selectedPhoto = getPhotoForColumn(row, x);
|
|
197
|
+
row = deletePhotoFromRowByColumn(row, x);
|
|
198
|
+
row = addPhotoToRow(row, selectedPhoto, selectedPhoto.column + 1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return row;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Moves a photo to the end of the previous row
|
|
206
|
+
* @param e
|
|
207
|
+
* @param props
|
|
208
|
+
*/
|
|
209
|
+
export const movePhotoUp = (e: React.MouseEvent<HTMLButtonElement>, props: PhotoGridProps) => {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
const { id, rowKey } = getPhotoIdAndRowKey(e);
|
|
212
|
+
|
|
213
|
+
let rowsCopy = { ...props.rows },
|
|
214
|
+
thisRow = sortRow(rowsCopy[rowKey]),
|
|
215
|
+
previousRowKey = rowKey - 1,
|
|
216
|
+
previousRow: PhotoItem[] = [];
|
|
217
|
+
|
|
218
|
+
if (rowsCopy[previousRowKey] != undefined) {
|
|
219
|
+
previousRow = sortRow(rowsCopy[previousRowKey]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const thisPhoto = getPhotoForId(thisRow, id);
|
|
223
|
+
|
|
224
|
+
let start = thisPhoto.column + 1,
|
|
225
|
+
end = thisRow.length;
|
|
226
|
+
|
|
227
|
+
thisRow = deletePhotoFromRowByColumn(thisRow, thisPhoto.column);
|
|
228
|
+
delete rowsCopy[rowKey];
|
|
229
|
+
|
|
230
|
+
if (thisRow.length) {
|
|
231
|
+
shufflePhotosBackOneColumn(thisRow, start, end);
|
|
232
|
+
rowsCopy[rowKey] = thisRow;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
previousRow = addPhotoToRow(previousRow, thisPhoto, previousRow.length + 1);
|
|
236
|
+
delete rowsCopy[previousRowKey];
|
|
237
|
+
rowsCopy[previousRowKey] = previousRow;
|
|
238
|
+
|
|
239
|
+
props.updateRows(rowsCopy);
|
|
240
|
+
props.increaseChanges();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Moves a photo to the beginning of the next row
|
|
245
|
+
* @param e
|
|
246
|
+
* @param props
|
|
247
|
+
*/
|
|
248
|
+
export const movePhotoDown = (e: React.MouseEvent<HTMLButtonElement>, props: PhotoGridProps) => {
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
const { id, rowKey } = getPhotoIdAndRowKey(e);
|
|
251
|
+
|
|
252
|
+
let rowsCopy = { ...props.rows },
|
|
253
|
+
thisRow = sortRow(rowsCopy[rowKey]),
|
|
254
|
+
nextRowKey = rowKey + 1;
|
|
255
|
+
|
|
256
|
+
const thisPhoto = getPhotoForId(thisRow, id);
|
|
257
|
+
|
|
258
|
+
let start = thisPhoto.column + 1,
|
|
259
|
+
end = thisRow.length;
|
|
260
|
+
|
|
261
|
+
thisRow = deletePhotoFromRowByColumn(thisRow, thisPhoto.column);
|
|
262
|
+
|
|
263
|
+
delete rowsCopy[rowKey];
|
|
264
|
+
|
|
265
|
+
if (thisRow.length) {
|
|
266
|
+
thisRow = shufflePhotosBackOneColumn(thisRow, start, end);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let nextRow = null;
|
|
270
|
+
|
|
271
|
+
if (rowsCopy[nextRowKey] == undefined) {
|
|
272
|
+
thisPhoto.column = 1;
|
|
273
|
+
nextRow = [thisPhoto];
|
|
274
|
+
} else {
|
|
275
|
+
nextRow = sortRow(rowsCopy[nextRowKey]);
|
|
276
|
+
nextRow = shufflePhotosForwardOneColumn(nextRow, nextRow.length);
|
|
277
|
+
nextRow = addPhotoToRow(nextRow, thisPhoto, 1);
|
|
278
|
+
delete rowsCopy[nextRowKey];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
rowsCopy[nextRowKey] = nextRow;
|
|
282
|
+
|
|
283
|
+
if (thisRow.length) {
|
|
284
|
+
rowsCopy[rowKey] = thisRow;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
props.updateRows(rowsCopy);
|
|
288
|
+
props.increaseChanges();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Swaps the row order
|
|
293
|
+
* @param rows
|
|
294
|
+
* @param firstRow
|
|
295
|
+
* @param firstRowKey
|
|
296
|
+
* @param secondRow
|
|
297
|
+
* @param secondRowKey
|
|
298
|
+
*/
|
|
299
|
+
const swapRows = (
|
|
300
|
+
rows: PhotoRows,
|
|
301
|
+
firstRow: PhotoItem[],
|
|
302
|
+
firstRowKey: number,
|
|
303
|
+
secondRow: PhotoItem[],
|
|
304
|
+
secondRowKey: number
|
|
305
|
+
): PhotoRows => {
|
|
306
|
+
delete rows[secondRowKey];
|
|
307
|
+
delete rows[firstRowKey];
|
|
308
|
+
rows[secondRowKey] = firstRow;
|
|
309
|
+
rows[firstRowKey] = secondRow;
|
|
310
|
+
|
|
311
|
+
return rows;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Move a row up
|
|
316
|
+
* @param e
|
|
317
|
+
* @param props
|
|
318
|
+
*/
|
|
319
|
+
export const moveRowUp = (e: React.MouseEvent<HTMLButtonElement>, props: PhotoGridProps) => {
|
|
320
|
+
e.preventDefault();
|
|
321
|
+
const target = e.currentTarget as HTMLButtonElement;
|
|
322
|
+
let rowKey = target.dataset.row;
|
|
323
|
+
|
|
324
|
+
if (!rowKey) {
|
|
325
|
+
throw new TypeError('row missing from row control');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const rowIndex: number = parseInt(rowKey),
|
|
329
|
+
previousRowKey = rowIndex - 1;
|
|
330
|
+
|
|
331
|
+
let rowsCopy = { ...props.rows },
|
|
332
|
+
thisRow = sortRow(rowsCopy[rowIndex]),
|
|
333
|
+
previousRow = sortRow(rowsCopy[previousRowKey]);
|
|
334
|
+
|
|
335
|
+
rowsCopy = swapRows(rowsCopy, thisRow, rowIndex, previousRow, previousRowKey);
|
|
336
|
+
|
|
337
|
+
props.updateRows(rowsCopy);
|
|
338
|
+
props.increaseChanges();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Moves a row down
|
|
343
|
+
* @param e
|
|
344
|
+
* @param props
|
|
345
|
+
*/
|
|
346
|
+
export const moveRowDown = (e: React.MouseEvent<HTMLButtonElement>, props: PhotoGridProps) => {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
const target = e.currentTarget as HTMLButtonElement;
|
|
349
|
+
let rowKey = target.dataset.row;
|
|
350
|
+
|
|
351
|
+
if (!rowKey) {
|
|
352
|
+
throw new TypeError('row missing from row control');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const rowIndex: number = parseInt(rowKey),
|
|
356
|
+
nextRowKey = rowIndex + 1;
|
|
357
|
+
|
|
358
|
+
let rowsCopy = { ...props.rows },
|
|
359
|
+
thisRow = sortRow(rowsCopy[rowIndex]),
|
|
360
|
+
nextRow = sortRow(rowsCopy[nextRowKey]);
|
|
361
|
+
|
|
362
|
+
rowsCopy = swapRows(rowsCopy, thisRow, rowIndex, nextRow, nextRowKey);
|
|
363
|
+
|
|
364
|
+
props.updateRows(rowsCopy);
|
|
365
|
+
props.increaseChanges();
|
|
366
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES5",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["dom", "es2015"],
|
|
6
|
+
"jsx": "react",
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"outDir": "./dist",
|
|
14
|
+
"rootDir": "./src"
|
|
15
|
+
},
|
|
16
|
+
"include": ["src"]
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
mode: 'production',
|
|
5
|
+
entry: './src/index.js',
|
|
6
|
+
output: {
|
|
7
|
+
path: path.resolve('dist'),
|
|
8
|
+
filename: 'index.js',
|
|
9
|
+
libraryTarget: 'commonjs2'
|
|
10
|
+
},
|
|
11
|
+
resolve: {
|
|
12
|
+
extensions: ['.tsx', '.ts', '.js'],
|
|
13
|
+
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
|
|
14
|
+
},
|
|
15
|
+
module: {
|
|
16
|
+
rules: [
|
|
17
|
+
{
|
|
18
|
+
test: /\.tsx?$/,
|
|
19
|
+
exclude: /node_modules/,
|
|
20
|
+
use: 'ts-loader',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
test: /\.js$/,
|
|
24
|
+
exclude: /node_modules/,
|
|
25
|
+
use: 'babel-loader',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
test: /\.css$/,
|
|
29
|
+
exclude: /node_modules/,
|
|
30
|
+
use: [ 'style-loader', 'css-loader' ],
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
externals: {
|
|
35
|
+
react: 'react',
|
|
36
|
+
'react-dom': 'react-dom'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|