ConvertFileModal.ts 12.9 KB
Newer Older
1
2
3
4
// This file is part of Webina, released under the Apache 2.0 License. See
// LICENSE.md or go to https://opensource.org/licenses/Apache-2.0 for full
// details. Copyright 2020 Jacob D. Durrant.

5
6
import * as Utils from "../../Utils";

7
8
declare var Vue;

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/** An object containing the vue-component computed functions. */
let computedFunctions = {
    /** The visibility (boolean, open/closed) of the convert file modal. Can
     * both get and set. */
    "convertFileModalShow": {
        get(): boolean {
            return this.$store.state["convertFileModalShow"];
        },

        set(val: boolean): void {
            this.$store.commit("setVar", {
                name: "convertFileModalShow",
                val,
            });
        },
    },

    /**
     * Get the extension of the user-submitted file.
     * @returns string The extension.
     */
    "currentExt"(): string {
        return this.$store.state["convertFileExt"].toUpperCase();
    },

    /**
     * Get the current type of the user-submitted file. "receptor" or
     * "ligand".
     * @returns string The type.
     */
    "currentType"(): string {
        return this.$store.state["convertFileType"];
    },

    /**
     * Determine whether 3D coordinates must be generated, given the file
     * extension. If they are required regardless, don't give the user the
     * option.
     * @returns boolean  True if generating 3D coordinates is required.
     */
    "gen3DRequired"(): boolean {
        if (["CAN", "SMI", "SMILES"].indexOf(this["currentExt"]) !== -1) {
            // It's one of the formats that always required that 3D
            // coordinates be generated.
            this["gen3D"] = true;
            return true;
        }
        return false;
    }
}

/** An object containing the vue-component methods functions. */
let methodsFunctions = {
62
63
64
65
66
67
68
    /**
     * Begin to convert the file to PDBQT.
     * @param  {*}      e                              The click event.
     * @param  {number=1} currentPDBOptimizationLevel  To what extent the PDB
     *                                                 file should be
     *                                                 optimized (to keep size
     *                                                 low for converting).
Jacob Durrant's avatar
Jacob Durrant committed
69
     * @param  {string[]} successMsgs                  The messages to display
70
71
72
73
     *                                                 to the user on success
     *                                                 (if any).
     * @returns void
     */
Jacob Durrant's avatar
Jacob Durrant committed
74
    "beginConvert"(e, currentPDBOptimizationLevel=1, successMsgs=[]): void {
75
76
77
78
79
80
81
        let frameWindow = document.getElementById("convert-frame")["contentWindow"];
        frameWindow["startSpinner"]();
        let content: string = this.$store.state["convertFile"];
        while (content.substr(content.length - 1, 1) === "\n") {
            content = content.substr(0, content.length - 1);
        }

82
        if (this["currentExt"].toUpperCase() === "PDB") {
Jacob Durrant's avatar
Jacob Durrant committed
83
84
85
86
            let msg = this.pdbOptimization(currentPDBOptimizationLevel);
            if (msg !== "") {
                successMsgs.push(msg);
            }
87
88
        }

89
90
91
92
93
94
        if (this["currentType"]!=="ligand") {
            // If it's not a ligand, there's no need to generate 3D
            // coordinates.
            this["gen3D"] = false;
        }

Jacob Durrant's avatar
Jacob Durrant committed
95
        frameWindow.document.querySelector("html").style.overflow = "hidden";
96
97
98
99
100
101
102
103
104
105
106
107
        frameWindow["PDBQTConvert"]["convert"](
            content,
            this["currentExt"].toLowerCase(),
            this["currentType"]==="ligand",
            this["addHydrogens"],
            this["gen3D"],
            parseFloat(this["phVal"])
        ).then((out) => {
            this.$store.commit("setVar", {
                name: this["currentType"] + "Contents",
                val: out
            });
108

109
110
            this["$refs"]["convert-modal"].hide();

111
            // This makes it look like it validated.
112
113
114
115
116
117
118
119
120
121
            this.$store.commit("setVar", {
                name: this["currentType"] + "ForceValidate",
                val: true
            });

            // This actually sets the validation.
            this.$store.commit("setValidationParam", {
                name: this["currentType"],
                val: true
            });
122
123
124
125

            // Update the filename to end in pdbqt.
            let newFilename = Utils.replaceExt(this.$store.state[this["currentType"] + "FileName"], "converted.pdbqt");
            this.$store.commit("updateFileName", { type: this["currentType"], filename: newFilename });
126

Jacob Durrant's avatar
Jacob Durrant committed
127
128
129
            if (successMsgs.length !== 0) {
                let overallMsg = successMsgs.map((m, i) => { return "(" + (i + 1).toString() + ") " + m; }).join(" ");
                this["$bvModal"]["msgBoxOk"]("To convert your file to PDBQT, Webina had to make the following modifications: " + overallMsg, {
130
131
132
                    "title": "Warning: File Too Big!",
                });
            }
133
        }).catch((msg) => {
134
135
            // The conversion failed. But if it's a PDB file, it might be
            // worth trying to optimize it further.
Jacob Durrant's avatar
Jacob Durrant committed
136
137
            if (currentPDBOptimizationLevel <= 4) {  // one less than max number in pdbOptimization.
                this["beginConvert"](e, currentPDBOptimizationLevel + 1, successMsgs);
138
139
140
                return;
            }

141
            this["$refs"]["convert-modal"].hide();
142
            this["$bvModal"]["msgBoxOk"]("Could not convert your file. Are you sure it is a properly formatted " + this["currentExt"] + " file? If so, it may be too large to convert in the browser.", {
143
144
145
146
147
148
                "title": "Error Converting File!",
            });
            this.$store.commit("setVar", {
                name: this["currentType"] + "ForceValidate",
                val: false
            });
149
            this.$store.commit("updateFileName", { type: this["currentType"], filename: "" });
150
151
152
153
154
155
            console.log("ERROR: " + msg);
        });

        e.preventDefault();
    },

156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172

    /**
     * PDB files are very common, yet openbabel.js cannot convert them if they
     * are too large. Here we make efforts to "optimize" the PDB file to
     * maximize the changes that openbabel.js will succeed.
     * @param  {number} level  The optimization level.
     * @returns string  A message to show the user re. any modifications made
     *                  to the PDB file.
     */
    pdbOptimization(level: number): string {
        let pdbTxt = this.$store.state["convertFile"];

        let msg = "";

        switch (level) {
            case 1:
                // Always run this optimization. Just removes lines that don't
Jacob Durrant's avatar
Jacob Durrant committed
173
174
175
176
177
178
179
180
181
                // start with ATOM and HETATM. Also keeps only the first frame
                // if it's a multi-frame PDB.

                if (pdbTxt.indexOf("\nEND") !== -1) {
                    // Perhaps a multi-frame PDB.
                    pdbTxt = pdbTxt.split("\nEND")[0];
                    msg = "Keep only the first frame."
                }

182
183
184
185
186
                pdbTxt = pdbTxt.split("\n").filter(l => l.slice(0, 5) === "ATOM " || l.slice(0, 7) === "HETATM ").join("\n");
                break;
            case 2:
                // Try removing everything but protein atoms.
                pdbTxt = Utils.keepOnlyProteinAtoms(pdbTxt);
Jacob Durrant's avatar
Jacob Durrant committed
187
                msg = "Discard non-protein atoms."
188
189
190
                break;
            case 3:
                // Keep only the first chain.
Jacob Durrant's avatar
Jacob Durrant committed
191
                let chain = pdbTxt.slice(21,22);  // first chain
192
                pdbTxt = pdbTxt.split("\n").filter(l => l.slice(21,22) === chain).join("\n");
Jacob Durrant's avatar
Jacob Durrant committed
193
194
195
196
197
198
199
200
201
202
203
                msg = "Keep only the first chain (chain " + chain + ").";
                break;
            case 4:
                // Remove existing hydrogen atoms.
                pdbTxt = pdbTxt.split("\n").filter(l => l.substr(12,4).replace(/ /g, "").substr(0, 1) !== "H").join("\n");
                msg = "Remove original hydrogen atoms.";
                break;
            case 5:
                // Remove beta, occupancy, etc. columns.
                pdbTxt = pdbTxt.split("\n").map(l => l.substr(0,54)).join("\n");
                msg = "Remove original occupancy, beta, and element columns.";
204
205
206
                break;
        }

Jacob Durrant's avatar
Jacob Durrant committed
207
208
        // console.log("HHHHH>>> " + msg + " >>>> " + pdbTxt.length.toString());

209
210
211
212
213
214
215
216
217
218
219
220
221
        this.$store.commit("setVar", {
            name: "convertFile",
            val: pdbTxt
        });

        return msg;
    },

    /**
     * The cancel button is pressed.
     * @returns void
     */
    "cancelPressed"(): void {
222
223
224
225
226
227
228
229
230
231
232
        // Not sure the below is really necessary, but let's just make
        // sure.
        this.$store.commit("setVar", {
            name: this["currentType"] + "FileName",
            val: undefined
        });

        this.$store.commit("setValidationParam", {
            name: this["currentType"],
            val: false
        });
233
234

        this.$store.commit("updateFileName", { type: this["currentType"], filename: "" });
235
    },
236
237
238
239
240
241

    /**
     * Reload the iframe containing the PDBConvert app.
     * @returns void
     */
    "reloadIFrame"(): void {
242
243
244
245
        document.getElementById("convert-frame")["src"] = "./pdbqt_convert/index.html?startBlank";
    }
}

246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/**
 * Setup the convert-file-modal Vue commponent.
 * @returns void
 */
export function setup(): void {
    Vue.component("convert-file-modal", {
        /**
         * Get the data associated with this component.
         * @returns any  The data.
         */
        "data": function () {
            return {
                "addHydrogens": true,
                "gen3D": false,
                "phVal": 7.4
            };
        },
263
264
        "computed": computedFunctions,
        "methods": methodsFunctions,
265
266
267
268
269
270
271
272
        "template": `
            <b-modal
              ref="convert-modal"
              @shown="reloadIFrame"
              ok-title="Convert" v-model="convertFileModalShow"
              id="convert-msg-modal" title="Convert File to PDBQT"
              @ok="beginConvert" @cancel="cancelPressed">
                <p class="my-4">
273
274
275
276
277
278
279
280
281
282
283
284
285
286
                    Webina works with PDBQT files, not {{currentExt}} files. We suggest you:
                    <span v-if="this['currentType']==='receptor'">
                        <ol>
                            <li>Add hydrogen atoms using <a href="http://www.poissonboltzmann.org/" target="_blank">PDB2PQR</a></li>
                            <li>Convert the resulting PQR file to PDB using <a href="http://openbabel.org/wiki/Main_Page" target="_blank">Open Babel</a></li>
                            <li>Convert the PDB file to PDBQT using <a target='_blank' href='http://mgltools.scripps.edu/'>MGLTools</a></li>
                        </ol>
                    </span>
                    <span v-else-if="this['currentType']==='ligand'">
                        <ol>
                            <li>Add hydrogen atoms to your ligand files (SMILES or SDF format) using <a target='_blank' href='https://git.durrantlab.pitt.edu/jdurrant/gypsum_dl'>Gypsum-DL</a></li>
                            <li>Convert the resulting PDB or SDF file(s) to PDBQT using <a target='_blank' href='http://mgltools.scripps.edu/'>MGLTools</a></li>
                        </ol>
                    </span>
287
                </p>
288

289
                <p>Or click "Convert" below to convert with the PDBQTConvert app, which should be good enough for most purposes.</p>
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329

                <b-form-checkbox
                    id="babel-add-hydrogens"
                    v-model="addHydrogens"
                    name="babel-add-hydrogens"
                    :value="true"
                    :unchecked-value="false"
                >
                    Add hydrogen atoms at pH
                    <b-form-input
                        id="ph-val"
                        v-model="phVal"
                        type="text"
                        placeholder="7.4"
                        class="form-control-sm"
                        @click.stop.prevent
                        style="width: 45px; height: 23px; text-align: center; margin-left: 2px; display: inline-block;"
                    ></b-form-input>
                </b-form-checkbox>

                <b-form-checkbox
                    v-if="(this['currentType']==='ligand') && (!gen3DRequired)"
                    id="babel-gen-3d"
                    v-model="gen3D"
                    name="babel-gen-3d"
                    :value="true"
                    :unchecked-value="false"
                >
                    Generate 3D coordinates.
                </b-form-checkbox>

                <iframe id="convert-frame" style="border: 0; width: 100%; height: 65px;"></iframe>

                <small class="form-text text-muted">
                    PDBQTConvert is an optional GPL-licensed helper app
                    built on <a
                    href="https://github.com/partridgejiang/cheminfo-to-web/tree/master/OpenBabel/OpenBabel-js" target="_blank">
                    OpenBabel JS</a>. It communicates with Webina at "arms
                    length" via an iframe.
                </small>
330
331
332
333
            </b-modal>`,
        "props": {}
    });
}