Skip to content

Commit

Permalink
[RFC] refactoring undo/redo - add undostack management
Browse files Browse the repository at this point in the history
  • Loading branch information
Viglino committed Apr 21, 2021
1 parent 18d1dac commit 7405346
Show file tree
Hide file tree
Showing 7 changed files with 387 additions and 100 deletions.
6 changes: 3 additions & 3 deletions examples/interaction/map.interaction.undocustom.html
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ <h1>ol-ext: Undo/redo custom</h1>
style = s.before;
vector.changed();
},
// redo function: set next style
function(s){
// redo function: reset the style
function(s) {
style = s.after;
vector.changed();
}
Expand All @@ -206,7 +206,7 @@ <h1>ol-ext: Undo/redo custom</h1>
// use blockStart / blockEnd to stack many undo in a same action
// undoInteraction.blockStart();
// Add undo style action
undoInteraction.push("style", { before: before, after: style });
undoInteraction.push('style', { before: before, after: style });
// undoInteraction.blockEnd();
}
setStyle();
Expand Down
106 changes: 91 additions & 15 deletions examples/interaction/map.interaction.undoredo.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
<html>
<head>
<!--
Copyright (c) 2015-2018 Jean-Marc VIGLINO,
released under CeCILL-B (french BSD like) licence: http://www.cecill.info/
-->
<title>ol-ext: Undo/redo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
Expand Down Expand Up @@ -57,6 +55,26 @@
background-color: currentColor!important;
border-color: #000;
}
.options ul {
cursor: pointer;
margin: 0;
padding: 0;
}
.options li {
padding: 0 .5em;
}
.options li:hover {
background-color: #369;
color: #fff;
}
.options .redo li {
opacity: .4;
}
.options button {
margin: auto;
display: block;
}

</style>

</head>
Expand All @@ -77,14 +95,27 @@ <h1>ol-ext: Undo/redo</h1>
<p>
Just add the interaction to the map to get it working.
It will watch all vector sources and all interactions in the map.
Use the <i>undo</i> and <i>redo</i> method to undo the last recorded operations.
<br/>
Undo can be stacked in a block to make them undoing at a time.
Just enclose the operation between a <i>blockStart()</i> and <i>blockEnd()</i>.
<br/>
You can stack operation on a map by enclosing operations beetween
<i>undoblockstart</i> and <i>undoblockend</i> events
(<i>map.dispatchEvent('undoblockstart')</i> and <i>map.dispatchEvent('undoblockend')</i>).
<ul>
<li>
Use the <i>undo()</i> and <i>redo()</i> method to undo the last recorded operations.
</li><li>
The <i>shift()</i> method lets you remove an operation from the undo stack.
</li><li>
Use the <i>getStack()</i> method to have a list of current undo actions in the stack.
</li><li>
<i>change:add</i>, <i>change:remove</i> or <i>change:clear</i> events
are triggered when the undo stack change.
</li><li>
Undo can be stacked in a block to make them undoing at a time.
Just enclose the operation between a <i>blockStart()</i> and <i>blockEnd()</i>.
</li><li>
You can stack operation on a map by enclosing operations beetween
<i>undoblockstart</i> and <i>undoblockend</i> events
(<i>map.dispatchEvent('undoblockstart')</i> and <i>map.dispatchEvent('undoblockend')</i>).
</li><li>
Use <i>setMaxLength()</i> and <i>setMaxSize()</i> methods to set the max stack length and max heap size.
</li>
</ul>
</p>
Cancelable operations:
<ul>
Expand Down Expand Up @@ -134,16 +165,25 @@ <h1>ol-ext: Undo/redo</h1>
</a>
</li>
<li>
<b>ol/interaction/FillAttribute</b>
<b>ol/interaction/FillAttribute</b> (see <a href="./map.interaction.undoredo2.html">example</a>)
</li>
</ul>
<p>
To add your own cancelable actions, look at
<a href="./map.interaction.undocustom.html">this example</a>.
</p>
</div>

<!-- Map div -->
<div id="map" style="width:600px; height:400px;"></div>

<div class="options" >
<div id="info"></div>
<h2>Undo stack</h2>
<button onclick="undoInteraction.clear()">clear stack...</button>
<hr/>
<ul class="undo"></ul>
<hr/>
<ul class="redo"></ul>
</div>

<script type="text/javascript">
Expand Down Expand Up @@ -177,7 +217,6 @@ <h1>ol-ext: Undo/redo</h1>
]
});


// Main control bar
var mainbar = new ol.control.Bar();
map.addControl(mainbar);
Expand All @@ -189,8 +228,8 @@ <h1>ol-ext: Undo/redo</h1>
});
mainbar.addControl(editbar);

// Add a fill interaction toset color attribute
var fill = new ol.interaction.FillAttribute({}, { color: [255,0,0] });
// Add a fill interaction to set color attribute
var fill = new ol.interaction.FillAttribute({ name: 'fill color' }, { color: [255,0,0] });
editbar.addControl(new ol.control.Toggle({
html: '<i class="fa fa-paint-brush"></i>',
title: 'fill color attribut',
Expand Down Expand Up @@ -230,6 +269,43 @@ <h1>ol-ext: Undo/redo</h1>
}
});

// Handle undo/redo stack
undoInteraction.on('stack:add', function (e) {
// New action element
if (!e.action.element) {
var elt = e.action.element = $('<li>').text(e.action.name || e.action.type);
elt.click(function() {
// undo or redo stack
if (elt.parent().hasClass('undo')) {
undoInteraction.undo();
} else {
undoInteraction.redo();
}
})
}
// Append to undo stack
$('.options .undo').append(e.action.element);
e.action.element.attr('title', 'undo');
if (!undoInteraction.hasRedo()) $('.options .redo').html('');
console.log(undoInteraction.length()+' undo | '+undoInteraction.length('redo')+' redo')
});
// Append to redo stack
undoInteraction.on('stack:remove', function (e) {
if (e.shift) {
$('.options .undo li').first().remove();
} else {
$('.options .redo').prepend($('.options .undo li').last());
}
e.action.element.attr('title', 'redo');
console.log(undoInteraction.length()+' undo | '+undoInteraction.length('redo')+' redo')
});
// Clear stack
undoInteraction.on('stack:clear', function (e) {
$('.options .undo').html('');
$('.options .redo').html('');
console.log(undoInteraction.length()+' undo | '+undoInteraction.length('redo')+' redo')
});

// Add buttons to the bar
var bar = new ol.control.Bar({
group: true,
Expand Down
1 change: 0 additions & 1 deletion src/control/EditBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ ol_control_EditBar.prototype._setSelectInteraction = function (options) {
handleClick: function(e) {
// Delete selection
del.delete(selectCtrl.getInteraction().getFeatures());
console.log('del')
var evt = {
type: 'select',
selected: [],
Expand Down
3 changes: 2 additions & 1 deletion src/interaction/DrawRegular.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,9 @@ ol_interaction_DrawRegular.prototype.start_ = function(evt) {
ol_interaction_DrawRegular.prototype.end_ = function(evt) {
this.coord_ = evt.coordinate;
this.started_ = false;
var g = this.getGeom_();
// Add new feature
if (this.coord_ && this.center_[0]!=this.coord_[0] && this.center_[1]!=this.coord_[1]) {
if (this.coord_ && (this.center_[0]!==this.coord_[0] || this.center_[1]!==this.coord_[1])) {
var f = this.feature_;
if (this.geometryName_) f.setGeometryName(this.geometryName_)

Expand Down
57 changes: 48 additions & 9 deletions src/interaction/FillAttribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {click as ol_events_condition_click} from 'ol/events/condition'
* @fires setattributeend
* @param {*} options extentol.interaction.Select options
* @param {boolean} options.active activate the interaction on start, default true
* @param {boolean} options.cursor use a paint bucket cursor, default true
* @param {string=} options.name
* @param {boolean|string} options.cursor interaction cursor if false use default, default use a paint bucket cursor
* @param {*} properties The properties as key/value
*/
var ol_interaction_FillAttribute = function(options, properties) {
Expand All @@ -18,14 +19,15 @@ var ol_interaction_FillAttribute = function(options, properties) {
if (!options.condition) options.condition = ol_events_condition_click;
ol_interaction_Select.call(this, options);
this.setActive(options.active!==false)
this.set('name', options.name);

this._attributes = properties;
this.on('select', function(e) {
this.getFeatures().clear();
this.fill(e.selected, this._attributes);
}.bind(this));

if (options.cursor!==false) {
if (options.cursor === undefined) {
var canvas = document.createElement('CANVAS');
canvas.width = canvas.height = 32;
var ctx = canvas.getContext("2d");
Expand Down Expand Up @@ -59,9 +61,26 @@ var ol_interaction_FillAttribute = function(options, properties) {

this._cursor = 'url('+canvas.toDataURL()+') 0 13, auto';
}
if (options.cursor) {
this._cursor = options.cursor;
}
};
ol_ext_inherits(ol_interaction_FillAttribute, ol_interaction_Select);

/** Define the interaction cursor
* @param {string} cursor CSS cursor
*/
ol_interaction_FillAttribute.prototype.setCursor = function(cursor) {
this._cursor = cursor;
};

/** Get the interaction cursor
* @return {string} cursor
*/
ol_interaction_FillAttribute.prototype.getCursor = function() {
return this._cursor;
};

/** Activate the interaction
* @param {boolean} active
*/
Expand Down Expand Up @@ -116,15 +135,35 @@ ol_interaction_FillAttribute.prototype.getAttribute = function(key) {
*/
ol_interaction_FillAttribute.prototype.fill = function(features, properties) {
if (features.length && properties) {
this.dispatchEvent({ type: 'setattributestart', features: features, properties: properties });

features.forEach(function(f) {
// Test changes
var changes = false;
for (var i=0, f; f = features[i]; i++) {
for (var p in properties) {
f.set(p, properties[p]);
if (f.get(p) !== properties[p]) changes = true;
}
});

this.dispatchEvent({ type: 'setattributeend', features: features, properties: properties });
if (changes) break;
};

// Set Attributes
if (changes) {
this.dispatchEvent({
type: 'setattributestart',
features: features,
properties: properties
});

features.forEach(function(f) {
for (var p in properties) {
f.set(p, properties[p]);
}
});

this.dispatchEvent({
type: 'setattributeend',
features: features,
properties: properties
});
}
}
};

Expand Down
13 changes: 10 additions & 3 deletions src/interaction/Offset.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,14 @@ ol_interaction_Offset.prototype.handleDownEvent_ = function(e) {
this.current_ = this.getFeatureAtPixel_(e);
if (this.current_) {
this.currentStyle_ = this.current_.feature.getStyle();
this.current_.feature.setStyle(this._style(this.current_.feature));
if (this.source_ && (this.get('duplicate') || e.originalEvent.ctrlKey)) {
this.current_.feature = this.current_.feature.clone();
this.current_.feature.setStyle(this._style(this.current_.feature));
this.source_.addFeature(this.current_.feature);
} else {
// Modify the current feature
this.dispatchEvent({ type:'modifystart', features: [ this.current_.feature ] });
this.current_.feature.setStyle(this._style(this.current_.feature));
this._modifystart = true;
}
this.dispatchEvent({ type:'offsetstart', feature: this.current_.feature, offset: 0 });
return true;
Expand All @@ -150,6 +151,10 @@ ol_interaction_Offset.prototype.handleDownEvent_ = function(e) {
* @private
*/
ol_interaction_Offset.prototype.handleDragEvent_ = function(e) {
if (this._modifystart) {
this.dispatchEvent({ type:'modifystart', features: [ this.current_.feature ] });
this._modifystart = false;
}
var p = this.current_.geom.getClosestPoint(e.coordinate);
var d = ol_coordinate_dist2d(p, e.coordinate);
var seg, v1, v2, offset;
Expand Down Expand Up @@ -196,7 +201,9 @@ ol_interaction_Offset.prototype.handleDragEvent_ = function(e) {
* @private
*/
ol_interaction_Offset.prototype.handleUpEvent_ = function(e) {
this.dispatchEvent({ type:'offsetend', feature: this.current_.feature, coordinate: e.coordinate });
if (!this._modifystart) {
this.dispatchEvent({ type:'offsetend', feature: this.current_.feature, coordinate: e.coordinate });
}
this.current_.feature.setStyle(this.currentStyle_);
this.current_ = false;
};
Expand Down

0 comments on commit 7405346

Please sign in to comment.