Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/GraphArea.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ function Graph({
useEffect(() => active && instance && instance.setCurStatus(), [active && instance]);
useEffect(() => {
if (active && instance) dispatcher({ type: T.SET_CUR_INSTANCE, payload: instance });
}, [active && instance]);
if (instance) dispatcher({ type: T.SET_GRAPH_INSTANCE, payload: { graphID, instance } });
}, [active, instance, graphID, dispatcher]);

useEffect(() => {
if (ref.current) {
Expand Down
42 changes: 36 additions & 6 deletions src/component/TabBar.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
MdEdit, MdClose, MdAdd,
} from 'react-icons/md';
Expand All @@ -8,19 +8,42 @@ import localStorageManager from '../graph-builder/local-storage-manager';
import { actionType as T } from '../reducer';
import { newProject, editDetails } from '../toolbarActions/toolbarFunctions';
import './tabBar.css';
import ConfirmModal from './modals/ConfirmModal';

const TabBar = ({ superState, dispatcher }) => {
const closeTab = (i, e) => {
e.stopPropagation();
// eslint-disable-next-line no-alert
if (!window.confirm('Do you confirm to close the tab? This action is irreversable.')) return;
const [confirmOpen, setConfirmOpen] = useState(false);
const [tabToClose, setTabToClose] = useState(null);

const closeTab = (i) => {
localStorageManager.remove(superState.graphs[i] ? superState.graphs[i].graphID : null);
dispatcher({ type: T.REMOVE_GRAPH, payload: i });
if (!superState.curGraphIndex && superState.graphs.length === 1) {
dispatcher({ type: T.SET_CUR_INSTANCE, payload: null });
dispatcher({ type: T.SET_CUR_INDEX, payload: -1 });
}
};

const handleRequestCloseTab = (i, e) => {
e.stopPropagation();
const graph = superState.graphs[i];
if (graph && graph.instance && !graph.instance.isSaved) {
setTabToClose(i);
setConfirmOpen(true);
} else {
closeTab(i);
}
};

const handleConfirmClose = () => {
closeTab(tabToClose);
setConfirmOpen(false);
setTabToClose(null);
};

const handleCancelClose = () => {
setConfirmOpen(false);
setTabToClose(null);
};
const editCur = (e) => {
e.stopPropagation();
editDetails(superState, dispatcher);
Expand Down Expand Up @@ -80,7 +103,7 @@ const TabBar = ({ superState, dispatcher }) => {
) : <></>}
<button
className="tab-act close"
onClick={closeTab.bind(this, i)}
onClick={handleRequestCloseTab.bind(this, i)}
type="button"
data-tip="Close current Workflow (Ctrl + Shift + L)"
data-for="header-tab"
Expand All @@ -90,6 +113,13 @@ const TabBar = ({ superState, dispatcher }) => {
<ReactTooltip place="bottom" type="dark" effect="solid" id="header-tab" />
</div>
))}
<ConfirmModal
isOpen={confirmOpen}
title="Close Tab"
message="Do you confirm to close the tab? This action is irreversible."
onConfirm={handleConfirmClose}
onCancel={handleCancelClose}
/>
</div>
);
};
Expand Down
78 changes: 58 additions & 20 deletions src/component/fileBrowser.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ import React, { useEffect, useState } from 'react';
import FileBrowser, { FileRenderers, FolderRenderers } from 'react-keyed-file-browser';
import { readFile, readTextFile } from '../toolbarActions/toolbarFunctions';
import { actionType as T } from '../reducer';
import ConfirmModal from './modals/ConfirmModal';
import './fileBrowser.css';

const LocalFileBrowser = ({ superState, dispatcher }) => {
const fileRef = React.useRef();
const dirInputRef = React.useRef();
const tempFilesRef = React.useRef(null);
const [confirmOpen, setConfirmOpen] = useState(false);
const [pendingFolderName, setPendingFolderName] = useState('');
const [dirButton, setDirButton] = useState(false);
const [fileState, setFileState] = useState([]);

useEffect(() => {
if (navigator.userAgent.indexOf('Edg') !== -1 || navigator.userAgent.indexOf('Chrome') !== -1) {
setDirButton(true);
}
// if (navigator.userAgent.indexOf('Edg') !== -1 || navigator.userAgent.indexOf('Chrome') !== -1) {
// setDirButton(true);
// }
// Force fallback to webkitdirectory to allow custom popup flow
setDirButton(false);
dispatcher({ type: T.SET_FILE_REF, payload: fileRef });
}, []);

Expand Down Expand Up @@ -133,31 +140,32 @@ const LocalFileBrowser = ({ superState, dispatcher }) => {
return (
<div>
{!dirButton && (
<label
className="inputButton"
htmlFor="fileButton"
>
Upload Directory
<>
<button
type="button"
className="inputButton"
onClick={() => dirInputRef.current.click()}
>
Upload Directory
</button>
<input
type="file"
accept=".py, .m, .c, .cpp, .v, .sh"
id="fileButton"
ref={dirInputRef}
style={{ display: 'none' }}
onClick={(e) => { e.target.value = null; }}
onChange={(e) => {
const filesArray = Array.from(e.target.files).map((file) => ({
key: file.webkitRelativePath,
modified: file.lastModified,
size: file.size,
fileObj: file,
}));

setFileState(filesArray);
window.localStorage.setItem('fileList', JSON.stringify(filesArray));
const { files } = e.target;
if (files && files.length > 0) {
tempFilesRef.current = files;
const folderName = files[0].webkitRelativePath.split('/')[0];
setPendingFolderName(folderName);
setConfirmOpen(true);
}
}}
webkitdirectory
webkitdirectory=""
/>
</label>
</>
)}
{dirButton && (
<button
Expand Down Expand Up @@ -212,6 +220,36 @@ const LocalFileBrowser = ({ superState, dispatcher }) => {
fileRenderer={FileRenderers.TableFile}
folderRenderer={FolderRenderers.TableFolder}
/>
<ConfirmModal
isOpen={confirmOpen}
title="Upload Directory"
message={`Directory Selected: ${pendingFolderName}`}
onConfirm={() => {
setConfirmOpen(false);
const files = tempFilesRef.current;
if (files && files.length > 0) {
const filesArray = Array.from(files).map((file) => ({
key: file.webkitRelativePath,
modified: file.lastModified,
size: file.size,
fileObj: file,
}));

setFileState(filesArray);
window.localStorage.setItem('fileList', JSON.stringify(filesArray));
if (filesArray.length > 0) {
dispatcher({ type: T.SET_DIR_NAME, payload: filesArray[0].key.split('/')[0] });
dispatcher({ type: T.SET_FILE_STATE, payload: filesArray });
}
}
tempFilesRef.current = null;
}}
onCancel={() => {
setConfirmOpen(false);
if (dirInputRef.current) dirInputRef.current.value = '';
tempFilesRef.current = null;
}}
/>
</div>
);
};
Expand Down
19 changes: 19 additions & 0 deletions src/component/modals/ConfirmModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import ParentModal from './ParentModal';
import './confirmModal.css';

const ConfirmModal = ({
isOpen, title, message, onConfirm, onCancel,
}) => (
<ParentModal ModelOpen={isOpen} closeModal={onCancel} title={title}>
<div className="confirm-modal-content">
<div className="confirm-modal-message">{message}</div>
<div className="confirm-modal-actions">
<button type="button" className="confirm-btn" onClick={onConfirm}>Yes</button>
<button type="button" className="cancel-btn" onClick={onCancel}>No</button>
</div>
</div>
</ParentModal>
);

export default ConfirmModal;
34 changes: 34 additions & 0 deletions src/component/modals/confirmModal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.confirm-modal-content {
padding: 20px;
text-align: center;
}
.confirm-modal-message {
margin-bottom: 20px;
font-size: 1.1em;
}
.confirm-modal-actions {
display: flex;
justify-content: center;
gap: 20px;
}
.confirm-btn, .cancel-btn {
padding: 8px 20px;
font-size: 1em;
border: none;
border-radius: 4px;
cursor: pointer;
}
.confirm-btn {
background: #d9534f;
color: #fff;
}
.cancel-btn {
background: #f0f0f0;
color: #333;
}
.confirm-btn:hover {
background: #c9302c;
}
.cancel-btn:hover {
background: #e0e0e0;
}
13 changes: 13 additions & 0 deletions src/graph-builder/graph-core/5-load-save.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ class GraphLoadSave extends GraphUndoRedo {
constructor(...args) {
super(...args);
this.autoSaveIntervalId = null;
this.lastSavedActionIndex = 0;
}

get isSaved() {
return this.curActionIndex === this.lastSavedActionIndex;
}

registerEvents() {
Expand Down Expand Up @@ -132,6 +137,7 @@ class GraphLoadSave extends GraphUndoRedo {
fileHandle: handle,
}]);
this.dispatcher({ type: T.SET_FILE_STATE, payload: fS });
this.lastSavedActionIndex = this.curActionIndex;
toast.success('File saved Successfully');
} catch (error) {
// AbortError is silently ignored (user cancelled)
Expand All @@ -140,6 +146,7 @@ class GraphLoadSave extends GraphUndoRedo {
// eslint-disable-next-line no-alert
const fileName = prompt('Filename:');
saveAs(blob, `${fileName || `${this.getName()}-concore`}.graphml`);
this.lastSavedActionIndex = this.curActionIndex;
toast.success('File saved Successfully');
}
}
Expand Down Expand Up @@ -172,6 +179,7 @@ class GraphLoadSave extends GraphUndoRedo {
const stream = await handle.createWritable();
await stream.write(blob);
await stream.close();
this.lastSavedActionIndex = this.curActionIndex;
toast.success('File saved Successfully');
} catch (error) {
// AbortError is silently ignored (user cancelled)
Expand Down Expand Up @@ -213,6 +221,7 @@ class GraphLoadSave extends GraphUndoRedo {
graphMLParser(graphML).then((graphObject) => {
localStorageManager.save(this.id, graphObject);
this.loadGraphFromLocalStorage();
this.lastSavedActionIndex = this.curActionIndex;
});
}

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

Expand Down
1 change: 1 addition & 0 deletions src/reducer/actionType.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const actionType = {
SET_FUNCTIONS: 'SET_FUNCTIONS',
SET_LOGS: 'SET_LOGS',
SET_LOGS_MESSAGE: 'SET_LOGS_MESSAGE',
SET_GRAPH_INSTANCE: 'SET_GRAPH_INSTANCE',
};

export default zealit(actionType);
8 changes: 8 additions & 0 deletions src/reducer/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ const reducer = (state, action) => {
return { ...newState };
}

case T.SET_GRAPH_INSTANCE: {
const newState = { ...state };
newState.graphs = newState.graphs.map((g) => (
g.graphID === action.payload.graphID ? { ...g, instance: action.payload.instance } : g
));
return { ...newState };
}

default:
return state;
}
Expand Down