Skip to content

Commit e483f87

Browse files
Merge branch 'dev' into Pytest-Setup
2 parents b3e1837 + c0370e4 commit e483f87

10 files changed

Lines changed: 216 additions & 18 deletions

File tree

js/api/ApiProvider.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ const ApiProvider = ({ children }) => {
178178
case 'env_update':
179179
apiHandlers.current.onEnvUpdate(cmd.data);
180180
break;
181+
case 'undo_state':
182+
apiHandlers.current.onUndoState(cmd);
183+
break;
181184

182185
default:
183186
// eslint-disable-next-line no-console
@@ -264,6 +267,13 @@ const ApiProvider = ({ children }) => {
264267
});
265268
};
266269

270+
const sendUndo = (envID) => {
271+
sendSocketMessage({
272+
cmd: 'undo',
273+
eid: envID,
274+
});
275+
};
276+
267277
// Send request to delete an environment
268278
const sendEnvDelete = (envID, previousEnv) => {
269279
sendSocketMessage({
@@ -362,6 +372,7 @@ const ApiProvider = ({ children }) => {
362372
sendPaneLayoutUpdate,
363373
sendPaneMessage,
364374
sendSaveAll,
375+
sendUndo,
365376
sessionInfo,
366377
setConnected,
367378
toggleOnlineState,

js/main.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ const App = () => {
112112
sendEnvSave,
113113
sendLayoutsSave,
114114
sendPaneClose,
115+
sendUndo,
115116
sendPaneLayoutUpdate,
116117
sessionInfo,
117118
toggleOnlineState,
@@ -134,6 +135,8 @@ const App = () => {
134135
layout: [],
135136
});
136137

138+
const [undoCounts, setUndoCounts] = useState({});
139+
137140
// user-changeable
138141
const [showEnvModal, setShowEnvModal] = useState(false);
139142
const [showViewModal, setShowViewModal] = useState(false);
@@ -343,6 +346,10 @@ const App = () => {
343346
else relayout();
344347
};
345348

349+
const onUndoState = ({ eid, count }) => {
350+
setUndoCounts((prev) => ({ ...prev, [eid]: count }));
351+
};
352+
346353
const onEnvUpdate = (data) => {
347354
var layoutLists = storeMeta.layoutLists;
348355
for (var envIdx in data) {
@@ -976,6 +983,11 @@ const App = () => {
976983
}}
977984
onViewChange={updateToLayout}
978985
onViewManageButton={() => setShowViewModal(!showViewModal)}
986+
canUndo={
987+
selection.envIDs.length === 1 &&
988+
(undoCounts[selection.envIDs[0]] || 0) > 0
989+
}
990+
onUndoButton={() => sendUndo(selection.envIDs[0])}
979991
onEnvSelect={onEnvSelect}
980992
onExportHtml={exportCurrentEnvToHtml}
981993
/>
@@ -1011,6 +1023,7 @@ const App = () => {
10111023
onReloadMessage,
10121024
onEnvUpdate,
10131025
onCloseMessage,
1026+
onUndoState,
10141027
onDisconnect,
10151028
};
10161029

js/topbar/EnvControls.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ function EnvControls(props) {
155155
data-toggle="tooltip"
156156
title={confirmClear ? 'Are you sure?' : 'Clear Current Environment'}
157157
data-placement="bottom"
158-
className={confirmClear ? 'btn btn-warning' : 'btn btn-default'}
158+
className={confirmClear ? 'btn btn-warning btn-sm' : 'btn btn-default btn-sm'}
159159
disabled={!(connected && envIDs.length > 0 && !readonly)}
160160
onClick={() => {
161161
if (confirmClear) {
@@ -171,7 +171,7 @@ function EnvControls(props) {
171171
data-toggle="tooltip"
172172
title="Save All Environments"
173173
data-placement="bottom"
174-
className="btn btn-default"
174+
className="btn btn-default btn-sm"
175175
disabled={!(connected && !readonly)}
176176
onClick={sendSaveAll}
177177
>
@@ -181,7 +181,7 @@ function EnvControls(props) {
181181
data-toggle="tooltip"
182182
title="Manage Environments"
183183
data-placement="bottom"
184-
className="btn btn-default"
184+
className="btn btn-default btn-sm"
185185
disabled={!(connected && envIDs.length > 0 && !readonly)}
186186
onClick={onEnvManageButton}
187187
>
@@ -193,7 +193,7 @@ function EnvControls(props) {
193193
title="Show All Windows from All Environments"
194194
data-placement="bottom"
195195
className={
196-
showAllEnvWindows ? 'btn btn-primary' : 'btn btn-default'
196+
showAllEnvWindows ? 'btn btn-primary btn-sm' : 'btn btn-default btn-sm'
197197
}
198198
onClick={onToggleShowAll}
199199
>

js/topbar/ViewControls.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ function ViewControls(props) {
2020
onViewManageButton,
2121
onRepackButton,
2222
onViewChange,
23+
onUndoButton,
24+
canUndo,
2325
onExportHtml,
2426
} = props;
2527

@@ -100,7 +102,7 @@ function ViewControls(props) {
100102
<div className="btn-group navbar-btn" role="group" aria-label="View:">
101103
<div className="btn-group" role="group">
102104
<button
103-
className="btn btn-default dropdown-toggle"
105+
className="btn btn-default btn-sm dropdown-toggle"
104106
type="button"
105107
id="viewDropdown"
106108
data-toggle="dropdown"
@@ -120,7 +122,7 @@ function ViewControls(props) {
120122
data-toggle="tooltip"
121123
title="Repack"
122124
data-placement="bottom"
123-
className="btn btn-default"
125+
className="btn btn-default btn-sm"
124126
onClick={onRepackButton}
125127
>
126128
<span className="glyphicon glyphicon-th" />
@@ -129,17 +131,30 @@ function ViewControls(props) {
129131
data-toggle="tooltip"
130132
title="Manage Views"
131133
data-placement="bottom"
132-
className="btn btn-default"
134+
className="btn btn-default btn-sm"
133135
disabled={!(connected && envIDs.length > 0 && !readonly)}
134136
onClick={onViewManageButton}
135137
>
136138
<span className="glyphicon glyphicon-folder-open" />
137139
</button>
140+
<button
141+
data-toggle="tooltip"
142+
title="Undo Close"
143+
data-placement="bottom"
144+
className="btn btn-default btn-sm"
145+
disabled={!(connected && envIDs.length === 1 && !readonly && canUndo)}
146+
onClick={onUndoButton}
147+
>
148+
<span
149+
className="glyphicon glyphicon-share-alt"
150+
style={{ transform: 'scaleX(-1)' }}
151+
/>
152+
</button>
138153
<button
139154
data-toggle="tooltip"
140155
title="Upload Dashboard JSON"
141156
data-placement="bottom"
142-
className="btn btn-default"
157+
className="btn btn-default btn-sm"
143158
onClick={() => fileInputRef.current && fileInputRef.current.click()}
144159
disabled={!(connected && !readonly)}
145160
aria-label="Upload JSON file"
@@ -158,7 +173,7 @@ function ViewControls(props) {
158173
data-toggle="tooltip"
159174
title="Export as HTML"
160175
data-placement="bottom"
161-
className="btn btn-default"
176+
className="btn btn-default btn-sm"
162177
disabled={!(connected && envIDs.length > 0)}
163178
onClick={onExportHtml}
164179
aria-label="Export as HTML"

py/visdom/server/defaults.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@
1717
DEFAULT_MAX_IMAGE_HISTORY = 4
1818
DEFAULT_MAX_OLD_CONTENT = 50
1919
DEFAULT_MAX_TEXT_LINES = 500
20+
DEFAULT_MAX_UNDO_HISTORY = 4
21+
UNDO_DIRNAME = ".undo"

py/visdom/server/handlers/socket_handlers.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
send_to_sources,
3636
broadcast,
3737
escape_eid,
38+
push_deleted,
39+
pop_deleted,
40+
clear_deleted,
41+
broadcast_undo_state,
3842
)
3943
from visdom.server.defaults import MAX_SOCKET_WAIT
4044

@@ -101,17 +105,45 @@ def on_message(self, message):
101105
elif cmd == "close":
102106
if "data" in msg and "eid" in msg:
103107
logging.info(f"closing window {msg['data']}")
108+
eid = escape_eid(msg["eid"])
109+
if eid not in self.state:
110+
return
111+
p_data = self.state[eid]["jsons"].pop(msg["data"], None)
112+
if p_data is not None:
113+
push_deleted(self.env_path, eid, msg["data"], p_data)
104114
env = self.state.get(msg["eid"])
105115
if env is None:
106116
return
107117
p_data = env["jsons"].pop(msg["data"], None)
108118
event = {
109119
"event_type": "close",
110120
"target": msg["data"],
111-
"eid": msg["eid"],
121+
"eid": eid,
112122
"pane_data": p_data,
113123
}
114124
send_to_sources(self, event)
125+
broadcast_undo_state(self, eid, self.env_path)
126+
127+
elif cmd == "undo":
128+
if "eid" in msg:
129+
eid = escape_eid(msg["eid"])
130+
if eid not in self.state:
131+
return
132+
popped = pop_deleted(self.env_path, eid)
133+
if popped:
134+
win_id, p_data = popped
135+
env = self.state[eid]["jsons"]
136+
max_i = max((p.get("i", -1) for p in env.values()), default=-1)
137+
p_data["i"] = max_i + 1
138+
env[win_id] = p_data
139+
broadcast_msg = dict(p_data)
140+
broadcast_msg["eid"] = eid
141+
broadcast(
142+
self,
143+
json.dumps(broadcast_msg, cls=NanSafeEncoder),
144+
eid,
145+
)
146+
broadcast_undo_state(self, eid, self.env_path)
115147

116148
elif cmd == "save":
117149
# save localStorage window metadata
@@ -137,6 +169,7 @@ def on_message(self, message):
137169
return
138170
logging.info(f"closing environment {eid}")
139171
self.state.pop(eid, None)
172+
clear_deleted(self.env_path, eid)
140173
if self.env_path is not None:
141174
p = os.path.join(self.env_path, "{0}.json".format(eid))
142175
if os.path.exists(p):

py/visdom/server/handlers/web_handlers.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
update_window,
4949
hash_password,
5050
stringify,
51+
push_deleted,
52+
clear_deleted,
5153
)
5254
from visdom.server.handlers.base_handlers import BaseHandler
5355

@@ -410,12 +412,10 @@ def wrap_func(handler, args):
410412

411413
keys = list(handler.state[eid]["jsons"].keys()) if win is None else [win]
412414
for win in keys:
413-
handler.state[eid]["jsons"].pop(win, None)
414-
broadcast(
415-
handler,
416-
json.dumps({"command": "close", "data": win}, cls=NanSafeEncoder),
417-
eid,
418-
)
415+
p_data = handler.state[eid]["jsons"].pop(win, None)
416+
if p_data is not None:
417+
push_deleted(handler.env_path, eid, win, p_data)
418+
broadcast(handler, json.dumps({"command": "close", "data": win}), eid)
419419

420420
@check_auth
421421
def post(self):
@@ -434,6 +434,7 @@ def wrap_func(handler, args):
434434
if eid == "main":
435435
return
436436
handler.state.pop(eid, None)
437+
clear_deleted(handler.env_path, eid)
437438
if handler.env_path is not None:
438439
p = os.path.join(handler.env_path, "{0}.json".format(eid))
439440
if os.path.exists(p):

py/visdom/static/js/main.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

py/visdom/static/js/main.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)