Commit 123afe60 authored by jdurrant's avatar jdurrant
Browse files

Working towards compatibility with updated file-loading system.

parent 1ff8d3dd
# Changes
## 2.2
1. Updated file loader system.
## 2.1
1. Web-browser app: changes to how interactions are displayed.
......
This diff is collapsed.
......@@ -15,6 +15,7 @@
"devDependencies": {
"@types/file-saver": "^2.0.1",
"@types/jquery": "^3.5.4",
"@types/jszip": "^3.4.1",
"clean-webpack-plugin": "^3.0.0",
"closure-webpack-plugin": "^2.3.0",
"copy-webpack-plugin": "^5.1.2",
......@@ -44,7 +45,8 @@
"bootstrap-vue": "^2.21.2",
"file-saver": "^2.0.5",
"google-closure-compiler": "^20210601.0.0",
"jszip": "^3.5.0",
"jszip": "^3.7.1",
"localforage": "^1.10.0",
"markdown-it": "^12.2.0",
"neo-blessed": "^0.2.0",
"popper.js": "^1.16.1",
......
......@@ -29,6 +29,8 @@ self.onmessage = function(e) {
params.push(paramVal);
}
debugger;
binana["run"](params);
// Get the output.
......
// Released under the Apache 2.0 License. See LICENSE.md or go to
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2022
// Jacob D. Durrant.
// A place to put properties that are common to multiple components.
// Common to mol-loader, and file loaders.
export var commonMultipleFilesProps = {
"multipleFiles": {
"type": Boolean,
"default": false
},
"saveMultipleFilesToDatabase": {
// Saves copies of files to database for use elsewhere (even after page
// reload). But I can imagine scenarios when you'd want to load multiple
// files without realoading the page, so false by default.
"type": Boolean,
"default": false
},
}
// Properties common to file load, from PDB, from URL, etc.
export var commonFileLoaderProps = {
"id": {
"type": String,
"required": false
},
"required": {
"type": Boolean,
"default": true,
},
"accept": {
"type": String,
"default": "", // e.g., ".pdbqt, .out, .pdb"
},
"convert": {
"type": String,
"default": "", // e.g., ".sdf, .mol2"
},
"valid": {
"type": Boolean,
"default": true
}
}
export var commonProteinEditingProps = {
"allowAtomExtract": {
"type": Boolean,
"default": false
},
"allowAtomDelete": {
"type": Boolean,
"default": true
}
}
// Used in both queue-catcher and queue-controller.
export var commonQueueProps = {
"trigger": {
"type": Boolean,
"default": false,
"required": true
},
"countDownSeconds": {
"type": Number,
"default": 5
},
"molLoaderIds": {
"type": Array,
"required": true
},
"outputZipFilename": {
"type": String,
"default": "output.zip"
}
}
\ No newline at end of file
// This file is released under the Apache 2.0 License. See
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2021
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2022
// Jacob D. Durrant.
import { addCSS } from "../Utils";
......@@ -54,9 +54,9 @@ export function setupFileLoaderFormGroup(): void {
:label="label"
:label-for="id"
:id="'input-group-' + id"
:style="styl"
label-cols="0"
label-cols-xl="1"
:style="styl + ';max-width:none !important;'"
:label-cols="label ? 12 : 0"
:label-cols-sm="label ? 2 : 0"
>
<slot></slot>
<small
......@@ -75,7 +75,10 @@ export function setupFileLoaderFormGroup(): void {
</span>
`,
"props": {
"label": String,
"label": {
"type": String,
"default": undefined
},
"id": String,
"styl": String,
"description": String,
......@@ -91,7 +94,6 @@ export function setupFileLoaderFormGroup(): void {
"methods": {},
"mounted"() {
addCSS(`.file-loader-form-group .col-form-label { hyphens: auto; }`);
// max-width: 100px !important;
}
})
}
// This file is released under the Apache 2.0 License. See
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2021
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2022
// Jacob D. Durrant.
declare var Vue;
......@@ -20,20 +20,33 @@ export function setupFileLoaderTextInput(): void {
};
},
"methods": {
/**
* Runs when the file is loaded.
* @returns void
*/
"onLoad"(): void {
this.$emit("onLoad", this["value"]);
},
/**
* Detect keypress to submit on enter.
* @param {KeyboardEvent} e
* @returns void
*/
"keydown"(e: KeyboardEvent): void {
if (e.key === "Enter") {
this["onLoad"]();
}
},
/**
* Detect keyup for v-bind.
* @param {KeyboardEvent} e
* @returns void
*/
"keyup"(e: KeyboardEvent) : void {
this.$emit("input", this["localValue"]);
}
// "clearText"(): void {
// this["val"] = "";
// }
},
"template": /*html*/ `
<b-input-group>
......
// This file is released under the Apache 2.0 License. See
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2021
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2022
// Jacob D. Durrant.
import { ISelection, ParentMol } from "../Mols/ParentMol";
export interface IVueXVar {
name: string;
val: any;
}
export interface IConvert extends IFileInfo{
onConvertDone: Function;
export interface IConvert extends IFileInfo {
onConvertDone: Function; // Must return IFileInfo
onConvertCancel: Function;
}
export interface IFileInfo {
filename: string;
fileContents: string;
// onConvertDone: IConvert;
// convertedResolveFunc?: Function;
// convertedRejectFunc?: Function;
// id?: string; // associated component id
mol: ParentMol;
}
export interface IFileLoadError {
......@@ -26,20 +24,32 @@ export interface IFileLoadError {
body: string;
}
// export interface IFileFromTextField {
// placeholder: string;
// tabName: string;
// loadFunc: Function
// onSuccess: Function;
// onError: Function;
// }
export interface IAllFiles {
selectedFilename: string;
allFiles: {[key: string]: string}; // filename => contents
}
export interface IResidueInfo {
residueId: string[],
residuePdbLines: string
export interface IExtractInfo {
selection: ISelection[],
pdbLines: string,
origFilename: string,
suggestedNewFilename: string
}
/**
* Converts ISelection to a string for labelling.
* @param {ISelection} sel
* @returns string
*/
export function iSelectionToStr(sel: ISelection): string {
if (sel["chains"] && !sel["resnames"] && !sel["resids"]) {
// Only has chain.
return "Chain: " + sel["chains"];
}
let prts = [];
if (sel["resnames"]) { prts.push(sel["resnames"]); }
if (sel["resids"]) { prts.push(sel["resids"]); }
if (sel["chains"]) { prts.push(sel["chains"]); }
return prts.join(":");
}
\ No newline at end of file
You can omit this if this component is registered globally (lots of other
components you use might already be using this one).
\ No newline at end of file
// This file is released under the Apache 2.0 License. See
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2022
// Jacob D. Durrant.
declare var Vue;
/** An object containing the vue-component computed functions. */
let computedFunctions = {}
/**
* Setup the small-pill-btn Vue commponent.
* @returns void
*/
export function setupSmallPillBtn(): void {
Vue.component('small-pill-btn', {
/**
* Get the data associated with this component.
* @returns any The data.
*/
"data": function() {
return {}
},
"computed": computedFunctions,
"template": /* html */ `
<b-button
pill :variant="actionStyling === 'delete' ? 'secondary' : 'outline-secondary'" size="sm"
class="py-0 px-1"
style="line-height:14px; font-size:90%; margin-right:2px;"
@click="onClick"
>
<span
v-if="actionStyling === 'extract'"
style="display:inline-block; transform: scaleX(-1);">&#10138;
</span>
<span v-else>&#10006;</span>
<slot></slot>
</b-button>
<!-- style="line-height:14px; font-size:80%;" -->
`,
"props": {
"actionStyling": {
"type": String,
"default": "delete" // Can also be "extract"
}
},
"methods": {
"onClick"(): void {
this.$emit("click");
}
},
"mounted"() {}
})
}
// This file is released under the Apache 2.0 License. See
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2021
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2022
// Jacob D. Durrant.
import { getMol } from "../Mols";
import { ParentMol } from "../Mols/ParentMol";
import { IFileInfo, IFileLoadError } from "./Interfaces";
/**
* Takes a string of extensions and returns an array of extensions.
* @param {string} exts A string containing a list of extensions separated by
* commas.
* @returns array
*/
export function extsStrToList(exts: string): string[] {
return exts
.toLowerCase()
......@@ -14,19 +22,103 @@ export function extsStrToList(exts: string): string[] {
);
}
export function getExt(filename: string): string {
/**
* Given a filename, return the basename of the file
* @param {string} filename The filename to get the basename of.
* @param {boolean} [extensive=true] If true, considers multi-component
* extensions (e.g., .pdb.txt).
* @returns {string}
*/
export function getBasename(filename: string, extensive = true): string {
let ext = getExt(filename, extensive);
return filename.substring(0, filename.length - ext.length - 1);
}
/**
* Given a filename, return the extension of the file
* @param {string} filename The filename to get the extension from.
* @param {boolean} [extensive=true] If true, considers multi-component
* extensions (e.g., .pdb.txt).
* @returns {string}
*/
export function getExt(filename: string, extensive = true): string {
if (filename === undefined) {
return "";
}
let fileNameParts = filename.toLowerCase().split(/\./g);
let ext = fileNameParts[fileNameParts.length - 1];
if (extensive) {
for (let i = fileNameParts.length - 2; i > 0; i--) {
// Note that because length -2 and i > 0 (not i > -1), doesn't get
// last (always included) or first (never included) parts. Assuming
// here that if any part has more than four characters, no longer
// extension. This is an arbitrary choice.
let prt = fileNameParts[i];
if (prt.length > 4) {
break;
}
ext = prt + "." + ext;
}
}
return ext;
}
/**
* Get the type of a file given the extension.
* @param {string} ext The extension.
* @param {string[]} [choices=undefined] The file-type options.
* @returns {string} The detected type (one of the elements ni choices, if it's
* given).
*/
export function getFileTypeFromExt(ext: string, choices: string[] = undefined): string {
// Remove first letter from ext if it is "."
if (ext.substring(0, 1) === ".") { ext = ext.substring(1); }
// Get the extension parts
let extPrts = ext.split(/\./g);
if (choices === undefined) {
return extPrts.pop().toLowerCase();
}
// Choices need to be lower case
let choicesLowerCase = choices.map(c => c.toLowerCase());
// In windows, text files often have the ".txt" extension, even if they are
// other formats (e.g., pdb.txt).
if (extPrts[extPrts.length - 1] === "txt") {
// But if txt is one of the choicesLowerCase, choose that.
if (choicesLowerCase.indexOf("txt") !== -1) {
return "txt";
}
extPrts.pop();
}
let lastExtPrt = extPrts.pop();
let idx = choicesLowerCase.indexOf(lastExtPrt);
if (idx === -1) {
// Not one of the choicesLowerCase
return undefined;
}
return choices[idx];
}
// let fileType = getFileTypeFromExt(".pdb.txt", ["pdb"]);
// fileType = getFileTypeFromExt(".pdb.txt", ["pdb", "txt"]);
// fileType = getFileTypeFromExt(".pdb", ["pdb"]);
// fileType = getFileTypeFromExt(".pdb", undefined);
/**
* Given a file object, returns a promise that resolves the text
* in that file.
* @param {*} fileObj The file object.
* @returns Promise
*/
export function getFileObjContents(fileObj): Promise<any> {
export function getFileObjContents(fileObj): Promise<string> {
return new Promise((resolve, reject) => {
var fr = new FileReader();
fr.onload = () => {
......@@ -35,17 +127,15 @@ export function getFileObjContents(fileObj): Promise<any> {
resolve(new TextDecoder("utf-8").decode(data));
};
fr.readAsArrayBuffer(fileObj);
// Reset the show non-protein atom's link.
// if (this["id"] === "receptor") {
// this.$store.commit("setVar", {
// name: "showKeepProteinOnlyLink",
// val: true,
// });
// }
});
}
/**
* Loads a remote file and sends it to the relevant Vue component.
* @param {string} url The URL of the remote file to load.
* @param {*} vueComp The Vue component.
* @returns {Promise<boolean>}
*/
export function loadRemote(url: string, vueComp: any): Promise<boolean> {
let urlUpper = url.toUpperCase();
if (
......@@ -77,7 +167,7 @@ export function loadRemote(url: string, vueComp: any): Promise<boolean> {
let filesInfo: IFileInfo[] = [{
filename: flnm,
fileContents: text
mol: getMol(flnm, text)
} as IFileInfo]
let allFilesLoaded = vueComp.onFilesLoaded(filesInfo);
......@@ -95,10 +185,10 @@ export function loadRemote(url: string, vueComp: any): Promise<boolean> {
})
}
export function deepCopy(obj: any): any {
return JSON.parse(JSON.stringify(obj));
}
/**
* Add a CSS string to the document's head
* @param {string} css The CSS to be added to the page.
*/
export function addCSS(css: string): void {
document.head.appendChild(Object.assign(
document.createElement("style"), {
......@@ -106,8 +196,20 @@ export function addCSS(css: string): void {
}));
}
/**
* Takes a string and converts it to lowercase, replaces any non-alphanumeric
* characters with dashes, and replaces any double dashes with a single dash.
* @param {string} complexString The string to be slugified.
* @returns {string}
*/
export function slugify(complexString: string): string {
// With help from codex
var slug = complexString.toLowerCase().replace(/[^a-z0-9]+/g, '-');
slug = slug.replace(/\-\-/g, "-");
return slug;
}
export function deepCopy(obj: any): any {
return Object.assign({}, obj);
}
// This file is released under the Apache 2.0 License. See
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2021
// Jacob D. Durrant.
import * as FileLoaderVue from "./FileLoader.Vue";
import { FileLoaderInputPlugin } from "./Plugins/FileLoaderInput.Vue";
import { PDBIDInputPlugin } from "./Plugins/PDBIDInput";
import { URLInputPlugin } from "./Plugins/URLInput";
export function setupFileLoader() {
// Always load all plugins. Whether they are displayed will be specified
// within vue.js, but just always load them all.
FileLoaderVue.setup([
new FileLoaderInputPlugin().setup(),
new PDBIDInputPlugin().setup(),
new URLInputPlugin().setup()
]);
}
\ No newline at end of file
// This file is released under the Apache 2.0 License. See
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2021
// https://opensource.org/licenses/Apache-2.0 for full details. Copyright 2022
// Jacob D. Durrant.
import { deepCopy } from "../Common/Utils";
/** An object containing the vue-component methods functions. */
export let fileLoaderFileListMethodsFunctions = {
"fileDismissed"(filename) {
// removeFileFromDatabase(this["id"], filename, this["associatedFileLoaderComponent"])
let files = deepCopy(this["value"]);
let keys = Object.keys(files);
/**
* Remove a loaded file and select the next file.
* @param {string} filename The name of the file to dismiss.
*/
"fileDismissed"(filename: string) {
let keys = Object.keys(this["value"]);
let idx = keys.indexOf(filename);
let newIdx = (idx === 0) ? idx + 1 : idx - 1;
let newFilename = keys[newIdx];
this["fileNameClicked"](newFilename);
delete files[filename];
// delete files[filename];
this.$delete(this["value"], filename);
this.$emit("input", files);
this.$emit("input", this["value"]);
this.$emit("onSelectedFilenameChange", newFilename);
// this.$nextTick(() => {
// this.$emit("onRequestRemoveFile", filename);
// });
},
/**
* Clears all entries in the list
*/
"clearAll"(): void {
// Clears all entries in the list.
// this.$emit("onRequestRemoveAllFiles");
this.$emit("input", {});
this.$emit("onSelectedFilenameChange", "");
// clearAllInDatabase(this["id"]);
// clearAllInDatabase();