Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Commit dd86afc

Browse files
authored
Merge pull request #175 from plotly/hide-undo-redo
Hide undo redo
2 parents 3cdcc0e + 55802ef commit dd86afc

File tree

5 files changed

+94
-30
lines changed

5 files changed

+94
-30
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [UNRELEASED]
6+
### Changed
7+
- Undo/redo toolbar is removed by default, unless `config.show_undo_redo=true` is provided. The CSS hack `._dash-undo-redo:{display:none;}` is no longer needed [#175](https://github.com/plotly/dash-renderer/pull/175)
8+
59
## [0.24.0] - 2019-05-15
610
### Fixed
711
- Fix regression on handling PreventUpdate (204 NO CONTENT) [#170](https://github.com/plotly/dash-renderer/pull/170)

Diff for: src/AppContainer.react.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ class UnconnectedAppContainer extends React.Component {
3030
if (type(config) === 'Null') {
3131
return <div className="_dash-loading">Loading...</div>;
3232
}
33+
const {show_undo_redo} = config;
3334
return (
3435
<React.Fragment>
35-
<Toolbar />
36+
{show_undo_redo ? <Toolbar /> : null}
3637
<APIController />
3738
<DocumentTitle />
3839
<Loading />

Diff for: src/actions/index.js

+8-22
Original file line numberDiff line numberDiff line change
@@ -131,34 +131,20 @@ export function redo() {
131131
};
132132
}
133133

134+
const UNDO = createAction('UNDO')();
134135
export function undo() {
135-
return function(dispatch, getState) {
136-
const history = getState().history;
137-
dispatch(createAction('UNDO')());
138-
const previous = history.past[history.past.length - 1];
139-
140-
// Update props
141-
dispatch(
142-
createAction('UNDO_PROP_CHANGE')({
143-
itempath: getState().paths[previous.id],
144-
props: previous.props,
145-
})
146-
);
147-
148-
// Notify observers
149-
dispatch(
150-
notifyObservers({
151-
id: previous.id,
152-
props: previous.props,
153-
})
154-
);
155-
};
136+
return undo_revert(UNDO);
156137
}
157138

139+
const REVERT = createAction('REVERT')();
158140
export function revert() {
141+
return undo_revert(REVERT);
142+
}
143+
144+
function undo_revert(undo_or_revert) {
159145
return function(dispatch, getState) {
160146
const history = getState().history;
161-
dispatch(createAction('REVERT')());
147+
dispatch(undo_or_revert);
162148
const previous = history.past[history.past.length - 1];
163149

164150
// Update props
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div id="react-entry-point"><div class="_dash-undo-redo" data-radium="true" style="position: fixed; bottom: 30px; left: 30px; font-size: 20px; text-align: center; z-index: 9999; background-color: rgba(255, 255, 255, 0.9);"><div data-radium="true" style="position: relative;"></div></div><div>Basic string3.14<div id="p.c.4" class="my-class" title="tooltip" style="color: red; font-size: 30px;">Child div with basic string</div><div id="p.c.5"></div><div id="p.c.6"><div id="p.c.6.p.c.0">Grandchild div</div><div id="p.c.6.p.c.1"><div id="p.c.6.p.c.1.p.c.0">Great grandchild</div>3.14159another basic string</div><div id="p.c.6.p.c.2"><div id="p.c.6.p.c.2.p.c.0"><div id="p.c.6.p.c.2.p.c.0.p.c"><div id="p.c.6.p.c.2.p.c.0.p.c.p.c.0"><div id="p.c.6.p.c.2.p.c.0.p.c.p.c.0.p.c.0"></div><div id="p.c.6.p.c.2.p.c.0.p.c.p.c.0.p.c.2"></div></div></div></div></div></div></div></div>
1+
<div id="react-entry-point"><div>Basic string3.14<div id="p.c.4" class="my-class" title="tooltip" style="color: red; font-size: 30px;">Child div with basic string</div><div id="p.c.5"></div><div id="p.c.6"><div id="p.c.6.p.c.0">Grandchild div</div><div id="p.c.6.p.c.1"><div id="p.c.6.p.c.1.p.c.0">Great grandchild</div>3.14159another basic string</div><div id="p.c.6.p.c.2"><div id="p.c.6.p.c.2.p.c.0"><div id="p.c.6.p.c.2.p.c.0.p.c"><div id="p.c.6.p.c.2.p.c.0.p.c.p.c.0"><div id="p.c.6.p.c.2.p.c.0.p.c.p.c.0.p.c.0"></div><div id="p.c.6.p.c.2.p.c.0.p.c.p.c.0.p.c.2"></div></div></div></div></div></div></div></div>

Diff for: tests/test_render.py

+79-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- coding: UTF-8 -*-
12
import os
23
import textwrap
34

@@ -109,7 +110,6 @@ def request_queue_assertions(
109110
if expected_length is not None:
110111
self.assertEqual(len(request_queue), expected_length)
111112

112-
113113
def test_initial_state(self):
114114
app = Dash(__name__)
115115
my_class_attrs = {
@@ -154,11 +154,12 @@ def test_initial_state(self):
154154
self.startServer(app)
155155
el = self.wait_for_element_by_css_selector('#react-entry-point')
156156

157+
# Note: this .html file shows there's no undo/redo button by default
157158
_dash_app_content_html = os.path.join(
158159
os.path.dirname(__file__),
159160
'test_assets', 'initial_state_dash_app_content.html')
160161
with open(_dash_app_content_html) as fp:
161-
rendered_dom = BeautifulSoup(fp.read(), 'lxml')
162+
rendered_dom = BeautifulSoup(fp.read().strip(), 'lxml')
162163
fetched_dom = BeautifulSoup(el.get_attribute('outerHTML'), 'lxml')
163164

164165
self.assertEqual(
@@ -211,6 +212,79 @@ def test_initial_state(self):
211212

212213
self.assertTrue(self.is_console_clean())
213214

215+
def click_undo(self):
216+
undo_selector = '._dash-undo-redo span:first-child div:last-child'
217+
undo = self.wait_for_element_by_css_selector(undo_selector)
218+
self.wait_for_text_to_equal(undo_selector, 'undo')
219+
undo.click()
220+
221+
def click_redo(self):
222+
redo_selector = '._dash-undo-redo span:last-child div:last-child'
223+
self.wait_for_text_to_equal(redo_selector, 'redo')
224+
redo = self.wait_for_element_by_css_selector(redo_selector)
225+
redo.click()
226+
227+
def check_undo_redo_exist(self, has_undo, has_redo):
228+
selector = '._dash-undo-redo span div:last-child'
229+
els = self.driver.find_elements_by_css_selector(selector)
230+
texts = (['undo'] if has_undo else []) + (['redo'] if has_redo else [])
231+
232+
self.assertEqual(len(els), len(texts))
233+
for el, text in zip(els, texts):
234+
self.assertEqual(el.text, text)
235+
236+
def test_undo_redo(self):
237+
app = Dash(__name__, show_undo_redo=True)
238+
app.layout = html.Div([dcc.Input(id='a'), html.Div(id='b')])
239+
240+
@app.callback(Output('b', 'children'), [Input('a', 'value')])
241+
def set_b(a):
242+
return a
243+
244+
self.startServer(app)
245+
246+
a = self.wait_for_element_by_css_selector('#a')
247+
a.send_keys('xyz')
248+
249+
self.wait_for_text_to_equal('#b', 'xyz')
250+
self.check_undo_redo_exist(True, False)
251+
252+
self.click_undo()
253+
self.wait_for_text_to_equal('#b', 'xy')
254+
self.check_undo_redo_exist(True, True)
255+
256+
self.click_undo()
257+
self.wait_for_text_to_equal('#b', 'x')
258+
self.check_undo_redo_exist(True, True)
259+
260+
self.click_redo()
261+
self.wait_for_text_to_equal('#b', 'xy')
262+
self.check_undo_redo_exist(True, True)
263+
264+
self.percy_snapshot(name='undo-redo')
265+
266+
self.click_undo()
267+
self.click_undo()
268+
self.wait_for_text_to_equal('#b', '')
269+
self.check_undo_redo_exist(False, True)
270+
271+
def test_no_undo_redo(self):
272+
app = Dash(__name__)
273+
app.layout = html.Div([dcc.Input(id='a'), html.Div(id='b')])
274+
275+
@app.callback(Output('b', 'children'), [Input('a', 'value')])
276+
def set_b(a):
277+
return a
278+
279+
self.startServer(app)
280+
281+
a = self.wait_for_element_by_css_selector('#a')
282+
a.send_keys('xyz')
283+
284+
self.wait_for_text_to_equal('#b', 'xyz')
285+
toolbar = self.driver.find_elements_by_css_selector('._dash-undo-redo')
286+
self.assertEqual(len(toolbar), 0)
287+
214288
def test_array_of_falsy_child(self):
215289
app = Dash(__name__)
216290
app.layout = html.Div(id='nully-wrapper', children=[0])
@@ -328,10 +402,9 @@ def update_input(value):
328402
'#react-entry-point').get_attribute('innerHTML'),
329403
'lxml').select_one('#output > div').contents
330404

331-
self.assertTrue(
332-
pad_input.attrs == {'type': 'text', 'id': 'sub-input-1', 'value': 'sub input initial value'}
333-
and pad_input.name == 'input',
334-
"pad input is correctly rendered")
405+
self.assertEqual(pad_input.attrs['value'], 'sub input initial value')
406+
self.assertEqual(pad_input.attrs['id'], 'sub-input-1')
407+
self.assertEqual(pad_input.name, 'input')
335408

336409
self.assertTrue(
337410
pad_div.text == pad_input.attrs['value']

0 commit comments

Comments
 (0)