diff --git a/boot/d4s-boot.js b/boot/d4s-boot.js
index 21c3a8d..b3296bd 100644
--- a/boot/d4s-boot.js
+++ b/boot/d4s-boot.js
@@ -21,6 +21,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
#queue = []
#interval = null
#config = null
+ #uma = false
#rpt = null
constructor() {
@@ -63,8 +64,8 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
console.log("Keycloak initialized and user authenticated")
//console.log("Token exp: " + this.expirationDate(this.#keycloak.tokenParsed.exp))
- //if an audience is provided then perform also authorization
- if (this.#audience) {
+ //if an audience is provided and UMA flow requested then perform also authorization
+ if (this.#audience && this.#uma) {
return this.loadConfig()
} else {
Promise.resolve()
@@ -101,7 +102,11 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
clientId: this.#clientId
})
- return this.#keycloak.init({onLoad: 'login-required', checkLoginIframe: false })
+ const properties = {onLoad: 'login-required', checkLoginIframe: false}
+ if(this.#audience && !this.#uma){
+ properties["scope"] = `d4s-context:${this.#audience}`
+ }
+ return this.#keycloak.init(properties)
}
startStateChecker() {
@@ -113,7 +118,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
} else {
if (this.#queue.length > 0) {
this.#keycloak.updateToken(30).then(() => {
- if (this.#audience) {
+ if (this.#uma && this.#audience) {
//console.log("Checking entitlement for audience", this.#audience)
const audience = encodeURIComponent(this.#audience)
return this.entitlement(audience)
@@ -156,18 +161,19 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
return d
}
- checkContext() {
- const parseJwt = this.parseJwt
- const expDt = this.expirationDate
- const audience = encodeURIComponent(this.#audience)
- this.entitlement(audience).then(function (rpt) {
- // onGrant callback function.
- // If authorization was successful you'll receive an RPT
- // with the necessary permissions to access the resource server
- //console.log(rpt)
- //console.log("rpt expires: " + expDt(parseJwt(rpt).exp))
- })
- }
+ // TODO: Candidate for removal
+ // checkContext() {
+ // const parseJwt = this.parseJwt
+ // const expDt = this.expirationDate
+ // const audience = encodeURIComponent(this.#audience)
+ // this.entitlement(audience).then(function (rpt) {
+ // // onGrant callback function.
+ // // If authorization was successful you'll receive an RPT
+ // // with the necessary permissions to access the resource server
+ // //console.log(rpt)
+ // //console.log("rpt expires: " + expDt(parseJwt(rpt).exp))
+ // })
+ // }
secureFetch(url, request) {
const p = new Promise((resolve, reject) => {
@@ -291,7 +297,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
}
static get observedAttributes() {
- return ["url", "realm", "gateway", "redirect-url", "context"];
+ return ["url", "realm", "gateway", "redirect-url", "context", "uma"];
}
attributeChangedCallback(name, oldValue, newValue) {
@@ -312,10 +318,17 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
case "context":
this.#audience = newValue
break
+ case "uma":
+ this.#uma = newValue === "true" ? true : false
+ break
}
}
}
+ get uma(){
+ return this.#uma
+ }
+
get authenticated(){
return this.#authenticated
}
diff --git a/ccp/js/executionformcontroller.js b/ccp/js/executionformcontroller.js
index 83cea27..91b7e26 100644
--- a/ccp/js/executionformcontroller.js
+++ b/ccp/js/executionformcontroller.js
@@ -491,7 +491,7 @@ class CCPExecutionForm extends HTMLElement {
"in": (e, d) => { return Object.values(d.inputs) },
target: "div",
apply: (e, d) => {
- e.innerHTML = `
FormData
object you have to leave the mime
attribute as null
.
+ * @param {*} method the method to use for the call, default to GET
+ * @param {*} uri the uri to invoke, relative to workspaceURL
provided in the object's constructor
+ * @param {*} body the payload to send
+ * @param {*} mime the mime type of the payload
+ * @param {*} extraHeaders extra HTTP headers to send
+ * @returns the reponse payload as a promise, JSON data promise if the returned data is "application/json", a String data promise if "text/*" or a blob data promise in other cases
+ */
+ callWorkspace(method, uri, body, mime, extraHeaders) {
+ let req = { };
+ if (method) {
+ req.method = method;
+ }
+ if (body) {
+ req.body = body;
+ }
+ if (extraHeaders) {
+ req.headers = extraHeaders;
+ }
+ if (mime) {
+ if (req.headers) {
+ req.headers["Content-Type"] = mime;
+ } else {
+ req.headers = { "Content-Type" : mime };
+ }
+ }
+ const url = this.#workspaceURL + (uri.startsWith('/') ? uri : '/' + uri);
+ return this.#d4sboot.secureFetch(url, req)
+ .then(resp => {
+ if (resp.ok) {
+ const contentType = resp.headers.get("content-type");
+ if (contentType && contentType.indexOf("application/json") !== -1) {
+ return resp.json();
+ } else if (contentType && contentType.indexOf("text/") !== -1) {
+ return resp.text();
+ } else {
+ return resp.blob();
+ }
+ } else {
+ throw "Cannot invoke workspace via secure fetch for URL: " + url;
+ }
+ }).catch(err => {
+ console.error(err);
+ throw err;
+ });
+ }
+
+ getWorkspace($extraHeaders) {
+ return this.callWorkspace("GET", "", null, null, $extraHeaders)
+ .then(json => {
+ return json.item.id;
+ }).catch(err => {
+ const msg = "Cannot get workspace root ID";
+ console.error(msg);
+ throw msg;
+ });
+ }
+
+ checkOrCreateFolder(parentFolderId, name, description, extraHeaders) {
+ const baseURI = "/items/" + parentFolderId;
+ console.log("Checking existance of folder: " + name);
+ let uri = baseURI + "/items/" + name;
+ return this.callWorkspace("GET", uri, null, null, extraHeaders)
+ .then(json => {
+ if (json.itemlist[0]) {
+ const id = json.itemlist[0].id;
+ console.log("'" + name + "' folder exists with and has id: " + id);
+ return id;
+ } else {
+ console.info("'" + name + "' folder doesn't exist, creating it: " + name);
+ uri = baseURI + "/create/FOLDER";
+ const params = new URLSearchParams({ name : name, description : description, hidden : false });
+ return this.callWorkspace("POST", uri, params.toString(), "application/x-www-form-urlencoded", extraHeaders).then(id => {
+ console.log("New '" + name + "' folder successfully created with id: " + id);
+ return id
+ });
+ }
+ });
+ };
+
+ uploadFile(parentFolderId, name, description, data, contentType, extraHeaders) {
+ const uri = "/items/" + parentFolderId + "/create/FILE";
+ const request = new FormData();
+ request.append("name", name);
+ request.append("description", description);
+ request.append(
+ "file",
+ new Blob([data], {
+ type: contentType
+ })
+ );
+ return this.callWorkspace("POST", uri, request, null, extraHeaders)
+ .then(id => {
+ console.info("File '" + name + "' successfully uploaded and its id is: " + id);
+ return true;
+ }).catch(err => {
+ console.error("Cannot upload file '" + name + "'. Error: " + err);
+ return false;
+ });
+ };
+}
\ No newline at end of file
diff --git a/i-gene/i-gene.js b/i-gene/i-gene.js
new file mode 100644
index 0000000..60e67e0
--- /dev/null
+++ b/i-gene/i-gene.js
@@ -0,0 +1,1135 @@
+class iGeneIndexer {
+
+ #parsedTSV = null;
+ #genome = null;
+ #pam = null;
+ #helper = null;
+ #currentDataLength = 0;
+ #pamOutDistances = null;
+ #pamInDistances = null;
+ #pamNoneDistances = null;
+ #chromosome = null;
+ #minIndex = null;
+ #maxIndex = null;
+ #genomeInterestingExtract = null;
+
+ static restrictionEnzymes = null;
+ static {
+ iGeneIndexer.fetchAndParseAllRestrictionEnzymesFileFromREBASE().then(reArray => {
+ iGeneIndexer.restrictionEnzymes = reArray;
+ }).catch(err => alert(err));
+ }
+
+ constructor(parsedTSV, genome, pam, helper) {
+ this.#parsedTSV = parsedTSV;
+ this.#genome = genome;
+ this.#pam = pam;
+ this.#helper = helper;
+
+ this.#currentDataLength = parsedTSV.length;
+
+ // Preparing distance matrices
+ this.#pamOutDistances = new Array(this.#currentDataLength);
+ this.#pamInDistances = new Array(this.#currentDataLength);
+ this.#pamNoneDistances = new Array(this.#currentDataLength);
+ for(var i = 0; i < this.#currentDataLength; i++) {
+ this.#pamOutDistances[i] = new Array(this.#currentDataLength);
+ this.#pamOutDistances[i].fill(NaN);
+ this.#pamInDistances[i] = new Array(this.#currentDataLength);
+ this.#pamInDistances[i].fill(NaN);
+ this.#pamNoneDistances[i] = new Array(this.#currentDataLength);
+ this.#pamNoneDistances[i].fill(NaN);
+ }
+
+ this.#parsedTSV.forEach((arrayItem, aiIndex) => {
+ const rank = this.getHelper().hasRank() ? this.getHelper().getRank(arrayItem) : aiIndex + 1;
+
+ // Make every entry to have a distance of zero from itself
+ this.#pamOutDistances[rank - 1][rank - 1] = 0;
+ this.#pamInDistances[rank - 1][rank - 1] = 0;
+ this.#pamNoneDistances[rank - 1][rank - 1] = 0;
+
+ const sequence = this.getHelper().getSequence(arrayItem);
+ const chromosome = this.getHelper().hasChromosome() ? this.getHelper().getChromosome(arrayItem) : this.getHelper().getDefaultChromosome();
+ const index = this.getHelper().getIndex(arrayItem);
+ const strand = this.getHelper().getStrand(arrayItem);
+
+ parsedTSV.forEach((comparedEntry, ceIndex) => {
+ const comparedEntryRank = this.getHelper().hasRank() ? this.getHelper().getRank(comparedEntry) : ceIndex + 1;
+ if (comparedEntryRank == rank) {
+ // Skipping comparison with itself
+ return;
+ }
+ const comparedEntrySequence = this.getHelper().getSequence(comparedEntry);
+ const comparedEntryStrand = this.getHelper().getStrand(comparedEntry);
+ const comparedEntryChromosome = this.getHelper().hasChromosome() ? this.getHelper().getChromosome(comparedEntry) : this.getHelper().getDefaultChromosome();
+ const comparedEntryIndex = this.getHelper().getIndex(comparedEntry);
+ // Checking chromosome, should be an impossible case but...
+ if (chromosome != null && comparedEntryChromosome != null && chromosome != comparedEntryChromosome) {
+ console.error("Different chromosome in genomic location, expected: " + chromosome + ", found: " + comparedEntryChromosome)
+ return;
+ }
+
+ this.#pamOutDistances[rank - 1][comparedEntryRank - 1] = this.computePamOutDistance(index, strand, sequence, comparedEntryIndex, comparedEntryStrand, comparedEntrySequence);
+ this.#pamInDistances[rank - 1][comparedEntryRank - 1] = this.computePamInDistance(index, strand, sequence, comparedEntryIndex, comparedEntryStrand, comparedEntrySequence);
+ this.#pamNoneDistances[rank - 1][comparedEntryRank - 1] = this.computePamNoneDistance(index, strand, sequence, comparedEntryIndex, comparedEntryStrand, comparedEntrySequence);
+ });
+
+ if (this.#chromosome == null) {
+ this.#chromosome = chromosome;
+ }
+ if (this.#minIndex == null || index < this.#minIndex) {
+ this.#minIndex = index;
+ }
+ if (this.#maxIndex == null || index > this.#maxIndex) {
+ this.#maxIndex = index;
+ }
+ });
+ this.fetchActualGenomePartFromUSCS().then(dna => {
+ this.#genomeInterestingExtract = dna;
+ }).catch(err => alert(err));
+
+ }
+
+ getGenome() {
+ return this.#genome;
+ }
+
+ getChromosome() {
+ return this.#chromosome;
+ }
+
+ getPAM() {
+ return this.#pam;
+ }
+
+ getHelper() {
+ return this.#helper;
+ }
+
+ getMinIndex() {
+ return this.#minIndex;
+ }
+
+ getMaxIndex() {
+ return this.#maxIndex;
+ }
+
+ getGenomeInterestingExtract() {
+ return this.#genomeInterestingExtract;
+ }
+
+ computeStartIndex(index, strand, sequence) {
+ if (strand == '+') {
+ return index;
+ } else {
+ return (index + sequence.length + (this.getHelper().hasPAMInSequence() ? 0 : this.#pam.length)) - 1;
+ }
+ }
+
+ computeEndIndex(index, strand, sequence) {
+ if (strand == '+') {
+ return (index + sequence.length - (this.getHelper().hasPAMInSequence() ? this.#pam.length : 0)) - 1;
+ } else {
+ return (index + (this.getHelper().hasPAMInSequence() ? this.#pam.length : 0)) - 1; // The PAM are the first n basis and have to be subtracted in this case
+ }
+ }
+
+ computePamOutDistance(index, strand, sequence, comparedEntryIndex, comparedEntryStrand, comparedEntrySequence) {
+ if (strand != comparedEntryStrand) {
+ const start = this.computeStartIndex(index, strand, sequence);
+ const comparedStart = this.computeStartIndex(comparedEntryIndex, comparedEntryStrand, comparedEntrySequence);
+
+ var distance = 0;
+ if (strand == '+') {
+ distance = start - comparedStart;
+ } else {
+ distance = comparedStart - start;
+ }
+ if (distance > 0) {
+ return distance;
+ }
+ }
+ return NaN;
+ }
+
+ computePamInDistance(index, strand, sequence, comparedEntryIndex, comparedEntryStrand, comparedEntrySequence) {
+ if (strand != comparedEntryStrand) {
+ const end = this.computeEndIndex(comparedEntryIndex, comparedEntryStrand, comparedEntrySequence);
+ const comparedEnd = this.computeEndIndex(index, strand, sequence);
+
+ var distance = 0;
+ if (strand == '+') {
+ distance = comparedEnd - end;
+ } else {
+ distance = end - comparedEnd;
+ }
+ if (distance > 0) {
+ return distance;
+ }
+ }
+ return NaN;
+ }
+
+ computePamNoneDistance(index, strand, sequence, comparedEntryIndex, comparedEntryStrand, comparedEntrySequence) {
+ const firstStart = this.computeStartIndex(index, strand, sequence);
+ const firstEnd = this.computeEndIndex(index, strand, sequence);
+ const secondStart = this.computeStartIndex(comparedEntryIndex, comparedEntryStrand, comparedEntrySequence);
+ const secondEnd = this.computeEndIndex(comparedEntryIndex, comparedEntryStrand, comparedEntrySequence);
+
+ return Math.max(firstStart, firstEnd, secondStart, secondEnd) - Math.min(firstStart, firstEnd, secondStart, secondEnd);
+ }
+
+ getMatchesOf(distancesMatrix, rank, distance) {
+ const matches = new Map();
+ for (var i = 0; i < this.#currentDataLength; i++) {
+ const currentDistance = distancesMatrix[rank - 1][i];
+ if ((i + 1) != rank && currentDistance != NaN && currentDistance > 0 && currentDistance <= distance) {
+ matches.set(i + 1, currentDistance);
+ }
+ }
+ return matches;
+ }
+
+ getPamOutMatches(rank, distance) {
+ return this.getMatchesOf(this.#pamOutDistances, rank, distance);
+ }
+
+ getPamInMatches(rank, distance) {
+ return this.getMatchesOf(this.#pamInDistances, rank, distance);
+ }
+
+ getNoneMatches(rank, distance) {
+ return this.getMatchesOf(this.#pamNoneDistances, rank, distance);
+ }
+
+ evaluateMatches(rank, restrictionEnzymeSequence, distance) {
+ const minIndex = this.getMinIndex();
+ // Getting the RE matches relative index and mapping to their absolute index by adding the chromosome extract start index (the minIndex)
+ const reMatchesIndexes = this.getAllMatches(
+ iGeneIndexer.iub2regexp(restrictionEnzymeSequence),
+ this.getGenomeInterestingExtract()
+ ).map(m => {return {index: minIndex + m.index, value: m.value}; });
+
+ const reReverseStrandMatchesIndexes = this.getAllMatches(
+ iGeneIndexer.iub2regexp(restrictionEnzymeSequence),
+ iGeneIndexer.reverseComplementOf(this.getGenomeInterestingExtract())
+ ).map(m => {return {index: minIndex + m.index + m.value.length, value: m.value}; });
+
+ const matches = [];
+ let gRNA = null;
+ this.#parsedTSV.forEach((arrayItem, aiIndex) => {
+ const aiRank = this.getHelper().hasRank() ? this.getHelper().getRank(arrayItem) : aiIndex + 1;
+ if (rank == aiRank) {
+ gRNA = arrayItem;
+ }
+ });
+ const sequence = this.getHelper().getSequence(gRNA);
+ const index = this.getHelper().getIndex(gRNA);
+ const strand = this.getHelper().getStrand(gRNA);
+
+ const start = this.computeStartIndex(index, strand, sequence);
+ const end = this.computeEndIndex(index, strand, sequence);
+ if (strand == '+') {
+ reMatchesIndexes.forEach((reMatch) => {
+ const reStartDistanceFromEnd = reMatch.index - end;
+ if (reStartDistanceFromEnd > 0 && reStartDistanceFromEnd <= distance) {
+ matches.push({chromosome: this.#chromosome, index: reMatch.index, sequence: reMatch.value, strand: '+', distance: reStartDistanceFromEnd});
+ }
+ const reEndDistanceFromStart = start - (reMatch.index + reMatch.value.length);
+ if (reEndDistanceFromStart > 0 && reEndDistanceFromStart <= distance) {
+ matches.push({chromosome: this.#chromosome, index: reMatch.index, sequence: reMatch.value, strand: '+', distance: -1 * reEndDistanceFromStart});
+ }
+ });
+ } else {
+ reReverseStrandMatchesIndexes.forEach((reRSMatch) => {
+ const reStartDistanceFromEnd = end - (reRSMatch.index + reRSMatch.value.length);
+ if (reStartDistanceFromEnd > 0 && reStartDistanceFromEnd <= distance) {
+ matches.push({chromosome: this.#chromosome, index: reRSMatch.index, sequence: reRSMatch.value, strand: '+', distance: reStartDistanceFromEnd});
+ }
+ const reEndDistanceFromStart = reRSMatch.index - start;
+ if (reEndDistanceFromStart > 0 && reEndDistanceFromStart <= distance) {
+ matches.push({chromosome: this.#chromosome, index: reRSMatch.index, sequence: reRSMatch.value, strand: '+', distance: -1 * reEndDistanceFromStart});
+ }
+ });
+ }
+ return matches;
+ }
+
+ getAllMatches(searchStr, str) {
+ var searchStrLen = searchStr.length;
+ if (searchStrLen == 0) {
+ return [];
+ }
+ const regEx = new RegExp(searchStr, "g");
+ const matches = [];
+ let match;
+ while ((match = regEx.exec(str)) !== null) {
+ matches.push({index: match.index, value: match[0]});
+ }
+ return matches;
+ }
+
+ async fetchActualGenomePartFromUSCS() {
+ return this.fetchGenomePartFromUSCS(this.getGenome(), this.getChromosome(), this.getMinIndex(), this.getMaxIndex());
+ }
+
+ /*
+ Respone JSON example:
+ {
+ "downloadTime": "2023:05:23T16:54:33Z",
+ "downloadTimeStamp": 1684860873,
+ "genome": "danRer10",
+ "chrom": "chr15",
+ "start": 43776601,
+ "end": 43794868,
+ "dna": "GTGCGCCGGA..."
+ }
+ */
+ async fetchGenomePartFromUSCS(genome, chromosome, start, end) {
+ const resp = await iGeneIndexer.fetchViaCORSProxy('https://genome-euro.ucsc.edu/cgi-bin/hubApi/getData/sequence?genome=' + genome + '&chrom=' + chromosome + '&start=' + start + '&end=' + end);
+ if (resp.ok) {
+ let json = await resp.json();
+ return json.dna;
+ } else {
+ let json = await resp.json();
+ throw "Error downloading genome part from USCS ([" + json.statusCode + ' - ' + json.statusMessage + '] - ' + json.error.split(' for endpoint')[0] + ')';
+ }
+ }
+
+ /*
+ Respone JSON example:
+ {
+ "downloadTime": "2023:05:23T17:11:47Z",
+ "downloadTimeStamp": 1684861907,
+ "genome": "danRer10",
+ "dataTime": "2015-01-22T19:35:19",
+ "dataTimeStamp": 1421951719,
+ "chromCount": 1061,
+ "chromosomes": {
+ "chr4": 76625712,
+ "chrUn_KN150525v1": 650,
+ "chrUn_KN150247v1": 728,
+ [...]
+ "chr3": 62385949,
+ "chr5": 71715914,
+ "chr7": 74082188
+ }
+ }
+ */
+ static async fetchGenomeChromosomesFromUSCS(genome) {
+ const resp = await iGeneIndexer.fetchViaCORSProxy('https://genome-euro.ucsc.edu/cgi-bin/hubApi/list/chromosomes?genome=' + genome);
+ if (resp.ok) {
+ let json = await resp.json();
+ return json.chromosomes;
+ } else {
+ throw "Error downloading '" + genome + "' genome's chromosomes from USCS";
+ }
+ }
+
+ static async loadAllRestrictionEnzymesFileFromREBASE() {
+ const allEnzymesResponse = await iGeneIndexer.fetchViaCORSProxy("http://rebase.neb.com/rebase/link_allenz");
+ if (allEnzymesResponse.ok) {
+ return await allEnzymesResponse.text();
+ } else {
+ const message = await allEnzymesResponse.text();
+ throw "Error downloading Restriction Enzymes list file from REBASE\n\n[" + allEnzymesResponse.status + (allEnzymesResponse.status == 404 ? ' - Not found' : '') + '] Message from service: ' + message;
+ }
+ }
+
+ static async fetchAndParseAllRestrictionEnzymesFileFromREBASE() {
+ return await this.loadAllRestrictionEnzymesFileFromREBASE().then(allEnzymesText => {
+ const enzymesSequence = allEnzymesText.substring(allEnzymesText.indexOf("<1>")).split("References:\n")[0].trim();
+ const enzymesToParseLines = enzymesSequence.split("\n");
+ const enzymes = [];
+ for (let i = 0; i < enzymesToParseLines.length; i += 9) {
+ enzymes.push({name: enzymesToParseLines[i].substring(3), sequence: enzymesToParseLines[i + 4].substring(3) });
+ }
+ return enzymes;
+ }
+ );
+ }
+
+ /**
+ * IUB2regexp
+ *
+ * A function that, given a sequence with IUB ambiguity codes,
+ * outputs a translation with IUB codes changed to a regular expression
+ *
+ * It is based on the IUB ambiguity codes:
+ * (Eur. J. Biochem. 150: 1-5, 1985):
+ * R = G or A
+ * Y = C or T
+ * M = A or C
+ * K = G or T
+ * S = G or C
+ * W = A or T
+ * B = not A (C or G or T)
+ * D = not C (A or G or T)
+ * H = not G (A or C or T)
+ * V = not T (A or C or G)
+ * N = A or C or G or T
+ *
+ */
+ static iub2regexp(iub) {
+
+ const iub2characterOrRegexp = {
+ 'A' : 'A',
+ 'C' : 'C',
+ 'G' : 'G',
+ 'T' : 'T',
+ 'R' : '[GA]',
+ 'Y' : '[CT]',
+ 'M' : '[AC]',
+ 'K' : '[GT]',
+ 'S' : '[GC]',
+ 'W' : '[AT]',
+ 'B' : '[CGT]',
+ 'D' : '[AGT]',
+ 'H' : '[ACT]',
+ 'V' : '[ACG]',
+ 'N' : '[ACGT]'
+ };
+
+ // Remove the ^ signs from the recognition sites
+ iub = iub.replace('\^', '');
+
+ let regularExpression = '';
+ // Translate each character in the iub sequence
+ [...iub].forEach(c => regularExpression += iub2characterOrRegexp[c]);
+
+ return regularExpression;
+ }
+
+
+ /**
+ * Returns the complement of a base, cosnidering the complete IUB notations
+ *
+ * Info source: https://arep.med.harvard.edu/labgc/adnan/projects/Utilities/revcomp.html
+ *
+ * @param {*} base the base to complement in IUB notation
+ * @returns the complement base (e.g.: For 'A' returns 'T')
+ */
+ static complement(base) {
+ return { A: 'T', T: 'A', G: 'C', C: 'G', Y: 'R', R: 'Y', S: 'S', W: 'W', K: 'M', M: 'K', B: 'V', D: 'H', H: 'D', V: 'B', N: 'N'}[base];
+ }
+
+ /**
+ * Converts a sequence to its reverse complement, considering the complete IUB notations by using the `complement(base)` function.
+ * @param {*} sequence the sequence to be converted
+ * @returns the reverse complement of the sequence
+ */
+ static reverseComplementOf(sequence) {
+ return sequence.split('').reverse().map(iGeneIndexer.complement).join('');
+ }
+
+ static fetchViaCORSProxy(url) {
+ return fetch("https://corsproxy.io/?" + encodeURIComponent(url));
+ }
+}
+
+class AbstractImporter extends HTMLElement {
+
+ #rootdoc = null;
+ #headers = null
+ #results = null;
+ #indexer = null;
+ #helper = null;
+
+ constructor(helper){
+ super()
+ this.#helper = helper;
+ this.#rootdoc = this.attachShadow({ "mode" : "open"})
+ this.render()
+ }
+
+ getRootDoc() {
+ return this.#rootdoc;
+ }
+
+ render(){
+ this.getRootDoc().innerHTML = ``;
+ }
+
+ parseTSV(tsvString) {
+ const results = tsvString.split("\n");
+ this.#headers = results[0].split("\t");
+ this.#results = [];
+ results.forEach((line, i)=>{ if(i > 0 && line.trim().length > 0) this.#results.push(line.split("\t")) });
+ }
+
+ importTSVExport(file, genome, pam) {
+ const reader = new FileReader();
+ reader.addEventListener("load", () => {
+ this.parseTSV(reader.result);
+ this.setIndexer(new iGeneIndexer(this.#results, genome, pam, this.getHelper()));
+ this.enableData();
+ })
+ reader.readAsText(file);
+ }
+
+ importTSVURL(tsvURL, genome, pam) {
+ fetch(tsvURL).then(resp => {
+ if (resp.ok) {
+ resp.text().then(textData => {
+ this.parseTSV(textData);
+ this.setIndexer(new iGeneIndexer(this.#results, genome, pam, this.getHelper()));
+ this.enableData();
+ })
+ } else {
+ resp.text().then(page => {
+ throw "Error loading TSV from URL: " + tsvURL + " (" + page + ")";
+ });
+ }
+ });
+ }
+
+ enableData() {
+ }
+
+ #API
+ setIndexer(indexer) {
+ this.#indexer = indexer;
+ }
+
+ getIndexer() {
+ return this.#indexer;
+ }
+
+ getHelper() {
+ return this.#helper;
+ }
+
+ getHeader() {
+ // Abstract, to be ovverrided in subclass
+ }
+
+ getRenderer(filter, output){
+ if (filter.mode == 're') {
+ if(output === "html") {
+ return new DefaultHTMLRETableRenderer(this, filter);
+ } else if(output === "tsv") {
+ return alert("This is not supported in R.E. mode");
+ } else if(output === "tsv2") {
+ return new DefaultTSVRETableRenderer(this, filter, "tsv2");
+ }
+ } else {
+ if(output === "html") {
+ return new DefaultHTMLDimerTableRenderer(this, filter);
+ } else if(output === "tsv") {
+ return new DefaultTSVDimerTableRenderer(this, filter);
+ } else if(output === "tsv2") {
+ return new DefaultTSVDimerTableRenderer(this, filter, "tsv2");
+ }
+ }
+ }
+
+ setTableHeaders(headers){
+ this.#headers = headers;
+ }
+
+ getTableHeaders(){
+ return this.#headers;
+ }
+
+ hasRank() {
+ return this.getHelper().hasRank();
+ }
+
+ getRank(line) {
+ return this.getHelper().getRank(line);
+ }
+
+ setTableLines(results){
+ this.#results = results;
+ }
+
+ getTableLines(){
+ return this.#results;
+ }
+
+ getTableLine(i){
+ return this.#results[i];
+ }
+
+ getTableLineByRank(r){
+ return this.#results[r-1];
+ }
+
+ getMatches(pam, rank, distance){
+ if(pam === "in"){
+ return this.getIndexer().getPamInMatches(rank, distance)
+ } else if(pam === "out"){
+ return this.getIndexer().getPamOutMatches(rank, distance)
+ } else {
+ var m = new Map();
+ this.getIndexer().getPamInMatches(rank, distance).forEach((v, k) => { m.set(k, v) })
+ this.getIndexer().getPamOutMatches(rank, distance).forEach((v, k) => { m.set(k, v) })
+ this.getIndexer().getNoneMatches(rank, distance).forEach((v, k) => { m.set(k, v) })
+ return m;
+ }
+ }
+
+ getREMatchesForRankAt(rank, re, distance) {
+ return this.getIndexer().evaluateMatches(rank, re, distance);
+ }
+
+ getDatadisplay() {
+ return document.querySelector("igene-data-display");
+ }
+}
+
+class AbstractHelper {
+ #defaultChromosome
+ constructor() {
+ if (this.constructor == AbstractHelper) {
+ throw new Error("AbstractHelper class can't be instantiated");
+ }
+ }
+ hasPAMInSequence() {
+ throw new Error("Method 'hasPAMInSequence' must be implementd");
+ }
+ hasRank() {
+ throw new Error("Method 'hasRank' must be implementd");
+ }
+ getRank(line) {
+ throw new Error("Method 'getRank' must be implementd");
+ }
+ getSequence(line) {
+ throw new Error("Method 'getTargetSequence' must be implementd");
+ }
+ hasChromosome() {
+ throw new Error("Method 'hasChromosome' must be implementd");
+ }
+ getChromosome(line) {
+ throw new Error("Method 'getChromosome' must be implementd");
+ }
+ getIndex(line) {
+ throw new Error("Method 'getIndex' must be implementd");
+ }
+ getStrand(line) {
+ throw new Error("Method 'getStrand' must be implementd");
+ }
+ setDefaultChromosome(chromosome) {
+ this.#defaultChromosome = chromosome;
+ }
+ getDefaultChromosome() {
+ return this.#defaultChromosome;
+ }
+}
+
+class ChopChopHelper extends AbstractHelper {
+ hasPAMInSequence() {
+ return true;
+ }
+ hasRank() {
+ return true;
+ }
+ getRank(line) {
+ return +line[0];
+ }
+ getSequence(line) {
+ return line[1];
+ }
+ hasChromosome() {
+ return true;
+ }
+ getChromosome(line) {
+ return line[2].split(':')[0];
+ }
+ getIndex(line) {
+ return +line[2].split(':')[1];
+ }
+ getStrand(line) {
+ return line[3];
+ }
+}
+
+class CrisprscanHelper extends AbstractHelper {
+ constructor() {
+ super();
+ }
+ hasPAMInSequence() {
+ return true;
+ }
+ hasRank() {
+ return false;
+ }
+ getSequence(line) {
+ return line[5];
+ }
+ hasChromosome() {
+ return false;
+ }
+ getChromosome(line) {
+ return null;
+ }
+ getIndex(line) {
+ return +line[1];
+ }
+ getStrand(line) {
+ return line[3];
+ }
+}
+
+class AbstractTableRenderer{
+
+ constructor(){}
+
+ renderTable(){
+ alert("Not yet implemented")
+ }
+
+ renderHeaders(){
+ alert("Not yet implemented")
+ }
+
+ renderRows(){
+ alert("Not yet implemented")
+ }
+
+ renderMatchRows(){
+ alert("Not yet implemented")
+ }
+}
+
+class DefaultHTMLDimerTableRenderer extends AbstractTableRenderer {
+ #source = null
+ #output = ""
+ #filter = null
+
+ constructor(source, filter){
+ super()
+ this.#source = source
+ this.#filter = filter
+ }
+
+ getOutput(){
+ return this.#output
+ }
+
+ renderTable(){
+ const s = this.#source
+ this.#output = `
+ I-GENE tool is an online tool to analyse your data for selecting gRNAs in pair or gRNAs in proximity to a desired restriction enzyme.
+The input information comes from the web tool CHOPCHOP either as a link or as an imported TSV file and from CrisprScan as imported TSV.
+Please follow the tutorial on how to use the I-GeneMatcher. If your video does not play you can download the video from here.
+ +The following tables describe the meaning of the columns in the various data tables produced as output by the I-GeneMatcher.
+Column | +Description | +
Rank | +Rank of guide 1 as reported in CHOPCHOP | +
Match rank | +Rank of guide 2 as reported in CHOPCHOP | +
Distance | +Precise distance between the guides with the selected configuration | +
Target sequence | +Sequence of guide 1 | +
Genomic location | +Position of guide 1 | +
Match Genomic location | +Position of guide 2 | +
Strand | +DNA strand that the guide 1 targets | +
Match Strand | +DNA strand that the guide 2 targets | +
GC content (%) | +GC content percentage of guide 1 | +
Match GC content (%) | +GC content percentage of guide 2 | +
Self-complementarity | +Self-complementarity value of guide 1 as reported in CHOPCHOP | +
Match Self-complementarity | +Self-complementarity value of guide 2 as reported in CHOPCHOP | +
MM0-3 | +Targets containing 0 to 3 mismatches for guide 1 | +
Match MM0-3 | +Targets containing 0 to 3 mismatches for guide 2 | +
Efficiency | +Efficiency value of guide 1 as reported in CHOPCHOP | +
Match Efficiency | +Efficiency value of guide 2 as reported in CHOPCHOP | +
Column | +Description | +||||||||
Rank | +Rank of guide 1 as reported in crisprscan | +||||||||
Match rank | +Rank of guide 2 as reported in crisprscan | +||||||||
Distance | +Precise distance between the guides with the selected configuration | +||||||||
Name | +Targeted chromosome of guide 1 | +||||||||
Match Name | +Targeted chromosome of guide 2 | +||||||||
Start | +Starting genomic position of guide 1 | +||||||||
Match Start | +Starting genomic position of guide 2 | +||||||||
End | +Ending genomic position of guide 1 | +||||||||
Match End | +Ending genomic position of guide 2 | +||||||||
Strand | +DNA strand that the guide 1 targets | +||||||||
Match Strand | +DNA strand that the guide 2 targets | +||||||||
Type | +20 nucleotides following the type of PAM for guide 1 | +||||||||
Match Type | +20 nucleotides following the type of PAM for guide 2 | +||||||||
Seq | +Targeting sequence containing the PAM sequence of guide 1 | +||||||||
Match Seq | +Targeting sequence containing the PAM sequence of guide 2 | +||||||||
Sgrna_seq | +Sequence of guide 1 | +||||||||
Match Sgrna_seq | +Sequence of guide 2 | +||||||||
Promoter | +If selected during the crisprscan research | +||||||||
Match Promoter | +If selected during the crisprscan research | +||||||||
Remaining values as reported by crisprscan. Prefix "Match" always indicates the corresponding values of guide 2. | +
Column | +Description | +
RE sequence | +Restriction site of the selected restriction enzyme | +
RE index | +Position of the restriction site | +
RE strand | +5'- 3' | +
Distance | +Precise distance from the guide | +
Rank | +Rank of the guide as reported in CHOPCHOP | +
Target sequence | +Sequence of the guide | +
Genomic location | +Position of the guide | +
Strand | +DNA strand that the guide targets | +
GC content (%) | +GC content percentage of the guide | +
Self-complementarity | +Self-complementarity value of the guide as reported in CHOPCHOP | +
MM0-3 | +Targets containing 0 to 3 mismatches | +
Efficiency | +Efficiency value as reported in CHOPCHOP | +
Column | +Description | +||||||||
RE sequence | +Restriction site of the selected restriction enzyme | +||||||||
RE index | +Position of the restriction site | +||||||||
RE strand | +5' - 3' | +||||||||
Distance | +Precise distance from the guide | +||||||||
Rank | +Rank of the guide as reported in crisprscan | +||||||||
Name | +Targeted chromosome | +||||||||
Start | +Starting genomic position of the guide | +||||||||
End | +Ending genomic position of the guide | +||||||||
Strand | +DNA strand that the guide targets | +||||||||
Type | +20 nucleotides following the type of PAM | +||||||||
Seq | +Targeting sequence containing the PAM sequence | +||||||||
Sgrna_seq | +Sequence of the guide | +||||||||
Promoter | +If selected during the crisprscan research | +||||||||
Remaining values as reported by crisprscan. | +