Skip to content

Commit 51d5c4e

Browse files
Frontend tweaks (#189)
* Show loading spinner when coloring (#120) * Update npm packages * Add log for failed docking/tunnel tasks (#168) * PR suggestions
1 parent 4700be6 commit 51d5c4e

14 files changed

Lines changed: 3536 additions & 2625 deletions

File tree

executor-docking/run_task.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ def execute_directory_task(docking_directory: str, taskId: int):
146146
logger.log(f"Task {taskId} failed, no structure file found")
147147
logger.close()
148148

149+
# copy log file to public directory so it can be accessed via API
150+
os.makedirs(os.path.join(docking_directory, str(taskId), "public"), exist_ok=True)
151+
shutil.copy(log_filename, os.path.join(docking_directory, str(taskId), "public", "log"))
152+
149153
# update the status file, reload it first to make sure we don't overwrite any changes
150154
status = _load_json(status_file)
151155
status["tasks"][taskId]["status"] = Status.FAILED.value
@@ -164,6 +168,10 @@ def execute_directory_task(docking_directory: str, taskId: int):
164168
logger.log(f"Task {taskId} failed, {str(e)}, {repr(e)}")
165169
logger.close()
166170

171+
# copy log file to public directory so it can be accessed via API
172+
os.makedirs(os.path.join(docking_directory, str(taskId), "public"), exist_ok=True)
173+
shutil.copy(log_filename, os.path.join(docking_directory, str(taskId), "public", "log"))
174+
167175
# update the status file, reload it first to make sure we don't overwrite any changes
168176
status = _load_json(status_file)
169177
status["tasks"][taskId]["status"] = Status.FAILED.value

executor-tunnels/run_task.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ def execute_directory_task(tunnels_directory: str, taskId: int):
133133
logger.log(f"Task {taskId} failed, no structure file found")
134134
logger.close()
135135

136+
# copy log file to public directory so it can be accessed via API
137+
os.makedirs(os.path.join(tunnels_directory, str(taskId), "public"), exist_ok=True)
138+
shutil.copy(log_filename, os.path.join(tunnels_directory, str(taskId), "public", "log"))
139+
136140
# update the status file, reload it first to make sure we don't overwrite any changes
137141
status = _load_json(status_file)
138142
status["tasks"][taskId]["status"] = Status.FAILED.value
@@ -151,6 +155,10 @@ def execute_directory_task(tunnels_directory: str, taskId: int):
151155
logger.log(f"Task {taskId} failed, {str(e)}, {repr(e)}")
152156
logger.close()
153157

158+
# copy log file to public directory so it can be accessed via API
159+
os.makedirs(os.path.join(tunnels_directory, str(taskId), "public"), exist_ok=True)
160+
shutil.copy(log_filename, os.path.join(tunnels_directory, str(taskId), "public", "log"))
161+
154162
# update the status file, reload it first to make sure we don't overwrite any changes
155163
status = _load_json(status_file)
156164
status["tasks"][taskId]["status"] = Status.FAILED.value

frontend/build/webpack.common.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ module.exports = {
2525
},
2626
"resolve": {
2727
"extensions": [".js", ".jsx", ".ts", ".tsx"],
28+
"fullySpecified": false,
2829
},
2930
"module": {
3031
"rules": [
32+
{
33+
"test": /\.m?js$/,
34+
"resolve": {
35+
"fullySpecified": false,
36+
},
37+
},
3138
{
3239
"test": /\.[jt]sx?$/,
3340
"loader": "esbuild-loader",

frontend/client/viewer/application.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,9 @@ export class Application extends React.Component<ReactApplicationProps, ReactApp
178178
});
179179
}
180180

181-
onPolymerColorChange(value: PolymerColorType) {
181+
async onPolymerColorChange(value: PolymerColorType) {
182182
this.setState({ "polymerColor": value });
183-
overPaintPolymer(value, this.props.molstarPlugin, this.state.data, this.state.polymerRepresentations, this.state.predictedPolymerRepresentations, this.state.pocketRepresentations);
183+
await overPaintPolymer(value, this.props.molstarPlugin, this.state.data, this.state.polymerRepresentations, this.state.predictedPolymerRepresentations, this.state.pocketRepresentations);
184184
}
185185

186186
onShowConfidentChange() {
@@ -207,7 +207,7 @@ export class Application extends React.Component<ReactApplicationProps, ReactApp
207207
//resolve RCSB at first - do it by recoloring the pocket to the default color value
208208
//currently there is no other way to "remove" one of the pockets without modyfing the others
209209
const newColor = isVisible ? "#" + this.state.data.pockets[index].color : "#F9F9F9";
210-
const track = this.state.pluginRcsb.getBoardData().find(e => e.trackId.indexOf("pocketsTrack") !== -1);
210+
const track = this.state.pluginRcsb.getBoardData().find((e: any) => e.trackId.indexOf("pocketsTrack") !== -1);
211211
if (track) {
212212
// this shouldn't be any but I couldn't find a way to avoid it
213213
track.trackData!.filter((e: any) => e.provenanceName === `pocket${index + 1}`).forEach((foundPocket: RcsbFvTrackDataElementInterface) => (foundPocket.color = newColor));

frontend/client/viewer/components/tasks-table.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,33 @@ export function TasksTable(props: { pocket: PocketData | null, predictionInfo: P
220220
setOpenConfirmDialogServerTasks(true);
221221
};
222222

223+
const handleFailedTaskClick = async (task: ServerTaskLocalStorageData) => {
224+
if (task.type === ServerTaskType.Docking) {
225+
const hash = await dockingHash(task.pocket.toString(), task.params[0], task.params[1]);
226+
const logUrl = `./api/v2/docking/${props.predictionInfo.database}/${props.predictionInfo.id}/${hash}/log`;
227+
window.open(logUrl, "_blank");
228+
} else if (task.type === ServerTaskType.Tunnels) {
229+
const hash = await tunnelsHash(task.pocket.toString());
230+
const logUrl = `./api/v2/tunnels/${props.predictionInfo.database}/${props.predictionInfo.id}/${hash}/log`;
231+
window.open(logUrl, "_blank");
232+
}
233+
};
234+
223235
const isUrl = (url: any) => { return typeof url === 'string' && (url.startsWith("http://") || url.startsWith("https://")); };
224236

237+
const renderTaskStatus = (task: ServerTaskLocalStorageData) => {
238+
const linkStyle = { textDecoration: "underline", cursor: "pointer" };
239+
240+
switch (task.status) {
241+
case "successful":
242+
return <span onClick={() => handleResultClick(task)} style={{ color: "blue", ...linkStyle }}>successful</span>;
243+
case "failed":
244+
return <span onClick={() => handleFailedTaskClick(task)} style={{ color: "red", ...linkStyle }}>failed (view log)</span>;
245+
default:
246+
return task.status;
247+
}
248+
};
249+
225250
return (
226251
<>
227252
{openConfirmDialogServerTasks && <ConfirmDialog setOpenDialog={setOpenConfirmDialogServerTasks} callback={removeServerTaskFromLocalStorage(serverTaskToDelete!)} task={serverTaskToDelete!} />}
@@ -261,7 +286,7 @@ export function TasksTable(props: { pocket: PocketData | null, predictionInfo: P
261286
<TableCell>{ServerTaskTypeDescriptors[task.type]}</TableCell>
262287
<TableCell>{task.name}</TableCell>
263288
<TableCell>{makeDateMoreReadable(task.created)}</TableCell>
264-
<TableCell>{task.status === "successful" ? <span onClick={() => handleResultClick(task)} style={{ color: "blue", textDecoration: "underline", cursor: "pointer" }}>successful</span> : task.status}</TableCell>
289+
<TableCell>{renderTaskStatus(task)}</TableCell>
265290
<TableCell>
266291
<button type="button" className="btn btn-outline-secondary btnIcon" title="Delete task" style={{ "padding": "0.25rem" }} onClick={() => handleServerTaskDeleteRequest(task)}>
267292
<i className="bi bi-trash" style={{ "display": "block", "fontSize": "small" }}></i>

frontend/client/viewer/components/visualization-tool-box.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,20 @@
4040
text-decoration: none;
4141
color: inherit;
4242
}
43+
44+
.visualization-toolbox-option-relative {
45+
position: relative;
46+
}
47+
48+
.visualization-toolbox-loading-overlay {
49+
position: absolute;
50+
top: 0;
51+
left: 0;
52+
right: 0;
53+
bottom: 0;
54+
display: flex;
55+
align-items: center;
56+
justify-content: center;
57+
background-color: rgba(255, 255, 255, 0.8);
58+
pointer-events: none;
59+
}

frontend/client/viewer/components/visualization-tool-box.tsx

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Box, Button, FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material";
1+
import { Box, Button, FormControl, FormHelperText, InputLabel, MenuItem, Select, CircularProgress } from "@mui/material";
22
import React from "react";
33

44
import "./visualization-tool-box.css";
@@ -16,14 +16,15 @@ export class VisualizationToolBox extends React.Component<{
1616
polymerColor: PolymerColorType,
1717
onPolymerViewChange: (polymerView: PolymerViewType) => void,
1818
onPocketsViewChange: (pocketsView: PocketsViewType) => void,
19-
onPolymerColorChange: (polymerColor: PolymerColorType) => void,
19+
onPolymerColorChange: (polymerColor: PolymerColorType) => Promise<void>,
2020
onShowConfidentChange: () => void,
2121
}, {
2222
polymerView: PolymerViewType,
2323
pocketsView: PocketsViewType,
2424
polymerColor: PolymerColorType,
2525
is1DViewerVisible: boolean,
26-
isShowOnlyPredicted: boolean;
26+
isShowOnlyPredicted: boolean,
27+
isColoringPolymer: boolean;
2728
}> {
2829

2930
constructor(props: any) {
@@ -42,7 +43,8 @@ export class VisualizationToolBox extends React.Component<{
4243
pocketsView: this.props.pocketsView,
4344
polymerColor: this.props.polymerColor,
4445
is1DViewerVisible: true,
45-
isShowOnlyPredicted: false
46+
isShowOnlyPredicted: false,
47+
isColoringPolymer: false
4648
};
4749
}
4850

@@ -81,9 +83,18 @@ export class VisualizationToolBox extends React.Component<{
8183
this.props.onPocketsViewChange(pocketsView);
8284
}
8385

84-
changePolymerColor(polymerColor: PolymerColorType) {
85-
this.setState({ polymerColor: polymerColor });
86-
this.props.onPolymerColorChange(polymerColor);
86+
async changePolymerColor(polymerColor: PolymerColorType) {
87+
this.setState({ polymerColor: polymerColor, isColoringPolymer: true }, async () => {
88+
try {
89+
// Wait for next frame to ensure UI updates
90+
await new Promise(resolve => setTimeout(resolve, 0));
91+
await this.props.onPolymerColorChange(polymerColor);
92+
} catch (error) {
93+
console.error("Error changing polymer color:", error);
94+
} finally {
95+
this.setState({ isColoringPolymer: false });
96+
}
97+
});
8798
}
8899

89100
changeShowConfident() {
@@ -138,7 +149,7 @@ export class VisualizationToolBox extends React.Component<{
138149
</div>
139150
</div>
140151

141-
<div className="visualization-toolbox-option">
152+
<div className="visualization-toolbox-option visualization-toolbox-option-relative">
142153
<div className="visualization-toolbox-option-description">
143154
<FormControl size="small" className="visualization-toolbox-formcontrol">
144155
<Select
@@ -147,6 +158,7 @@ export class VisualizationToolBox extends React.Component<{
147158
value={this.state.polymerColor}
148159
onChange={(event) => this.changePolymerColor(event.target.value as PolymerColorType)}
149160
className="visualization-toolbox-select"
161+
disabled={this.state.isColoringPolymer}
150162
>
151163
<MenuItem value={PolymerColorType.White}>White</MenuItem>
152164
{this.scoresDataAvailable(this.props.predictionData.structure.scores.conservation) &&
@@ -156,6 +168,11 @@ export class VisualizationToolBox extends React.Component<{
156168
</Select>
157169
<FormHelperText sx={{ textAlign: "center" }}>Polymer coloring</FormHelperText>
158170
</FormControl>
171+
{this.state.isColoringPolymer && (
172+
<div className="visualization-toolbox-loading-overlay">
173+
<CircularProgress size={24} />
174+
</div>
175+
)}
159176
</div>
160177
</div>
161178
</div>

0 commit comments

Comments
 (0)