-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fabric v6 support #48
Comments
try this histroy.ts import { Canvas } from 'fabric';
type HistoryEventCallback = () => void;
class HistoryFeature {
private canvas: Canvas;
private historyUndo: string[];
private historyRedo: string[];
private extraProps: string[];
private historyNextState: string;
private historyProcessing: boolean;
constructor(canvas: Canvas) {
this.canvas = canvas;
this.historyUndo = [];
this.historyRedo = [];
this.extraProps = ['selectable', 'editable'];
this.historyNextState = this._historyNext();
this.historyProcessing = false;
this._historyInit();
}
private _historyNext(): string {
return JSON.stringify(this.canvas.toDatalessJSON(this.extraProps));
}
private _historyEvents() {
return {
'object:added': this._historySaveAction.bind(this),
'object:removed': this._historySaveAction.bind(this),
'object:modified': this._historySaveAction.bind(this),
'object:skewing': this._historySaveAction.bind(this),
};
}
private _historyInit() {
this.canvas.on(this._historyEvents());
}
private _historyDispose() {
this.canvas.off(this._historyEvents());
}
private _historySaveAction() {
if (this.historyProcessing) return;
const json = this.historyNextState;
this.historyUndo.push(json);
this.historyNextState = this._historyNext();
this.canvas.fire('history:append', { json: json });
}
undo(callback?: HistoryEventCallback) {
this.historyProcessing = true;
const history = this.historyUndo.pop();
if (history) {
this.historyRedo.push(this._historyNext());
this.historyNextState = history;
this._loadHistory(history, 'history:undo', callback);
} else {
this.historyProcessing = false;
}
}
redo(callback?: HistoryEventCallback) {
this.historyProcessing = true;
const history = this.historyRedo.pop();
if (history) {
this.historyUndo.push(this._historyNext());
this.historyNextState = history;
this._loadHistory(history, 'history:redo', callback);
} else {
this.historyProcessing = false;
}
}
private _loadHistory(history: string, event: string, callback?: HistoryEventCallback) {
this.canvas.loadFromJSON(history, () => {
this.canvas.renderAll();
this.canvas.fire(event);
this.historyProcessing = false;
if (callback) callback();
});
}
clearHistory() {
this.historyUndo = [];
this.historyRedo = [];
this.canvas.fire('history:clear');
}
onHistory() {
this.historyProcessing = false;
this._historySaveAction();
}
canUndo(): boolean {
return this.historyUndo.length > 0;
}
canRedo(): boolean {
return this.historyRedo.length > 0;
}
offHistory() {
this.historyProcessing = true;
}
}
export default HistoryFeature; |
In Dart I did this because I believe it is more efficient than saving the entire canvas at all times import 'package:rava_frontend/src/shared/js_interop/fabric_interop.dart';
extension ListExtension on List {
List splice2(int index, [num howMany = 0, dynamic elements]) {
var endIndex = index + howMany.truncate();
removeRange(index, endIndex >= length ? length : endIndex);
if (elements != null) {
insertAll(index, elements is List ? elements : [elements]);
}
return this;
}
List splice(int start, [int deleteCount = 0]) {
final deletedElements = sublist(start, start + deleteCount);
removeRange(start, start + deleteCount);
return deletedElements;
}
}
enum StateAction { create, modify, remove, none }
class TransformCommand {
FabricObject element;
Map<String, dynamic> state = {};
Map<String, dynamic> prevState = {};
List<String> statePropertiesKeys = [];
StateAction action = StateAction.none;
CommandHistory? history;
TransformCommand(this.element, this.action) {
switch (action) {
case StateAction.create:
element.saveState(); // save state for initialize
init();
break;
case StateAction.modify:
init();
element.saveState(); // save new state
break;
case StateAction.remove:
break;
case StateAction.none:
}
}
void init() {
_initStateProperties();
_saveState();
_savePrevState();
}
/// redo
void execute() {
// print('TransformCommand execute ${action.name}');
if (action == StateAction.create) {
history?.canvas.add(element);
} else if (action == StateAction.remove) {
history?.canvas.remove(element);
} else {
_restoreState();
element.setCoords();
}
}
/// undo
void undo() {
// print('TransformCommand undo ${action.name}');
if (action == StateAction.create) {
history?.canvas.remove(element);
} else if (action == StateAction.remove) {
history?.canvas.add(element);
} else {
_restorePrevState();
element.setCoords();
}
}
// private
void _initStateProperties() {
statePropertiesKeys = element.statePropertiesKeys;
}
void _restoreState() {
_restore(state);
}
void _restorePrevState() {
_restore(prevState);
}
void _restore(Map<String, dynamic> state) {
for (var prop in statePropertiesKeys) {
element.set(prop, state[prop]);
}
}
void _saveState() {
for (var prop in statePropertiesKeys) {
state[prop] = element.get(prop);
}
//print('_saveState $state');
}
void _savePrevState() {
final oldState = element.statePropertiesAsMap;
if (oldState != null) {
for (var prop in statePropertiesKeys) {
prevState[prop] = oldState[prop];
}
} else {
throw Exception(
'TransformCommand@_savePrevState não foi possivel obter o estado anterior');
}
//print('_savePrevState $oldState');
}
}
class CommandHistory {
final commands = <TransformCommand>[];
int index = 0;
final Canvas canvas;
CommandHistory(this.canvas);
int getIndex() {
return index;
}
CommandHistory add(TransformCommand command) {
command.history = this;
if (commands.isNotEmpty) {
commands.splice(index, commands.length - index);
}
commands.add(command);
index++;
return this;
}
/// undo
CommandHistory back() {
//print(' CommandHistory back() ${commands.length} $index');
if (index > 0) {
final command = commands[--index];
command.undo();
}
return this;
}
/// redo
CommandHistory forward() {
//print(' CommandHistory forward() ${commands.length} $index');
if (index < commands.length) {
final command = commands[index++];
command.execute();
}
return this;
}
CommandHistory clear() {
commands.clear();
commands.length = 0;
index = 0;
return this;
}
}
...
final canvas = Canvas( canvasEl );
final history = CommandHistory(canvas);
...
void onModifiedObject(ModifiedEvent event) {
final target = event.target;
history.add(TransformCommand(target, StateAction.modify));
} |
4 tasks
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Fabric v6 is currently in beta, "pretty close to the official release" (fabricjs/fabric.js#8299 (comment)). Would be awesome to see fabric-history supporting this new version.
Currently "import 'fabric-history';" crashes my application when used with fabric v6 (
ERROR Error: Uncaught (in promise): ReferenceError: fabric is not defined
), probably because of the changes in imports mentioned here.The text was updated successfully, but these errors were encountered: