Commit c909c53f authored by jdurrant's avatar jdurrant
Browse files

Many updates.

parent 3f8e2320
This diff is collapsed.
../../../../vuejs_components/src/UI/Forms/FileLoader
\ No newline at end of file
// 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
// Jacob D. Durrant.
import { addCSS } from "../Utils";
declare var Vue;
/** An object containing the vue-component computed functions. */
let computedFunctions = {
/**
* Determines whether this component has a label.
* @returns boolean True if it does, false otherwise.
*/
hasLabel(): boolean {
return this["label"] !== "" && this["label"] !== undefined;
},
/**
* Determines if label should be placed to the left or above. Number of
* columns for the label width 'xs' screens and up. Always 0 (labele above
* on small screens).
* @returns number 0
*/
"labelCols"(): number {
return 0;
// Used to return 2, but now I think it's good to have label on top if
// there isn't plenty of room.
// return ((this.hasLabel === true) && (this["labelToLeft"] === true)) ? 2 : 0;
},
}
/**
* Setup the file-loader-form-group Vue commponent.
* @returns void
*/
export function setupFileLoaderFormGroup(): void {
Vue.component('file-loader-form-group', {
/**
* Get the data associated with this component.
* @returns any The data.
*/
"data": function() {
return {}
},
"computed": computedFunctions,
"template": /* html */ `
<span class="file-loader-form-group">
<b-form-group
v-if="formGroupWrapper"
:label="label"
:label-for="id"
:id="'input-group-' + id"
:style="styl"
label-cols="0"
label-cols-xl="1"
>
<slot></slot>
<small
tabindex="-1"
:id="'input-group-input-group-' + id + '__BV_description_'"
class="form-text text-muted" style="display:inline;"
v-html="description">
</small>
<small class="form-text text-muted" style="display:inline;">
<slot name="extraDescription"></slot>
</small>
</b-form-group>
<div v-else>
<slot></slot>
</div>
</span>
`,
"props": {
"label": String,
"id": String,
"styl": String,
"description": String,
"formGroupWrapper": {
"type": Boolean,
"default": true
},
"labelToLeft": {
"type": Boolean,
"default": true
}
},
"methods": {},
"mounted"() {
addCSS(`.file-loader-form-group .col-form-label { hyphens: auto; }`);
// max-width: 100px !important;
}
})
}
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 2021
// Jacob D. Durrant.
declare var Vue;
/**
* Setup the file-loader-text-input Vue commponent.
* @returns void
*/
export function setupFileLoaderTextInput(): void {
Vue.component("file-loader-text-input", {
/**
* Get the data associated with this component.
* @returns any The data.
*/
"data"(): any {
return {
"localValue": ""
};
},
"methods": {
"onLoad"(): void {
this.$emit("onLoad", this["value"]);
},
"keydown"(e: KeyboardEvent): void {
if (e.key === "Enter") {
this["onLoad"]();
}
},
"keyup"(e: KeyboardEvent) : void {
this.$emit("input", this["localValue"]);
}
// "clearText"(): void {
// this["val"] = "";
// }
},
"template": /*html*/ `
<b-input-group>
<b-form-input
v-model="localValue"
style="border-top-left-radius:4px; border-bottom-left-radius:4px;"
:placeholder="placeholder"
:formatter="formatter"
@keydown="keydown"
@keyup="keyup"
:state="valid"
></b-form-input>
<b-input-group-append>
<b-button
style="background-color:#e9ecef; color:#4a5056; border:1px solid #ced4da; border-top-right-radius:4px; border-bottom-right-radius:4px;"
variant="outline-primary"
@click="onLoad"
:disabled="btnDisabledFunc(value)"
>
Load
</b-button>
</b-input-group-append>
</b-input-group>`,
"props": {
"placeholder": {
"type": String,
"default": "Type here...",
},
"formatter": {
"type": Function,
"default": (t) => {return t;}
},
"btnDisabledFunc": {
"type": Function,
"default": () => {return false;}
},
"valid": {
"type": Boolean,
"default": true
},
"value": {
"type": String,
"default": ""
}
},
"computed": {},
/**
* Runs when the vue component is mounted.
* @returns void
*/
"mounted": () => {},
});
}
// 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.
export interface IVueXVar {
name: string;
val: any;
}
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
}
export interface IFileLoadError {
title: string;
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 IExtractInfo {
selection: ISelection[],
pdbLines: string,
origFilename: string,
suggestedNewFilename: string
}
export interface ISelection {
resname?: string;
resid?: string;
chain?: string;
nonProtein?: boolean;
}
export function iSelectionToStr(sel: ISelection): string {
if (sel["chain"] && !sel["resname"] && !sel["resid"]) {
// Only has chain.
return "Chain: " + sel["chain"];
}
let prts = [];
if (sel["resname"]) { prts.push(sel["resname"]); }
if (sel["resid"]) { prts.push(sel["resid"]); }
if (sel["chain"]) { prts.push(sel["chain"]); }
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 2021
// 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
// Jacob D. Durrant.
import { IFileInfo, IFileLoadError } from "./Interfaces";
export function extsStrToList(exts: string): string[] {
return exts
.toLowerCase()
.split(/,/g)
.map(
(e) =>
e.replace(/ /g, "").replace(/\./, "")
);
}
export function getBasename(filename: string, extensive = true): string {
let ext = getExt(filename, extensive);
return filename.substring(0, filename.length - ext.length - 1);
}
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;
}
/**
* 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> {
return new Promise((resolve, reject) => {
var fr = new FileReader();
fr.onload = () => {
// @ts-ignore: Not sure why this causes Typescript problems.
var data = new Uint8Array(fr.result);
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,
// });
// }
});
}
export function loadRemote(url: string, vueComp: any): Promise<boolean> {
let urlUpper = url.toUpperCase();
if (
(urlUpper.slice(0, 7) !== "HTTP://") &&
(urlUpper.slice(0, 8) !== "HTTPS://")
) {
vueComp.onError({
title: "Bad URL",
body: `The URL should start with http:// or https://.`
} as IFileLoadError);
return Promise.resolve(false);
}
return new Promise((resolve, reject) => {
fetch(url)
.then(response => {
if (!response.ok) {
vueComp.onError({
title: "Bad URL",
body: `Could not load the URL ${url}. Status ` + response.status.toString() + ": " + response.statusText
} as IFileLoadError);
resolve(false);
} else {
return response.text()
}
})
.then(text => {
let flnm = url.split("/").pop();
let filesInfo: IFileInfo[] = [{
filename: flnm,
fileContents: text
} as IFileInfo]
let allFilesLoaded = vueComp.onFilesLoaded(filesInfo);
// false if invalid files or something.
resolve(allFilesLoaded);
})
.catch((err) => {
vueComp.onError({
title: "Bad URL",
body: `Could not load the URL ${url}: ` + err.message
} as IFileLoadError);
resolve(false);
});
})
}
export function deepCopy(obj: any): any {
return JSON.parse(JSON.stringify(obj));
}
export function addCSS(css: string): void {
document.head.appendChild(Object.assign(
document.createElement("style"), {
textContent: css
}));
}
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;
}
\ 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
// 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);
let idx = keys.indexOf(filename);
let newIdx = (idx === 0) ? idx + 1 : idx - 1;
let newFilename = keys[newIdx];
this["fileNameClicked"](newFilename);
delete files[filename];
this.$emit("input", files);
this.$emit("onSelectedFilenameChange", newFilename);
// this.$nextTick(() => {
// this.$emit("onRequestRemoveFile", filename);
// });
},
"clearAll"(): void {
// Clears all entries in the list.
// this.$emit("onRequestRemoveAllFiles");
this.$emit("input", {});
this.$emit("onSelectedFilenameChange", "");
// clearAllInDatabase(this["id"]);
// clearAllInDatabase();
},