Compare commits
5 Commits
d22dbd7629
...
40f7e25d8f
Author | SHA1 | Date | |
---|---|---|---|
40f7e25d8f | |||
e85ad4f4bc | |||
934086251b | |||
ba9713e3eb | |||
c25a2c5fe2 |
BIN
.nova/Artwork
BIN
.nova/Artwork
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
@ -1,5 +0,0 @@
|
||||
{
|
||||
"workspace.art_style" : 1,
|
||||
"workspace.color" : 10,
|
||||
"workspace.name" : "Treadl"
|
||||
}
|
@ -10,6 +10,8 @@ export default {
|
||||
UPDATE_EDITOR: 'UPDATE_EDITOR',
|
||||
RECEIVE_SNIPPET: 'RECEIVE_SNIPPET',
|
||||
DELETE_SNIPPET: 'DELETE_SNIPPET',
|
||||
RECEIVE_SNAPSHOT: 'RECEIVE_SNAPSHOT',
|
||||
TRAVERSE_SNAPSHOTS: 'TRAVERSE_SNAPSHOTS',
|
||||
RECEIVE_COMMENT: 'RECEIVE_COMMENT',
|
||||
DELETE_COMMENT: 'DELETE_COMMENT',
|
||||
|
||||
@ -55,6 +57,14 @@ export default {
|
||||
return { type: this.DELETE_SNIPPET, snippetId };
|
||||
},
|
||||
|
||||
receiveSnapshot(snapshot) {
|
||||
return { type: this.RECEIVE_SNAPSHOT, snapshot };
|
||||
},
|
||||
|
||||
traverseSnapshots(direction) {
|
||||
return { type: this.TRAVERSE_SNAPSHOTS, direction };
|
||||
},
|
||||
|
||||
receiveComment(comment) {
|
||||
return { type: this.RECEIVE_COMMENT, comment };
|
||||
},
|
||||
|
@ -5,6 +5,7 @@ import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useBlocker } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import styled from 'styled-components';
|
||||
import Tour from '../../../includes/Tour';
|
||||
import ElementPan from '../../../includes/ElementPan';
|
||||
@ -43,6 +44,8 @@ function Draft() {
|
||||
dispatch(actions.objects.receive(o));
|
||||
setObject(o);
|
||||
setPattern(o.pattern);
|
||||
// Create a snapshot on first load (so undo has somewhere to revert to)
|
||||
setTimeout(() => createSnapshot(o.pattern), 1000);
|
||||
});
|
||||
}, [objectId]);
|
||||
|
||||
@ -51,15 +54,29 @@ function Draft() {
|
||||
currentLocation.pathname !== nextLocation.pathname
|
||||
);
|
||||
|
||||
const createSnapshot = (snapshotPattern) => {
|
||||
const deepCopy = JSON.parse(JSON.stringify(snapshotPattern));
|
||||
const snapshot = {
|
||||
objectId: objectId,
|
||||
createdAt: new Date(),
|
||||
warp: deepCopy.warp,
|
||||
weft: deepCopy.weft,
|
||||
tieups: deepCopy.tieups,
|
||||
};
|
||||
dispatch(actions.objects.receiveSnapshot(snapshot));
|
||||
};
|
||||
const debouncedSnapshot = useDebouncedCallback(createSnapshot, 1000);
|
||||
|
||||
const updateObject = (update) => {
|
||||
setObject(Object.assign({}, object, update));
|
||||
setUnsaved(true);
|
||||
};
|
||||
|
||||
const updatePattern = (update) => {
|
||||
const updatePattern = (update, withoutSnapshot = false) => {
|
||||
const newPattern = Object.assign({}, pattern, update);
|
||||
setPattern(Object.assign({}, pattern, newPattern));
|
||||
setPattern(newPattern);
|
||||
setUnsaved(true);
|
||||
if (!withoutSnapshot) debouncedSnapshot(newPattern);
|
||||
};
|
||||
|
||||
const saveObject = () => {
|
||||
|
@ -63,13 +63,15 @@ function Tools({ object, pattern, warp, weft, unsaved, saving, baseSize, updateP
|
||||
const { objectId, username, projectPath } = useParams();
|
||||
const { colours } = pattern;
|
||||
|
||||
const { user, project, editor } = useSelector(state => {
|
||||
const { user, project, editor, snapshots, currentSnapshotIndex } = useSelector(state => {
|
||||
const user = state.users.users.filter(u => state.auth.currentUserId === u._id)[0];
|
||||
let project = {};
|
||||
state.projects.projects.forEach((p) => {
|
||||
if (p.path === projectPath && p.owner && p.owner.username === username) project = p;
|
||||
});
|
||||
return { user, project, editor: state.objects.editor };
|
||||
const snapshots = state.objects.snapshots.filter(s => s.objectId === objectId);
|
||||
const currentSnapshotIndex = state.objects.currentSnapshotIndex;
|
||||
return { user, project, editor: state.objects.editor, snapshots, currentSnapshotIndex };
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@ -85,6 +87,22 @@ function Tools({ object, pattern, warp, weft, unsaved, saving, baseSize, updateP
|
||||
if (colours?.length && !editor.colour) setColour(colours[colours.length - 1]);
|
||||
}, [colours]);
|
||||
|
||||
const applyNextHistory = direction => {
|
||||
const snapshot = snapshots[currentSnapshotIndex + direction];
|
||||
if (!snapshot) return;
|
||||
const newWarp = Object.assign({}, snapshot.warp);
|
||||
const newWeft = Object.assign({}, snapshot.weft);
|
||||
const newTieups = Object.assign([], snapshot.tieups);
|
||||
updatePattern({ warp: newWarp, weft: newWeft, tieups: newTieups }, true);
|
||||
dispatch(actions.objects.traverseSnapshots(direction));
|
||||
};
|
||||
const undo = () => {
|
||||
applyNextHistory(1);
|
||||
};
|
||||
const redo = () => {
|
||||
applyNextHistory(-1);
|
||||
};
|
||||
|
||||
const enableTool = (tool) => {
|
||||
dispatch(actions.objects.updateEditor({ tool, colour: editor.colour }));
|
||||
};
|
||||
@ -174,11 +192,11 @@ function Tools({ object, pattern, warp, weft, unsaved, saving, baseSize, updateP
|
||||
toast('🗑️ Pattern deleted');
|
||||
dispatch(actions.objects.delete(objectId));
|
||||
navigate(`/${project.fullName}`);
|
||||
}, err => console.log(err));
|
||||
});
|
||||
}
|
||||
|
||||
const revertChanges = () => {
|
||||
const sure = window.confirm('Really revert your changes to your last save point?\n\nAny updates to your pattern since you last saved will be lost.')
|
||||
const sure = window.confirm('Really revert your changes to your last save point?\n\nAny updates to your pattern since you last saved, along with your "undo" history, will be lost.')
|
||||
if (sure) {
|
||||
window.location.reload();
|
||||
}
|
||||
@ -247,6 +265,12 @@ function Tools({ object, pattern, warp, weft, unsaved, saving, baseSize, updateP
|
||||
</div>
|
||||
:
|
||||
<div style={{display: 'flex', alignItems: 'end'}}>
|
||||
<div style={{marginRight: 10}}>
|
||||
<Button.Group size="tiny">
|
||||
<Button disabled={currentSnapshotIndex >= (snapshots?.length - 1)} data-tooltip="Undo" size="tiny" icon onClick={undo}><Icon name="undo" /></Button>
|
||||
<Button disabled={!currentSnapshotIndex} data-tooltip="Redo" size="tiny" icon onClick={redo}><Icon name="redo" /></Button>
|
||||
</Button.Group>
|
||||
</div>
|
||||
<div>
|
||||
<ToolLabel>View</ToolLabel>
|
||||
<Popup hoverable on='click'
|
||||
|
@ -9,6 +9,8 @@ const initialState = {
|
||||
selected: null,
|
||||
editor: { tool: 'straight', colour: null, view: 'interlacement', autoExtend: true },
|
||||
snippets: [],
|
||||
snapshots: [],
|
||||
currentSnapshotIndex: 0,
|
||||
};
|
||||
|
||||
function objects(state = initialState, action) {
|
||||
@ -94,6 +96,18 @@ function objects(state = initialState, action) {
|
||||
return Object.assign({}, state, { snippets: state.snippets.filter(e => e._id !== action.snippetId) });
|
||||
}
|
||||
|
||||
case actions.objects.RECEIVE_SNAPSHOT: {
|
||||
const snapshots = Object.assign([], state.snapshots);
|
||||
snapshots.splice(0, 0, action.snapshot);
|
||||
if (snapshots.length > 10) snapshots.pop(); // Only keep the latest 10 snapshots
|
||||
return Object.assign({}, state, { snapshots, currentSnapshotIndex: 0});
|
||||
}
|
||||
case actions.objects.TRAVERSE_SNAPSHOTS: {
|
||||
const currentSnapshotIndex = state.currentSnapshotIndex + action.direction;
|
||||
if (currentSnapshotIndex < 0 || currentSnapshotIndex >= state.snapshots.length) return state;
|
||||
return Object.assign({}, state, { currentSnapshotIndex });
|
||||
}
|
||||
|
||||
case actions.objects.RECEIVE_COMMENT: {
|
||||
let found = false;
|
||||
const comments = state.comments.map(e => {
|
||||
|
Loading…
Reference in New Issue
Block a user