Skip to content

Commit 9d64d2b

Browse files
authored
Clone rows with unique values (#253)
* disabling clone option for standard classes * adding new rows on duplicate clone rows * changed message & editing multiple unique fields * added cancel button for edit clone rows
1 parent b382e1e commit 9d64d2b

File tree

3 files changed

+131
-6
lines changed

3 files changed

+131
-6
lines changed

src/dashboard/Data/Browser/B4ABrowserToolbar.react.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ let B4ABrowserToolbar = ({
129129
/>
130130
<Separator />
131131
<MenuItem
132-
disabled={!selectionLength}
132+
disabled={!selectionLength || classNameForEditors.startsWith('_')}
133133
text={`Clone ${selectionLength <= 1 ? 'this row' : 'these rows'}`}
134134
onClick={onCloneSelectedRows}
135135
/>

src/dashboard/Data/Browser/Browser.react.js

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@ class Browser extends DashboardView {
7979
filters: new List(),
8080
ordering: '-createdAt',
8181
selection: {},
82+
uniqueClassFields: new List(),
8283

8384
data: null,
8485
lastMax: -1,
8586
newObject: null,
87+
editCloneRows: null,
8688

8789
lastError: null,
8890
lastNote: null,
@@ -99,6 +101,7 @@ class Browser extends DashboardView {
99101
this.fetchData = this.fetchData.bind(this);
100102
this.fetchRelation = this.fetchRelation.bind(this);
101103
this.fetchRelationCount = this.fetchRelationCount.bind(this);
104+
this.fetchClassIndexes = this.fetchClassIndexes.bind(this);
102105
this.fetchNextPage = this.fetchNextPage.bind(this);
103106
this.updateFilters = this.updateFilters.bind(this);
104107
this.showRemoveColumn = this.showRemoveColumn.bind(this);
@@ -141,6 +144,8 @@ class Browser extends DashboardView {
141144
this.closeEditRowDialog = this.closeEditRowDialog.bind(this);
142145
this.handleShowAcl = this.handleShowAcl.bind(this);
143146
this.onDialogToggle = this.onDialogToggle.bind(this);
147+
this.addEditCloneRows = this.addEditCloneRows.bind(this);
148+
this.abortEditCloneRows = this.abortEditCloneRows.bind(this);
144149
}
145150

146151
getFooterMenuButtons() {
@@ -421,6 +426,7 @@ class Browser extends DashboardView {
421426
this.fetchRelation(relation, filters);
422427
} else if (className) {
423428
this.fetchData(className, filters);
429+
this.fetchClassIndexes(className);
424430
}
425431
}
426432

@@ -596,6 +602,20 @@ class Browser extends DashboardView {
596602
}
597603
}
598604

605+
addEditCloneRows(cloneRows) {
606+
this.setState({
607+
editCloneRows: cloneRows
608+
});
609+
}
610+
611+
abortEditCloneRows(){
612+
if (this.state.editCloneRows) {
613+
this.setState({
614+
editCloneRows: null
615+
});
616+
}
617+
}
618+
599619
abortAddRow() {
600620
if (this.state.newObject) {
601621
this.setState({
@@ -647,7 +667,8 @@ class Browser extends DashboardView {
647667
newObject: null,
648668
lastMax: -1,
649669
selection: {},
650-
relation: null
670+
relation: null,
671+
editCloneRows: null
651672
};
652673
if (relation) {
653674
await this.setState(initialState);
@@ -733,6 +754,24 @@ class Browser extends DashboardView {
733754
return await this.context.currentApp.getRelationCount(relation);
734755
}
735756

757+
async fetchClassIndexes(className){
758+
try {
759+
const data = await this.context.currentApp.getIndexes(className);
760+
if(data){
761+
this.setState({
762+
uniqueClassFields: data
763+
.filter(idxObj => idxObj.unique)
764+
.map(obj => Object.keys(JSON.parse(obj.index)))
765+
.flat()
766+
});
767+
}
768+
} catch (error) {
769+
this.setState({
770+
uniqueClassFields: []
771+
});
772+
}
773+
}
774+
736775
fetchNextPage() {
737776
if (!this.state.data || this.state.isUnique) {
738777
return null;
@@ -860,12 +899,16 @@ class Browser extends DashboardView {
860899
}
861900

862901
updateRow(row, attr, value) {
863-
let isNewObject = row < 0;
902+
let isNewObject = row === -1;
903+
let isEditCloneObj = row < -1;
864904
let obj = isNewObject ? this.state.newObject : this.state.data[row];
865905
if (!obj && isNewObject) {
866906
obj = this.getLastCreatedObject(this.state.data)
867907
isNewObject = false
868908
}
909+
if(isEditCloneObj){
910+
obj = this.state.editCloneRows[row + (this.state.editCloneRows.length+1)];
911+
}
869912
const prev = obj.get(attr);
870913
if (value === prev) {
871914
return;
@@ -876,11 +919,11 @@ class Browser extends DashboardView {
876919
obj.set(attr, value);
877920
}
878921
obj.save(null, { useMasterKey: true }).then((objectSaved) => {
879-
const createdOrUpdated = isNewObject ? 'created' : 'updated';
922+
const createdOrUpdated = isNewObject || isEditCloneObj ? 'created' : 'updated';
880923
let msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' ' + createdOrUpdated;
881924
this.showNote(msg, false);
882925

883-
const state = { data: this.state.data };
926+
const state = { data: this.state.data, editCloneRows: this.state.editCloneRows };
884927

885928
if (isNewObject) {
886929
const relation = this.state.relation;
@@ -919,13 +962,24 @@ class Browser extends DashboardView {
919962
this.state.counts[obj.className] += 1;
920963
}
921964
}
965+
if(isEditCloneObj){
966+
state.editCloneRows = state.editCloneRows.filter(
967+
cloneObj => cloneObj._localId !== obj._localId
968+
);
969+
if(state.editCloneRows.length === 0)
970+
state.editCloneRows = null;
971+
if (this.props.params.className === obj.className) {
972+
this.state.data.unshift(obj);
973+
}
974+
this.state.counts[obj.className] += 1;
975+
}
922976
this.setState(state);
923977
}, (error) => {
924978
let msg = typeof error === 'string' ? error : error.message;
925979
if (msg) {
926980
msg = msg[0].toUpperCase() + msg.substr(1);
927981
}
928-
if (!isNewObject) {
982+
if (!isNewObject && !isEditCloneObj) {
929983
obj.set(attr, prev);
930984
this.setState({ data: this.state.data });
931985
}
@@ -1161,6 +1215,17 @@ class Browser extends DashboardView {
11611215
try {
11621216
await Parse.Object.saveAll(toClone, { useMasterKey: true });
11631217
} catch (error) {
1218+
if(error.code === 137){
1219+
const newClonedObjects = [];
1220+
toClone.forEach(cloneObj => {
1221+
this.state.uniqueClassFields.forEach(field => {
1222+
const fieldType = typeof cloneObj.get(field);
1223+
cloneObj.set(field, fieldType === 'string' ? '' : undefined);
1224+
});
1225+
newClonedObjects.push(cloneObj);
1226+
});
1227+
this.addEditCloneRows(newClonedObjects);
1228+
}
11641229
this.setState({
11651230
selection: {},
11661231
showCloneSelectedRowsDialog: false
@@ -1348,6 +1413,7 @@ class Browser extends DashboardView {
13481413
uniqueField={this.state.uniqueField}
13491414
count={count}
13501415
perms={this.state.clp[className]}
1416+
editCloneRows={this.state.editCloneRows}
13511417
schema={this.props.schema}
13521418
filters={this.state.filters}
13531419
onFilterChange={this.updateFilters}
@@ -1385,6 +1451,7 @@ class Browser extends DashboardView {
13851451
onAbortAddRow={this.abortAddRow}
13861452
onAddRowWithModal={this.addRowWithModal}
13871453
onAddClass={this.showCreateClass}
1454+
onAbortEditCloneRows={this.abortEditCloneRows}
13881455
err={this.state.err}
13891456
showNote={this.showNote}
13901457
onClickIndexManager={this.onClickIndexManager} />

src/dashboard/Data/Browser/BrowserTable.react.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,54 @@ export default class BrowserTable extends React.Component {
115115
(rowWidth, { visible, width }) => visible ? rowWidth + width : rowWidth,
116116
this.props.onAddRow ? 210 : 0
117117
);
118+
let editCloneRows = null;
119+
if(this.props.editCloneRows){
120+
editCloneRows = (
121+
<div style={{ marginBottom: 30, borderBottom: "1px solid #169CEE" }}>
122+
{this.props.editCloneRows.map((cloneRow, idx) => {
123+
let index = (this.props.editCloneRows.length + 1) * -1 + idx;
124+
const currentCol = this.props.current && this.props.current.row === index ? this.props.current.col : undefined;
125+
const isEditingRow = this.props.current && this.props.current.row === index && !!this.props.editing;
126+
return (
127+
<BrowserRow
128+
key={index}
129+
isEditing={isEditingRow}
130+
className={this.props.className}
131+
columns={this.props.columns}
132+
schema={this.props.schema}
133+
simplifiedSchema={this.props.simplifiedSchema}
134+
filters={this.props.filters}
135+
currentCol={currentCol}
136+
isUnique={this.props.isUnique}
137+
obj={cloneRow}
138+
onPointerClick={this.props.onPointerClick}
139+
onFilterChange={this.props.onFilterChange}
140+
order={this.props.order}
141+
readOnlyFields={READ_ONLY}
142+
row={index}
143+
rowWidth={rowWidth}
144+
selection={this.props.selection}
145+
selectRow={this.props.selectRow}
146+
setCurrent={this.props.setCurrent}
147+
setEditing={this.props.setEditing}
148+
setRelation={this.props.setRelation}
149+
setCopyableValue={this.props.setCopyableValue}
150+
setContextMenu={this.props.setContextMenu}
151+
onEditSelectedRow={this.props.onEditSelectedRow}
152+
/>
153+
);
154+
}
155+
)}
156+
<Button
157+
value='Cancel'
158+
primary={false}
159+
onClick={this.props.onAbortEditCloneRows}
160+
width='55px'
161+
additionalStyles={{ fontSize: '12px', height: '20px', lineHeight: '20px', margin: '5px', padding: '0'}}
162+
/>
163+
</div>
164+
);
165+
}
118166
let newRow = null;
119167
if (this.props.newObject && this.state.offset <= 0) {
120168
const currentCol = this.props.current && this.props.current.row === -1 ? this.props.current.col : undefined;
@@ -209,6 +257,9 @@ export default class BrowserTable extends React.Component {
209257
}
210258
}
211259
let obj = this.props.current.row < 0 ? this.props.newObject : this.props.data[this.props.current.row];
260+
if(!obj && this.props.current.row < -1){
261+
obj = this.props.editCloneRows[this.props.current.row + this.props.editCloneRows.length + 1];
262+
}
212263
let value = obj ;
213264
if (!this.props.isUnique) {
214265
if (type === 'Array' || type === 'Object') {
@@ -235,9 +286,15 @@ export default class BrowserTable extends React.Component {
235286
value = '';
236287
}
237288
let wrapTop = Math.max(0, this.props.current.row * ROW_HEIGHT);
289+
if(this.props.current.row < -1 && this.props.editCloneRows){
290+
wrapTop = ROW_HEIGHT * (this.props.current.row + (this.props.editCloneRows.length + 1));
291+
}
238292
if (this.props.current.row > -1 && this.props.newObject) {
239293
wrapTop += 90;
240294
}
295+
if(this.props.current.row >= -1 && this.props.editCloneRows){
296+
wrapTop += ROW_HEIGHT * (this.props.editCloneRows.length + 1) + 30;
297+
}
241298
let wrapLeft = 30;
242299
for (let i = 0; i < this.props.current.col; i++) {
243300
const column = this.props.order[i];
@@ -306,6 +363,7 @@ export default class BrowserTable extends React.Component {
306363
table = (
307364
<div className={styles.table} ref='table'>
308365
<div style={{ height: Math.max(0, this.state.offset * ROW_HEIGHT) }} />
366+
{editCloneRows}
309367
{newRow}
310368
{rows}
311369
<div style={{ height: Math.max(0, (this.props.data.length - this.state.offset - MAX_ROWS) * ROW_HEIGHT) }} />

0 commit comments

Comments
 (0)