Skip to content

Commit 8936f42

Browse files
authored
Merge pull request #268 from GREENRAT-K405/dev
Display a normal pop-up message instead of windows alert at file upload directory and close tab in graph workspace. fix issue #248
2 parents 8dc73af + 04a7711 commit 8936f42

8 files changed

Lines changed: 171 additions & 27 deletions

File tree

src/GraphArea.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ function Graph({
4444
useEffect(() => active && instance && instance.setCurStatus(), [active && instance]);
4545
useEffect(() => {
4646
if (active && instance) dispatcher({ type: T.SET_CUR_INSTANCE, payload: instance });
47-
}, [active && instance]);
47+
if (instance) dispatcher({ type: T.SET_GRAPH_INSTANCE, payload: { graphID, instance } });
48+
}, [active, instance, graphID, dispatcher]);
4849

4950
useEffect(() => {
5051
if (ref.current) {

src/component/TabBar.jsx

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import {
33
MdEdit, MdClose, MdAdd,
44
} from 'react-icons/md';
@@ -8,19 +8,42 @@ import localStorageManager from '../graph-builder/local-storage-manager';
88
import { actionType as T } from '../reducer';
99
import { newProject, editDetails } from '../toolbarActions/toolbarFunctions';
1010
import './tabBar.css';
11+
import ConfirmModal from './modals/ConfirmModal';
1112

1213
const TabBar = ({ superState, dispatcher }) => {
13-
const closeTab = (i, e) => {
14-
e.stopPropagation();
15-
// eslint-disable-next-line no-alert
16-
if (!window.confirm('Do you confirm to close the tab? This action is irreversable.')) return;
14+
const [confirmOpen, setConfirmOpen] = useState(false);
15+
const [tabToClose, setTabToClose] = useState(null);
16+
17+
const closeTab = (i) => {
1718
localStorageManager.remove(superState.graphs[i] ? superState.graphs[i].graphID : null);
1819
dispatcher({ type: T.REMOVE_GRAPH, payload: i });
1920
if (!superState.curGraphIndex && superState.graphs.length === 1) {
2021
dispatcher({ type: T.SET_CUR_INSTANCE, payload: null });
2122
dispatcher({ type: T.SET_CUR_INDEX, payload: -1 });
2223
}
2324
};
25+
26+
const handleRequestCloseTab = (i, e) => {
27+
e.stopPropagation();
28+
const graph = superState.graphs[i];
29+
if (graph && graph.instance && !graph.instance.isSaved) {
30+
setTabToClose(i);
31+
setConfirmOpen(true);
32+
} else {
33+
closeTab(i);
34+
}
35+
};
36+
37+
const handleConfirmClose = () => {
38+
closeTab(tabToClose);
39+
setConfirmOpen(false);
40+
setTabToClose(null);
41+
};
42+
43+
const handleCancelClose = () => {
44+
setConfirmOpen(false);
45+
setTabToClose(null);
46+
};
2447
const editCur = (e) => {
2548
e.stopPropagation();
2649
editDetails(superState, dispatcher);
@@ -80,7 +103,7 @@ const TabBar = ({ superState, dispatcher }) => {
80103
) : <></>}
81104
<button
82105
className="tab-act close"
83-
onClick={closeTab.bind(this, i)}
106+
onClick={handleRequestCloseTab.bind(this, i)}
84107
type="button"
85108
data-tip="Close current Workflow (Ctrl + Shift + L)"
86109
data-for="header-tab"
@@ -90,6 +113,13 @@ const TabBar = ({ superState, dispatcher }) => {
90113
<ReactTooltip place="bottom" type="dark" effect="solid" id="header-tab" />
91114
</div>
92115
))}
116+
<ConfirmModal
117+
isOpen={confirmOpen}
118+
title="Close Tab"
119+
message="Do you confirm to close the tab? This action is irreversible."
120+
onConfirm={handleConfirmClose}
121+
onCancel={handleCancelClose}
122+
/>
93123
</div>
94124
);
95125
};

src/component/fileBrowser.jsx

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,24 @@ import React, { useEffect, useState } from 'react';
77
import FileBrowser, { FileRenderers, FolderRenderers } from 'react-keyed-file-browser';
88
import { readFile, readTextFile } from '../toolbarActions/toolbarFunctions';
99
import { actionType as T } from '../reducer';
10+
import ConfirmModal from './modals/ConfirmModal';
1011
import './fileBrowser.css';
1112

1213
const LocalFileBrowser = ({ superState, dispatcher }) => {
1314
const fileRef = React.useRef();
15+
const dirInputRef = React.useRef();
16+
const tempFilesRef = React.useRef(null);
17+
const [confirmOpen, setConfirmOpen] = useState(false);
18+
const [pendingFolderName, setPendingFolderName] = useState('');
1419
const [dirButton, setDirButton] = useState(false);
1520
const [fileState, setFileState] = useState([]);
1621

1722
useEffect(() => {
18-
if (navigator.userAgent.indexOf('Edg') !== -1 || navigator.userAgent.indexOf('Chrome') !== -1) {
19-
setDirButton(true);
20-
}
23+
// if (navigator.userAgent.indexOf('Edg') !== -1 || navigator.userAgent.indexOf('Chrome') !== -1) {
24+
// setDirButton(true);
25+
// }
26+
// Force fallback to webkitdirectory to allow custom popup flow
27+
setDirButton(false);
2128
dispatcher({ type: T.SET_FILE_REF, payload: fileRef });
2229
}, []);
2330

@@ -133,31 +140,32 @@ const LocalFileBrowser = ({ superState, dispatcher }) => {
133140
return (
134141
<div>
135142
{!dirButton && (
136-
<label
137-
className="inputButton"
138-
htmlFor="fileButton"
139-
>
140-
Upload Directory
143+
<>
144+
<button
145+
type="button"
146+
className="inputButton"
147+
onClick={() => dirInputRef.current.click()}
148+
>
149+
Upload Directory
150+
</button>
141151
<input
142152
type="file"
143153
accept=".py, .m, .c, .cpp, .v, .sh"
144-
id="fileButton"
154+
ref={dirInputRef}
145155
style={{ display: 'none' }}
146156
onClick={(e) => { e.target.value = null; }}
147157
onChange={(e) => {
148-
const filesArray = Array.from(e.target.files).map((file) => ({
149-
key: file.webkitRelativePath,
150-
modified: file.lastModified,
151-
size: file.size,
152-
fileObj: file,
153-
}));
154-
155-
setFileState(filesArray);
156-
window.localStorage.setItem('fileList', JSON.stringify(filesArray));
158+
const { files } = e.target;
159+
if (files && files.length > 0) {
160+
tempFilesRef.current = files;
161+
const folderName = files[0].webkitRelativePath.split('/')[0];
162+
setPendingFolderName(folderName);
163+
setConfirmOpen(true);
164+
}
157165
}}
158-
webkitdirectory
166+
webkitdirectory=""
159167
/>
160-
</label>
168+
</>
161169
)}
162170
{dirButton && (
163171
<button
@@ -212,6 +220,36 @@ const LocalFileBrowser = ({ superState, dispatcher }) => {
212220
fileRenderer={FileRenderers.TableFile}
213221
folderRenderer={FolderRenderers.TableFolder}
214222
/>
223+
<ConfirmModal
224+
isOpen={confirmOpen}
225+
title="Upload Directory"
226+
message={`Directory Selected: ${pendingFolderName}`}
227+
onConfirm={() => {
228+
setConfirmOpen(false);
229+
const files = tempFilesRef.current;
230+
if (files && files.length > 0) {
231+
const filesArray = Array.from(files).map((file) => ({
232+
key: file.webkitRelativePath,
233+
modified: file.lastModified,
234+
size: file.size,
235+
fileObj: file,
236+
}));
237+
238+
setFileState(filesArray);
239+
window.localStorage.setItem('fileList', JSON.stringify(filesArray));
240+
if (filesArray.length > 0) {
241+
dispatcher({ type: T.SET_DIR_NAME, payload: filesArray[0].key.split('/')[0] });
242+
dispatcher({ type: T.SET_FILE_STATE, payload: filesArray });
243+
}
244+
}
245+
tempFilesRef.current = null;
246+
}}
247+
onCancel={() => {
248+
setConfirmOpen(false);
249+
if (dirInputRef.current) dirInputRef.current.value = '';
250+
tempFilesRef.current = null;
251+
}}
252+
/>
215253
</div>
216254
);
217255
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import ParentModal from './ParentModal';
3+
import './confirmModal.css';
4+
5+
const ConfirmModal = ({
6+
isOpen, title, message, onConfirm, onCancel,
7+
}) => (
8+
<ParentModal ModelOpen={isOpen} closeModal={onCancel} title={title}>
9+
<div className="confirm-modal-content">
10+
<div className="confirm-modal-message">{message}</div>
11+
<div className="confirm-modal-actions">
12+
<button type="button" className="confirm-btn" onClick={onConfirm}>Yes</button>
13+
<button type="button" className="cancel-btn" onClick={onCancel}>No</button>
14+
</div>
15+
</div>
16+
</ParentModal>
17+
);
18+
19+
export default ConfirmModal;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.confirm-modal-content {
2+
padding: 20px;
3+
text-align: center;
4+
}
5+
.confirm-modal-message {
6+
margin-bottom: 20px;
7+
font-size: 1.1em;
8+
}
9+
.confirm-modal-actions {
10+
display: flex;
11+
justify-content: center;
12+
gap: 20px;
13+
}
14+
.confirm-btn, .cancel-btn {
15+
padding: 8px 20px;
16+
font-size: 1em;
17+
border: none;
18+
border-radius: 4px;
19+
cursor: pointer;
20+
}
21+
.confirm-btn {
22+
background: #d9534f;
23+
color: #fff;
24+
}
25+
.cancel-btn {
26+
background: #f0f0f0;
27+
color: #333;
28+
}
29+
.confirm-btn:hover {
30+
background: #c9302c;
31+
}
32+
.cancel-btn:hover {
33+
background: #e0e0e0;
34+
}

src/graph-builder/graph-core/5-load-save.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ class GraphLoadSave extends GraphUndoRedo {
1313
constructor(...args) {
1414
super(...args);
1515
this.autoSaveIntervalId = null;
16+
this.lastSavedActionIndex = 0;
17+
}
18+
19+
get isSaved() {
20+
return this.curActionIndex === this.lastSavedActionIndex;
1621
}
1722

1823
registerEvents() {
@@ -132,6 +137,7 @@ class GraphLoadSave extends GraphUndoRedo {
132137
fileHandle: handle,
133138
}]);
134139
this.dispatcher({ type: T.SET_FILE_STATE, payload: fS });
140+
this.lastSavedActionIndex = this.curActionIndex;
135141
toast.success('File saved Successfully');
136142
} catch (error) {
137143
// AbortError is silently ignored (user cancelled)
@@ -140,6 +146,7 @@ class GraphLoadSave extends GraphUndoRedo {
140146
// eslint-disable-next-line no-alert
141147
const fileName = prompt('Filename:');
142148
saveAs(blob, `${fileName || `${this.getName()}-concore`}.graphml`);
149+
this.lastSavedActionIndex = this.curActionIndex;
143150
toast.success('File saved Successfully');
144151
}
145152
}
@@ -172,6 +179,7 @@ class GraphLoadSave extends GraphUndoRedo {
172179
const stream = await handle.createWritable();
173180
await stream.write(blob);
174181
await stream.close();
182+
this.lastSavedActionIndex = this.curActionIndex;
175183
toast.success('File saved Successfully');
176184
} catch (error) {
177185
// AbortError is silently ignored (user cancelled)
@@ -213,6 +221,7 @@ class GraphLoadSave extends GraphUndoRedo {
213221
graphMLParser(graphML).then((graphObject) => {
214222
localStorageManager.save(this.id, graphObject);
215223
this.loadGraphFromLocalStorage();
224+
this.lastSavedActionIndex = this.curActionIndex;
216225
});
217226
}
218227

@@ -225,6 +234,10 @@ class GraphLoadSave extends GraphUndoRedo {
225234
const graphContent = localStorageManager.get(this.id);
226235
if (!graphContent) return false;
227236
this.loadJson(graphContent);
237+
// If loaded from localStorage, assume UNSAVED unless actions are 0.
238+
// Actually, loadJson sets curActionIndex based on history.
239+
// If we want to support recovering unsaved work, we should leave lastSavedActionIndex as 0 (unless graph is empty and curActionIndex is 0).
240+
if (this.curActionIndex === 0) this.lastSavedActionIndex = 0;
228241
return true;
229242
}
230243

src/reducer/actionType.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const actionType = {
4444
SET_FUNCTIONS: 'SET_FUNCTIONS',
4545
SET_LOGS: 'SET_LOGS',
4646
SET_LOGS_MESSAGE: 'SET_LOGS_MESSAGE',
47+
SET_GRAPH_INSTANCE: 'SET_GRAPH_INSTANCE',
4748
};
4849

4950
export default zealit(actionType);

src/reducer/reducer.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,14 @@ const reducer = (state, action) => {
243243
return { ...newState };
244244
}
245245

246+
case T.SET_GRAPH_INSTANCE: {
247+
const newState = { ...state };
248+
newState.graphs = newState.graphs.map((g) => (
249+
g.graphID === action.payload.graphID ? { ...g, instance: action.payload.instance } : g
250+
));
251+
return { ...newState };
252+
}
253+
246254
default:
247255
return state;
248256
}

0 commit comments

Comments
 (0)