master #25

Merged
m.lettere merged 22 commits from master into prod 2025-01-10 15:15:33 +01:00
19 changed files with 2531 additions and 93 deletions

View File

@ -21,6 +21,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
#queue = [] #queue = []
#interval = null #interval = null
#config = null #config = null
#uma = false
#rpt = null #rpt = null
constructor() { constructor() {
@ -63,8 +64,8 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
console.log("Keycloak initialized and user authenticated") console.log("Keycloak initialized and user authenticated")
//console.log("Token exp: " + this.expirationDate(this.#keycloak.tokenParsed.exp)) //console.log("Token exp: " + this.expirationDate(this.#keycloak.tokenParsed.exp))
//if an audience is provided then perform also authorization //if an audience is provided and UMA flow requested then perform also authorization
if (this.#audience) { if (this.#audience && this.#uma) {
return this.loadConfig() return this.loadConfig()
} else { } else {
Promise.resolve() Promise.resolve()
@ -101,7 +102,11 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
clientId: this.#clientId 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() { startStateChecker() {
@ -113,7 +118,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
} else { } else {
if (this.#queue.length > 0) { if (this.#queue.length > 0) {
this.#keycloak.updateToken(30).then(() => { this.#keycloak.updateToken(30).then(() => {
if (this.#audience) { if (this.#uma && this.#audience) {
//console.log("Checking entitlement for audience", this.#audience) //console.log("Checking entitlement for audience", this.#audience)
const audience = encodeURIComponent(this.#audience) const audience = encodeURIComponent(this.#audience)
return this.entitlement(audience) return this.entitlement(audience)
@ -156,18 +161,19 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
return d return d
} }
checkContext() { // TODO: Candidate for removal
const parseJwt = this.parseJwt // checkContext() {
const expDt = this.expirationDate // const parseJwt = this.parseJwt
const audience = encodeURIComponent(this.#audience) // const expDt = this.expirationDate
this.entitlement(audience).then(function (rpt) { // const audience = encodeURIComponent(this.#audience)
// onGrant callback function. // this.entitlement(audience).then(function (rpt) {
// If authorization was successful you'll receive an RPT // // onGrant callback function.
// with the necessary permissions to access the resource server // // If authorization was successful you'll receive an RPT
//console.log(rpt) // // with the necessary permissions to access the resource server
//console.log("rpt expires: " + expDt(parseJwt(rpt).exp)) // //console.log(rpt)
}) // //console.log("rpt expires: " + expDt(parseJwt(rpt).exp))
} // })
// }
secureFetch(url, request) { secureFetch(url, request) {
const p = new Promise((resolve, reject) => { const p = new Promise((resolve, reject) => {
@ -291,7 +297,7 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
} }
static get observedAttributes() { static get observedAttributes() {
return ["url", "realm", "gateway", "redirect-url", "context"]; return ["url", "realm", "gateway", "redirect-url", "context", "uma"];
} }
attributeChangedCallback(name, oldValue, newValue) { attributeChangedCallback(name, oldValue, newValue) {
@ -312,10 +318,17 @@ window.customElements.define('d4s-boot-2', class extends HTMLElement {
case "context": case "context":
this.#audience = newValue this.#audience = newValue
break break
case "uma":
this.#uma = newValue === "true" ? true : false
break
} }
} }
} }
get uma(){
return this.#uma
}
get authenticated(){ get authenticated(){
return this.#authenticated return this.#authenticated
} }

View File

@ -491,7 +491,7 @@ class CCPExecutionForm extends HTMLElement {
"in": (e, d) => { return Object.values(d.inputs) }, "in": (e, d) => { return Object.values(d.inputs) },
target: "div", target: "div",
apply: (e, d) => { apply: (e, d) => {
e.innerHTML = `<d4s-ccp-input name="${d.id}" input='${btoa(JSON.stringify(d))}'></d4s-ccp-input>` e.innerHTML = `<d4s-ccp-input name="${d.id}" input='${base64EncodeUnicode(JSON.stringify(d))}'></d4s-ccp-input>`
} }
} }
] ]
@ -504,7 +504,7 @@ class CCPExecutionForm extends HTMLElement {
"in": (e, d) => { return Object.values(d.outputs) }, "in": (e, d) => { return Object.values(d.outputs) },
target: "div", target: "div",
apply: (e, d) => { apply: (e, d) => {
e.innerHTML = `<d4s-ccp-output name="${d.id}" output='${btoa(JSON.stringify(d))}'></d4s-ccp-output>` e.innerHTML = `<d4s-ccp-output name="${d.id}" output='${base64EncodeUnicode(JSON.stringify(d))}'></d4s-ccp-output>`
} }
} }
] ]

View File

@ -53,7 +53,9 @@ class CCPExecutionHistory extends HTMLElement {
"runtime_help" : "The runtime where this execution has been scheduled on", "runtime_help" : "The runtime where this execution has been scheduled on",
"loading_archived_help" : "Loading archived executions from workspace. This can take a while.", "loading_archived_help" : "Loading archived executions from workspace. This can take a while.",
"err_reexecute" : "Unable to re-submit the execution. Please check console for further info", "err_reexecute" : "Unable to re-submit the execution. Please check console for further info",
"err_generate_code_for" : "Unable to generate code for " "err_generate_code_for" : "Unable to generate code for ",
"show_metadata" : "Show metadata",
"show_metadata_help" : "Show also metadata related to this execution",
} }
} }
@ -180,8 +182,12 @@ class CCPExecutionHistory extends HTMLElement {
</div> </div>
<div name="logterminalcontainer" style="margin:5px 0 5px 0"> <div name="logterminalcontainer" style="margin:5px 0 5px 0">
</div> </div>
<ul> <ul class="my-3" style="list-style:none;padding-left:0;max-height:8rem; overflow-y:auto; overflow-x:hidden">
<li></li> <label class="form-check form-switch form-check-inline">
<span class="mr-2" alt="${this.getLabel("show_metadata_help")}" title="${this.getLabel("show_metadata_help")}">${this.getLabel("show_metadata")}</span>
<input class="form-check-input" type="checkbox" name="toggle_meta">
</label>
<li style="text-overflow: ellipsis;white-space:nowrap;overflow-x: clip;"></li>
</ul> </ul>
<div class="d-flex flex-column align-items-end" style="gap: 3px;"> <div class="d-flex flex-column align-items-end" style="gap: 3px;">
<div class="d-flex align-items-center" style="gap:5px" title="${this.getLabel("generate_code_help")}"> <div class="d-flex align-items-center" style="gap:5px" title="${this.getLabel("generate_code_help")}">
@ -228,11 +234,11 @@ class CCPExecutionHistory extends HTMLElement {
<button data-index="0" name="reexecute2" title="${this.getLabel("re-submit_help")}" class="btn btn-info ccp-toolbar-button ccp-toolbar-button-small"> <button data-index="0" name="reexecute2" title="${this.getLabel("re-submit_help")}" class="btn btn-info ccp-toolbar-button ccp-toolbar-button-small">
${this.getLabel("re-submit")} ${this.getLabel("re-submit")}
</button> </button>
<button name="restore" title="${this.getLabel("restore_execution_help")}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small"> <!--button name="restore" title="${this.getLabel("restore_execution_help")}" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
<svg viewBox="0 -960 960 960" width="24px" fill="#5f6368"> <svg viewBox="0 -960 960 960" width="24px" fill="#5f6368">
<path d="M480-250q78 0 134-56t56-134q0-78-56-134t-134-56q-38 0-71 14t-59 38v-62h-60v170h170v-60h-72q17-18 41-29t51-11q54 0 92 38t38 92q0 54-38 92t-92 38q-44 0-77-25.5T356-400h-62q14 65 65.5 107.5T480-250ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm0-80h480v-446L526-800H240v640Zm0 0v-640 640Z"/> <path d="M480-250q78 0 134-56t56-134q0-78-56-134t-134-56q-38 0-71 14t-59 38v-62h-60v170h170v-60h-72q17-18 41-29t51-11q54 0 92 38t38 92q0 54-38 92t-92 38q-44 0-77-25.5T356-400h-62q14 65 65.5 107.5T480-250ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm0-80h480v-446L526-800H240v640Zm0 0v-640 640Z"/>
</svg> </svg>
</button> </button-->
<button name="delete" title="${this.getLabel("delete_help")}" class="btn btn-danger ccp-toolbar-button ccp-toolbar-button-small"> <button name="delete" title="${this.getLabel("delete_help")}" class="btn btn-danger ccp-toolbar-button ccp-toolbar-button-small">
<svg viewBox="0 0 24 24"> <svg viewBox="0 0 24 24">
<path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"></path> <path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"></path>
@ -695,6 +701,7 @@ class CCPExecutionHistory extends HTMLElement {
//parse all metada content that is useful to build up an entry //parse all metada content that is useful to build up an entry
const metadata = await this.#ws.download(metadatafolder[0].id, null, true) const metadata = await this.#ws.download(metadatafolder[0].id, null, true)
const zip = new JSZip() const zip = new JSZip()
try{
const req = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/request.json").async("string")))) const req = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/request.json").async("string"))))
const status = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/jobStatus.json").async("string")))) const status = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/jobStatus.json").async("string"))))
const meth = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/method.json").async("string")))) const meth = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/method.json").async("string"))))
@ -727,6 +734,9 @@ class CCPExecutionHistory extends HTMLElement {
} }
if(this.#archived[entry.method]) this.#archived[entry.method].push(entry); if(this.#archived[entry.method]) this.#archived[entry.method].push(entry);
else this.#archived[entry.method] = [entry] else this.#archived[entry.method] = [entry]
} catch (err){
console.warn(`Skipping entry ${metadatafolder[0].id} because of ${err} created at ${new Date(metadatafolder[0].creationTime)}`, metadatafolder[0])
}
} }
} }
} }
@ -765,9 +775,15 @@ class CCPExecutionHistory extends HTMLElement {
{ {
target : "details[name=level1]", target : "details[name=level1]",
apply : (e,d)=>{ apply : (e,d)=>{
if(!!d && d !== "undefined"){
e.alt = e.title = d e.alt = e.title = d
if(sessionStorage.getItem(d) === "open") e.open = "open"; if(sessionStorage.getItem(d) === "open") e.open = "open";
else e.removeAttribute("open"); else e.removeAttribute("open");
}else{
//hide away entries that may occur as empty because of timing issues on the server
e.style.display = "none"
this.showLoading(e.parentElement)
}
}, },
on_toggle : ev=>{ on_toggle : ev=>{
if(ev.target.open){ if(ev.target.open){
@ -957,12 +973,20 @@ class CCPExecutionHistory extends HTMLElement {
}, },
{ {
target : "ul", target : "ul",
on_click: ev=>{
const tgt = ev.target
const ul = ev.currentTarget
if(tgt.getAttribute("name") === "toggle_meta"){
Array.prototype.slice.call(ul.querySelectorAll("li.metadata")).forEach(e=>e.classList.toggle("d-none"))
}
},
recurse : [ recurse : [
{ {
target : "li", target : "li",
"in" : (e,d)=>{ "in" : (e,d)=>{
return d.resources.map(l=>{ const resources = d.resources ? d.resources : []
return { href : this.#serviceurl + "/executions/" + d.id + "/" + l.path, path : l.path} return resources.map(l=>{
return { id: d.id, size: l.size, href : this.#serviceurl + "/executions/" + d.id + "/" + l.path, path : l.path}
}) })
}, },
on_click : ev=>{ on_click : ev=>{
@ -971,7 +995,17 @@ class CCPExecutionHistory extends HTMLElement {
this.download(href, name) this.download(href, name)
}, },
apply : (e,d)=>{ apply : (e,d)=>{
e.innerHTML = `<a href="${d.href}" onclick="event.preventDefault()">${d.path}</a>` const regexp = /^metadata.+|^auth.+|.+\/ccp-entrypoint.sh$|.+\/ccpenv$|.+\/docker-compose.yaml/
var size = ""
if(d.path.match(regexp)){
e.classList.add("metadata")
e.classList.add("d-none")
}else if(d.size){
const s = Number(d.size)
var i = s === 0 ? 0 : Math.floor(Math.log(s) / Math.log(1024));
size = `[${+((s / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]}]`
}
e.innerHTML = `<a href="${d.href}" onclick="event.preventDefault()">${d.path}</a><small class="text-muted ml-2">${size} </small>`
} }
} }
] ]
@ -1030,10 +1064,10 @@ class CCPExecutionHistory extends HTMLElement {
}, },
on_click: ev=>{ on_click: ev=>{
if(ev.target.getAttribute("name") === "restore"){ if(ev.target.getAttribute("name") === "restore"){
if(window.confirm(this.getLabel("confirm_restore_execution"))){ // if(window.confirm(this.getLabel("confirm_restore_execution"))){
const href = ev.currentTarget.getAttribute("data-href") // const href = ev.currentTarget.getAttribute("data-href")
this.fromArchiveFolder(href) // this.fromArchiveFolder(href)
} // }
}else if(ev.target.getAttribute("name") === "delete"){ }else if(ev.target.getAttribute("name") === "delete"){
if(window.confirm(this.getLabel("confirm_delete_archived_execution"))){ if(window.confirm(this.getLabel("confirm_delete_archived_execution"))){
const wsid = ev.currentTarget.getAttribute("data-wsid") const wsid = ev.currentTarget.getAttribute("data-wsid")

View File

@ -7,65 +7,65 @@ class CCPInputWidgetController extends HTMLElement {
} }
connectedCallback() { connectedCallback() {
this.#data = JSON.parse(atob(this.getAttribute("input"))) this.#data = JSON.parse(base64DecodeUnicode(this.getAttribute("input")))
if (this.isChecklist()) { if (this.isChecklist()) {
const opts = this.#data.schema.enum.join(",") const opts = this.#data.schema.enum.join(",")
this.innerHTML += `<d4s-ccp-input-checklist readonly="${this.#data.schema.readOnly}" options="${opts}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-checklist>` this.innerHTML += `<d4s-ccp-input-checklist readonly="${this.#data.schema.readOnly}" options="${opts}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-checklist>`
this.querySelector("d4s-ccp-input-checklist").default = this.#data.schema.default this.querySelector("d4s-ccp-input-checklist").default = this.#data.schema.default
} else if (this.isEnum()) { } else if (this.isEnum()) {
const opts = this.#data.schema.enum.join(",") const opts = this.#data.schema.enum.join(",")
this.innerHTML += `<d4s-ccp-input-enum readonly="${this.#data.schema.readOnly}" options="${opts}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-enum>` this.innerHTML += `<d4s-ccp-input-enum readonly="${this.#data.schema.readOnly}" options="${opts}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-enum>`
this.querySelector("d4s-ccp-input-enum").default = this.#data.schema.default this.querySelector("d4s-ccp-input-enum").default = this.#data.schema.default
} else if (this.isCode()) { } else if (this.isCode()) {
this.innerHTML += `<d4s-ccp-input-textarea readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-textarea>` this.innerHTML += `<d4s-ccp-input-textarea readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-textarea>`
this.querySelector("d4s-ccp-input-textarea").default = this.#data.schema.default this.querySelector("d4s-ccp-input-textarea").default = this.#data.schema.default
} else if (this.isDateTime()) { } else if (this.isDateTime()) {
const t = this.#data.schema.format.toLowerCase() === "datetime" ? const t = this.#data.schema.format.toLowerCase() === "datetime" ?
"datetime-local" : this.#data.schema.format.toLowerCase() "datetime-local" : this.#data.schema.format.toLowerCase()
this.innerHTML += `<d4s-ccp-input-simple type="${t}" readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-simple>` this.innerHTML += `<d4s-ccp-input-simple type="${t}" readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-simple>`
this.querySelector("d4s-ccp-input-simple").default = this.#data.schema.default this.querySelector("d4s-ccp-input-simple").default = this.#data.schema.default
} else if (this.isFile()) { } else if (this.isFile()) {
this.innerHTML += `<d4s-ccp-input-file readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-file>` this.innerHTML += `<d4s-ccp-input-file readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-file>`
this.querySelector("d4s-ccp-input-file").default = this.#data.schema.default this.querySelector("d4s-ccp-input-file").default = this.#data.schema.default
} else if (this.isRemoteFile()) { } else if (this.isRemoteFile()) {
this.innerHTML += `<d4s-ccp-input-remotefile readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-remotefile>` this.innerHTML += `<d4s-ccp-input-remotefile readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-remotefile>`
this.querySelector("d4s-ccp-input-remotefile").default = this.#data.schema.default this.querySelector("d4s-ccp-input-remotefile").default = this.#data.schema.default
} else if (this.isGeo()) { } else if (this.isGeo()) {
this.innerHTML += `<d4s-ccp-input-geo readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-remotefile>` this.innerHTML += `<d4s-ccp-input-geo readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-remotefile>`
this.querySelector("d4s-ccp-input-geo").default = this.#data.schema.default this.querySelector("d4s-ccp-input-geo").default = this.#data.schema.default
} else if (this.isSecret()) { } else if (this.isSecret()) {
this.innerHTML += `<d4s-ccp-input-secret readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs }" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-secret>` this.innerHTML += `<d4s-ccp-input-secret readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs }" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-secret>`
this.querySelector("d4s-ccp-input-secret").default = this.#data.schema.default this.querySelector("d4s-ccp-input-secret").default = this.#data.schema.default
} else if (this.isBoolean()) { } else if (this.isBoolean()) {
this.innerHTML += `<d4s-ccp-input-boolean readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-boolean>` this.innerHTML += `<d4s-ccp-input-boolean readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-boolean>`
this.querySelector("d4s-ccp-input-boolean").default = this.#data.schema.default this.querySelector("d4s-ccp-input-boolean").default = this.#data.schema.default
} else if (this.isNumber()) { } else if (this.isNumber()) {
this.innerHTML += `<d4s-ccp-input-simple type="number" readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-simple>` this.innerHTML += `<d4s-ccp-input-simple type="number" readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-simple>`
this.querySelector("d4s-ccp-input-simple").default = this.#data.schema.default this.querySelector("d4s-ccp-input-simple").default = this.#data.schema.default
} else { } else {
this.innerHTML += `<d4s-ccp-input-simple readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${btoa(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-simple>` this.innerHTML += `<d4s-ccp-input-simple readonly="${this.#data.schema.readOnly}" default="${this.#data.schema.default}" maxOccurs="${this.#data.maxOccurs}" minOccurs="${this.#data.minOccurs}" name="${this.#data.id}" description="${base64EncodeUnicode(this.#data.description)}" title="${this.#data.title}"></d4s-ccp-input-simple>`
this.querySelector("d4s-ccp-input-simple").default = this.#data.schema.default this.querySelector("d4s-ccp-input-simple").default = this.#data.schema.default
} }
@ -173,7 +173,7 @@ class CCPBaseInputWidgetController extends HTMLElement {
this.#rootdoc = this//this.attachShadow({ mode: "open" }); this.#rootdoc = this//this.attachShadow({ mode: "open" });
this.#name = this.getAttribute("name") this.#name = this.getAttribute("name")
this.#title = this.getAttribute("title") this.#title = this.getAttribute("title")
this.#description = this.getAttribute("description") this.#description = base64DecodeUnicode(this.getAttribute("description"))
//this.#default = this.getAttribute("default") //this.#default = this.getAttribute("default")
this.#minOccurs = Number(this.getAttribute("minoccurs") ? this.getAttribute("minoccurs") : 1) this.#minOccurs = Number(this.getAttribute("minoccurs") ? this.getAttribute("minoccurs") : 1)
this.#maxOccurs = Number(this.getAttribute("maxoccurs") ? this.getAttribute("maxoccurs") : 1) this.#maxOccurs = Number(this.getAttribute("maxoccurs") ? this.getAttribute("maxoccurs") : 1)
@ -296,7 +296,7 @@ class CCPBaseInputWidgetController extends HTMLElement {
</div> </div>
</div> </div>
<div> <div>
<small class="text-muted">${atob(this.#description)}</small> <small class="text-muted">${this.#description}</small>
</div> </div>
<div name="content"> <div name="content">
</div> </div>
@ -616,7 +616,7 @@ class CCPRemoteFileInputWidgetController extends CCPBaseInputWidgetController {
</div> </div>
<small class="text-muted m-0">Select an item or drag and drop it to a proper input</small> <small class="text-muted m-0">Select an item or drag and drop it to a proper input</small>
</div> </div>
<div style="min-width:500px; max-width:640px;overflow:auto;etxt-wrap:nowrap;text-overflow: ellipsis;min-height:5rem; max-height:10rem;"> <div style="min-width:500px; max-width:640px;overflow:auto;text-wrap:nowrap;text-overflow: ellipsis;min-height:5rem; max-height:10rem;">
<d4s-storage-tree <d4s-storage-tree
base-url="${addresses[iss]}" base-url="${addresses[iss]}"
file-download-enabled="true" file-download-enabled="true"

View File

@ -66,7 +66,7 @@ class CCPInputWidgetEditorController extends HTMLElement {
} }
renderDefaultByType() { renderDefaultByType() {
return `<d4s-ccp-input name="default" input="${btoa(JSON.stringify(this.#input))}"></d4s-ccp-input>` return `<d4s-ccp-input name="default" input="${base64EncodeUnicode(JSON.stringify(this.#input))}"></d4s-ccp-input>`
} }
renderDeleteButton() { renderDeleteButton() {
@ -144,7 +144,7 @@ class CCPInputWidgetEditorController extends HTMLElement {
<option value="date" ${this.isSelectedFormat('date') ? "selected" : ""}>Date</option> <option value="date" ${this.isSelectedFormat('date') ? "selected" : ""}>Date</option>
<option value="time" ${this.isSelectedFormat('time') ? "selected" : ""}>Time</option> <option value="time" ${this.isSelectedFormat('time') ? "selected" : ""}>Time</option>
<option value="dateTime" ${this.isSelectedFormat('dateTime') ? "selected" : ""}>Date time</option> <option value="dateTime" ${this.isSelectedFormat('dateTime') ? "selected" : ""}>Date time</option>
<option value="number" ${this.isSelectedFormat('number') ? "selected" : ""}>Number</option> <option value="number" ${this.isSelectedFormat('number') ? "selected" : ""}>Integer Number</option>
<option value="boolean" ${this.isSelectedFormat('boolean') ? "selected" : ""}>True/False</option> <option value="boolean" ${this.isSelectedFormat('boolean') ? "selected" : ""}>True/False</option>
<option value="code" ${this.isSelectedFormat('code') ? "selected" : ""}>Code</option> <option value="code" ${this.isSelectedFormat('code') ? "selected" : ""}>Code</option>
<option value="file" ${this.isSelectedFormat('file') ? "selected" : ""}>File</option> <option value="file" ${this.isSelectedFormat('file') ? "selected" : ""}>File</option>

View File

@ -4,7 +4,7 @@ class CCPOutputWidgetController extends HTMLElement {
constructor(){ constructor(){
super() super()
this.#output = JSON.parse(atob(this.getAttribute("output"))) this.#output = JSON.parse(base64DecodeUnicode(this.getAttribute("output")))
} }
connectedCallback(){ connectedCallback(){

View File

@ -217,6 +217,10 @@
} else { } else {
kc.enableLogging = false; kc.enableLogging = false;
} }
if (typeof initOptions.scope === 'string') {
kc.scope = initOptions.scope;
}
} }
if (!kc.responseMode) { if (!kc.responseMode) {
@ -438,15 +442,13 @@
baseUrl = kc.endpoints.authorize(); baseUrl = kc.endpoints.authorize();
} }
var scope; var scope = options && options.scope || kc.scope;
if (options && options.scope) { if (!scope) {
if (options.scope.indexOf("openid") != -1) { // if scope is not set, default to "openid"
scope = options.scope;
} else {
scope = "openid " + options.scope;
}
} else {
scope = "openid"; scope = "openid";
} else if (scope.indexOf("openid") === -1) {
// if openid scope is missing, prefix the given scopes with it
scope = "openid " + scope;
} }
var url = baseUrl var url = baseUrl

25
common/js/utils.js Normal file
View File

@ -0,0 +1,25 @@
function base64EncodeUnicode(str) {
// Encode the string as UTF-8 bytes
const utf8Bytes = new TextEncoder().encode(str);
// Convert the bytes to a binary string
let binary = '';
utf8Bytes.forEach(byte => {
binary += String.fromCharCode(byte);
});
// Encode the binary string to Base64
return btoa(binary);
}
function base64DecodeUnicode(base64) {
// Decode the Base64 string to a binary string
const binary = atob(base64);
// Convert the binary string to a Uint8Array
const bytes = Uint8Array.from(binary, char => char.charCodeAt(0));
// Decode the UTF-8 bytes back to a Unicode string
const decoder = new TextDecoder();
return decoder.decode(bytes);
}

View File

@ -0,0 +1,206 @@
class ChopChopImporter extends AbstractImporter {
#id = null;
#runinfo = null;
#query = null;
#cut = null;
#vis = null;
constructor(){
super(new ChopChopHelper())
}
render(){
this.getRootDoc().innerHTML = `
<link href="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js rel="stylesheet" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<div class="card">
<div class="card-header">
<h5>Import from <a href="https://chopchop.cbu.uib.no" target="_new">CHOPCHOP</a></h5>
</div>
<div class="card-body">
<label for="basic-url" class="form-label">Enter your CHOPCHOP job URL</label>
<div class="input-group mb-3">
<span class="input-group-text">https://chopchop.cbu.uib.no/results/</span>
<input value="" type="text" class="form-control" id="chopchop_job_id" placeholder="CHOPCHOP job id (e.g.: 1684831154920.0437)" data-bs-toggle="tooltip" title="You can drag-and-drop or copy-and-paste CHOPCHOP address or directly write"/>
<button class="btn btn-outline-primary" type="button" id="chopchop_import_button">Import</button>
</div>
<label for="basic-url" class="form-label">Or import a Chopchop TSV exported file or from a D4Science Workspace file share long URL (e.g. https://data.d4science.net/shub/E_TnRRST...)</label>
<div class="input-group mb-3">
<div><input class="btn btn-outline-primary" type="file" accept=".tsv" id="chopchop_file"><input class="btn btn-outline-primary" type="url" placeholder="https://data.d4science.net/shub/E_TnRRST..." pattern="https://data.d4science.net/shub/.*" size="34" id="chopchop_url"></div>
<select class="form-select" id="chopchop_file_genome">
<option value="">-- Select the Genome --</option>
<option value="danRer10">Danio rerio (danRer10)</option>
<option value="danRer11">Danio rerio (danRer11)</option>
<option value="hg19">Homo sapiens (hg19)</option>
<option value="hg38">Homo sapiens (hg38)</option>
<option value="mm10">Mus musculus (mm10)</option>
<option value="mm39">Mus musculus (mm39)</option>
</select>
<select class="form-select" id="chopchop_file_pam"/>
<option value="">-- Select the PAM --</option>
<option value="NGG">NGG</option>
<option value="NAG">NAG</option>
<option value="NGA">NGA</option>
<option value="NRG">NRG (R = A or G)</option>
</select>
<button class="btn btn-outline-primary" type="button" id="chopchop_file_import_button">Import</button>
</div>
</div>
<div class="card-footer">
<a name="trigger" class="d-none" href="#"><a/>
</div>
</div>
`
this.getRootDoc().querySelector("#chopchop_job_id").addEventListener("paste", ev=> {
const contents = ev.clipboardData.getData('text');
if (contents.indexOf('/results/') > 0) {
ev.preventDefault();
this.getRootDoc().querySelector("#chopchop_job_id").value = contents.substring(contents.indexOf("/results/") + 9, contents.indexOf("/details/") != -1 ? contents.indexOf("/details/") + 1 : contents.length);
}
});
this.getRootDoc().querySelector("#chopchop_job_id").addEventListener("drop", ev=> {
const uri = ev.dataTransfer.getData("text/uri-list");
if (uri.indexOf('/results/') > 0) {
ev.preventDefault();
ev.stopPropagation();
this.getRootDoc().querySelector("#chopchop_job_id").value = uri.substring(uri.indexOf("/results/") + 9, uri.indexOf("/details/") != -1 ? uri.indexOf("/details/") + 1 : uri.length);
}
});
this.getRootDoc().querySelector("#chopchop_import_button").addEventListener("click", ev=> {
this.#id = this.getRootDoc().querySelector("#chopchop_job_id").value
if (this.#id == null || this.#id == '') {
alert("Please, enter the CHOPCHOP job id to import");
return;
}
if (this.#id.endsWith('/')) {
this.#id = this.#id.substring(0, this.#id.length - 1)
}
this.import()
})
this.getRootDoc().querySelector("#chopchop_file_import_button").addEventListener("click", ev=> {
const tsvFile = this.getRootDoc().querySelector("#chopchop_file").files[0];
const tsvURL = this.getRootDoc().querySelector("#chopchop_url").value;
if ((tsvFile == null || tsvFile == '') && (tsvURL == null || tsvURL == '')) {
alert("Please, select/enter the CHOPCHOP's TSV file/URL to import first");
return;
}
const genome = this.getGenomeFromInput()
if (genome == null || genome == '') {
alert("Please, select the Genome first");
return;
}
const pam = this.getPAMFromInput();
if (pam == null || pam == '') {
alert("Please, select the PAM first");
return;
}
if (tsvFile != null && tsvFile != '') {
this.importTSVExport(tsvFile, genome, pam);
} else {
this.importTSVURL(tsvURL, genome, pam);
}
})
this.getRootDoc().querySelector("a[name=trigger]").addEventListener("click", ev=> {
ev.preventDefault()
ev.stopPropagation()
if(this.getDatadisplay()){
this.getDatadisplay().show(this)
}
})
}
getGenomeFromInput() {
return this.getRootDoc().querySelector("#chopchop_file_genome").value;
}
getPAMFromInput() {
return this.getRootDoc().querySelector("#chopchop_file_pam").value;
}
import() {
this.downloadData()
.then(v=>{
const runinfo = v[0].split("\t")
this.#runinfo = {
targets : runinfo[0],
genome : runinfo[1],
mode : runinfo[2],
uniqueMethod_cong : runinfo[3],
quideSize : runinfo[4]
}
this.#query = JSON.parse(v[1])
this.parseTSV(v[2])
this.#cut = JSON.parse(v[3])
this.#vis = JSON.parse(v[4])
this.setIndexer(new iGeneIndexer(this.getTableLines(), this.getGenomeFromRunInfo(), this.getPAMFromRunInfo(), this.getHelper()));
this.enableData();
}).catch(err=>console.error(err))
}
downloadData() {
const base = "http://chopchop.cbu.uib.no/results/" + this.#id + "/";
const urls = [base + "run.info", base + "query.json", base + "results.tsv", base + "cutcoords.json", base + "viscoords.json"];
const fetches = urls.map(u => {
return iGeneIndexer.fetchViaCORSProxy(u).then(r => { if(r.status !== 200) throw "Error fetching CHOPCHOP job via CORS proxy..."; else return r.text() });
})
return Promise.all(fetches);
}
enableData(){
const a = this.getRootDoc().querySelector("a[name=trigger]")
if (this.#query != null) {
a.textContent = `
Show ${this.getTableLines().length} matches on genome ${this.getGenomeFromRunInfo()} ${this.getShortInput()} for ${this.#query.forSelect}
`
} else {
a.textContent = `
Show ${this.getTableLines().length} matches from imported TSV data
`
}
a.classList.remove("d-none")
}
#API
getGenomeFromRunInfo() {
return this.#runinfo.genome;
}
getPAMFromRunInfo() {
let pam = 'NGG' // PAM is initialized with default value of the Cas9 sequence, then is copied from result's '-M' query parameter, if defined
this.#query.opts.forEach((element, index) => {
if (element == '-M') {
pam = this.#query.opts[index +1];
}
});
return pam;
}
getShortInput(){
if (this.#runinfo != null) {
var input = ""
if(this.#query.geneInput === ""){
input = "fasta " + this.#query.fastaInput.length > 10 ? this.#query.fastaInput.substring(0,10) + "..." : this.#query.fastaInput
}else{
input = "gene " + this.#query.geneInput
}
return input
} else {
return "[No info]"
}
}
getHeader(){
if (this.#runinfo != null) {
return `Imported from CHOPCHOP job ${this.#id}: ${this.getGenomeFromRunInfo()} ${this.getShortInput()}`;
} else {
return `Imported from CHOPCHOP TSV export: ${this.getGenomeFromInput()} - ${this.getPAMFromInput()}`;
}
}
}
window.customElements.define('igene-chopchop-importer', ChopChopImporter);

View File

@ -0,0 +1,133 @@
class CrisprscanImporter extends AbstractImporter {
constructor() {
super(new CrisprscanHelper())
}
render(){
this.getRootDoc().innerHTML = `
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<div class="card">
<div class="card-header">
<h5>Import from <a href="https://www.crisprscan.org" target="_new">CRISPRscan</a></h5>
</div>
<div class="card-body">
<label for="basic-url" class="form-label">Import a CRISPRscan TSV exported file or from a D4Science Workspace file share long URL (e.g. https://data.d4science.net/shub/E_TnRRST...)</label>
<div class="input-group mb-3">
<div><input class="btn btn-outline-primary" type="file" accept=".tsv" id="crispscan_file"><input class="btn btn-outline-primary" type="url" placeholder="https://data.d4science.net/shub/E_TnRRST..." pattern="https://data.d4science.net/shub/.*" size="34" id="crisprscan_url"></div>
<select class="form-select w-auto" id="crispscan_file_genome">
<option value="">[Select Genome]</option>
<option value="danRer10">Danio rerio (danRer10)</option>
<option value="danRer11">Danio rerio (danRer11)</option>
<option value="hg19">Homo sapiens (hg19)</option>
<option value="hg38">Homo sapiens (hg38)</option>
<option value="mm10">Mus musculus (mm10)</option>
<option value="mm39">Mus musculus (mm39)</option>
</select>
<select class="form-select w-auto" id="crispscan_file_chromosome" disabled="true">
<select>
<select class="form-select w-auto" id="crispscan_file_pam"/>
<option value="">[Select PAM]</option>
<option value="NGG">NGG</option>
<option value="NAG">NAG</option>
<option value="NGA">NGA</option>
<option value="NRG">NRG (R = A or G)</option>
</select>
<button class="btn btn-outline-primary" type="button" id="crispscan_file_import_button">Import</button>
</div>
</div>
<div class="card-footer">
<a name="trigger" class="d-none" href="#"><a/>
</div>
</div>
`
this.getRootDoc().querySelector("#crispscan_file_genome").addEventListener("change", ev=> {
const genome = ev.target.value;
const chromosomeSelect = this.getRootDoc().querySelector("#crispscan_file_chromosome");
chromosomeSelect.innerHTML = "";
if (genome != '') {
iGeneIndexer.fetchGenomeChromosomesFromUSCS(genome).then(chromosomes => {
chromosomeSelect.innerHTML = '<option value="">[Select Chromosome]</option>';
for (const [name, index] of Object.entries(chromosomes).sort()) {
if (name.length < 6) {
const newOption = document.createElement("option");
newOption.text = name;
newOption.value = name;
chromosomeSelect.add(newOption);
}
}
chromosomeSelect.disabled = false;
}).catch(err => alert(err));
} else {
chromosomeSelect.disabled = true;
}
});
this.getRootDoc().querySelector("#crispscan_file_import_button").addEventListener("click", ev=> {
const tsvFile = this.getRootDoc().querySelector("#crispscan_file").files[0];
const tsvURL = this.getRootDoc().querySelector("#crisprscan_url").value;
if ((tsvFile == null || tsvFile == '') && (tsvURL == null || tsvURL == '')) {
alert("Please, select/enter the CRISPRscan's TSV file/URL to import first");
return;
}
const genome = this.getRootDoc().querySelector("#crispscan_file_genome").value
if (genome == null || genome == '') {
alert("Please, enter the Genome first");
return;
}
const chromosome = this.getRootDoc().querySelector("#crispscan_file_chromosome").value
if (chromosome == null || chromosome == '') {
alert("Please, enter the chromosome first");
return;
}
const pam = this.getRootDoc().querySelector("#crispscan_file_pam").value
if (pam == null || pam == '') {
alert("Please, enter the PAM first");
return;
}
this.getHelper().setDefaultChromosome(chromosome);
if (tsvFile != null && tsvFile != '') {
this.importTSVExport(tsvFile, genome, pam);
} else {
this.importTSVURL(tsvURL, genome, pam);
}
})
this.getRootDoc().querySelector("a[name=trigger]").addEventListener("click", ev=>{
ev.preventDefault()
ev.stopPropagation()
if(this.getDatadisplay()){
this.getDatadisplay().show(this)
}
})
}
enableData(){
const a = this.getRootDoc().querySelector("a[name=trigger]")
a.textContent = `
Show ${this.getTableLines().length} matches from imported TSV data
`
a.classList.remove("d-none")
}
getHeader(){
return `Imported from CRISPRscan TSV file`
}
getRenderer(filter, output){
if (filter.mode !== 're') {
if(output === "html") {
return new CrisprScanHTMLDimerTableRenderer(this, filter);
} else if(output === "tsv2") {
return new CrisprScanTSVDimerTableRenderer(this, filter, "tsv2")
} else {
return super.getRenderer(this, filter)
}
} else {
return super.getRenderer(filter, output)
}
}
}
window.customElements.define('igene-crisprscan-importer', CrisprscanImporter);

View File

@ -0,0 +1,325 @@
class DataDisplay extends HTMLElement{
#rootdoc = null;
#source = null;
#filter = {
mode : "out",
distance: 15,
enzyme: "",
zeromatches : false
}
#timeout = null;
#export = ""
constructor(){
super()
this.#rootdoc = this.attachShadow({ "mode" : "open"})
this.render()
}
show(source){
this.#source = source
this.render()
}
render(){
if(this.#source == null){
this.#rootdoc.innerHTML = `<h5>Nothing to show</h5>`
}else{
const s = this.#source
this.#rootdoc.innerHTML = `
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<style>
#table-container{
max-height: 500px;
overflow-y: auto;
}
table, th, td {
user-select: none;
}
thead > tr {
position: sticky;
top: 0;
}
tr.table-row-nomatch{
opacity: 0.5;
text-decoration: line-through;
}
tr.table-row-match{
cursor: pointer;
}
td.rank-td{
min-width:8rem;
max-width:8rem;
width:8rem;
}
</style>
<div class="d-flex flex-column" style="gap:10px">
<h5>${s.getHeader()}</h5>
<div class="filters d-flex flex-wrap align-items-center justify-content-between">
<div class="d-flex flex-wrap align-items-center" style="gap:5px">
<fieldset>
<legend>Pair of two guides
<span name="help" style="line-height:3px" class="btn border-primary bg-warning text-primary position-relative p-2 m-2 rounded-circle">?</span>
<div style="max-width: 30%;z-index: 100;" class="help-text d-none position-absolute bg-white border rounded border-primary shadow p-2">
<div>
<h4 class="text-decoration-underline">Pair of two guides</h4>
<p class="small">Analysis of the imported data to find a pair of two gRNAs in the maximum desired distance. By clicking on the gRNA, the gRNAs that can be used as a pair are shown. The number of available gRNAs as a pair is shown as a blue square.</p>
</div>
<div class="d-flex flex-column gap-1">
<h5>PAM OUT</h5>
<img src="https://cdn.dev.d4science.org/i-gene/resources/pamout.png" class="border rounded mx-auto" title="PAM OUT" alt="pam out"/>
<p class="small" style="text-align: justify;">The PAM sites of the pair of two gRNAs face outwards. The distance between them is calculated from the 5 end of the one gRNA till the 5 end of the other gRNA.</p>
</div>
<div class="d-flex flex-column gap-1">
<h5>PAM IN</h5>
<img src="https://cdn.dev.d4science.org/i-gene/resources/pamin.png" class="border rounded mx-auto" title="PAM OUT" alt="pam out"/>
<p class="small" style="text-align: justify;">The PAM sites of the pair of two gRNAs face inwards. The distance between them is calculated from the 3 end of the one gRNA till the 3 end of the other gRNA.</p>
</div>
<div>
<h5>Any PAM</h5>
<p class="small" style="text-align: justify;">All the different directions of the two gRNAs and the configurations of the PAM sites are considered, including the configurations PAM OUT and PAM IN.</p>
</div>
</div>
</legend>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="pam" id="pam-out" value="out" ${this.#filter.mode == 'out' ? 'checked' : ''}>
<label class="form-check-label" for="pam-out">PAM out</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="pam" id="pam-in" value="in" ${this.#filter.mode == 'in' ? 'checked' : ''}>
<label class="form-check-label" for="pam-in">PAM in</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="pam" id="pam-any" value="any" ${this.#filter.mode == 'none' ? 'checked' : ''}>
<label class="form-check-label" for="pam-any">Any PAM</label>
</div>
</fieldset>
<fieldset>
<legend>Restriction Enzyme
<span name="help" style="line-height:3px" class="btn border-primary bg-warning text-primary position-relative p-2 m-2 rounded-circle">?</span>
<div style="max-width: 30%;z-index: 100;" class="help-text d-none position-absolute bg-white border rounded border-primary shadow p-2">
<div class="d-flex flex-column gap-1">
<h4 class="text-decoration-underline">Restriction Enzyme</h4>
<img src="https://cdn.dev.d4science.org/i-gene/resources/re.png" class="border rounded mx-auto" title="PAM OUT" alt="pam out"/>
<p class="small" style="text-align:justify;">Analysis of the imported data to find gRNAs in a maximum distance of a selected restriction enzyme. The distance between the gRNA and the restriction enzyme is calculated as the closest distance between the 5 or 3 end of the restriction enzyme and the 5 or 3 end the gRNA. By clicking on the gRNA, the accurate distance between the gRNA and the restriction enzyme is shown in a grey square. The means that the restriction site is located upstream of the gRNA recognition site.</p>
</div>
</div>
</legend>
<div style="display:inline-flex;align-items:center;gap:1rem;">
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="pam" id="re" value="re" ${this.#filter.mode == 're' ? 'checked' : ''}>
</div>
<!--div class="form-group">
<input id="restriction-enzyme" list="restriction-enzyme-datalist" disabled/>
<small id="restriction-enzyme-feedback" class="form-text text-muted"></small>
<datalist id="restriction-enzyme-datalist">
</datalist>
</div-->
<div>
<nw-smart-input id="restriction-enzyme"></nw-smart-input>
</div>
</div>
</fieldset>
<fieldset>
<legend>Maximum distance</legend>
<input class="form-control" style="max-width:6rem" type="number" name="distance" min="1" step="1" id="distance" value="${this.#filter.distance}" placeholder="A number distance">
</fieldset>
</div>
<fieldset>
<legend></legend>
<div class="d-flex flex-wrap align-content-baseline" style="gap:10px">
<div class="col form-check form-switch">
<input class="form-check-input" type="checkbox" value="" id="toggle-zero-matches">
<label class="form-check-label" for="toggle-zero-matches">Show items with 0 matches</label>
</div>
<div class="col">
<button name="tsvexport2" class="btn btn-primary">Download TSV</button>
</div>
<div class="col">
<button name="export2ws" title="Export to D4Science Workspace" class="btn btn-primary">
<svg viewBox="0 96 960 960" style="width: 32px; height: 32px; fill: white;">
<path d="M140 796h680V516H140v280Zm540.118-90Q701 706 715.5 691.382q14.5-14.617 14.5-35.5Q730 635 715.382 620.5q-14.617-14.5-35.5-14.5Q659 606 644.5 620.618q-14.5 14.617-14.5 35.5Q630 677 644.618 691.5q14.617 14.5 35.5 14.5ZM880 456h-85L695 356H265L165 456H80l142-142q8-8 19.278-13 11.278-5 23.722-5h430q12.444 0 23.722 5T738 314l142 142ZM140 856q-24.75 0-42.375-17.625T80 796V456h800v340q0 24.75-17.625 42.375T820 856H140Z"/>
</svg>
WS Export
</button>
</div>
</div>
</fieldset>
</div>
<div id="table-container">
${this.renderTable()}
</div>
</div>
`
const helps = Array.prototype.slice.call(this.#rootdoc.querySelectorAll("span[name='help']"))
helps.forEach(h=>{
h.addEventListener("click", ev=>{
ev.target.parentElement.querySelector(".help-text").classList.toggle("d-none")
})
})
document.addEventListener("keydown", ev=>{
if(ev.key === "Escape"){
const hts = Array.prototype.slice.call(this.#rootdoc.querySelectorAll(".help-text"))
hts.forEach(ht=>{
if(!ht.classList.contains("d-none")) ht.classList.add("d-none");
})
}
})
this.#rootdoc.querySelector("div.filters").addEventListener("change", ev=>{
const tgt = ev.target
if(tgt.id === "pam-any" || tgt.id === "pam-in" || tgt.id === "pam-out" || tgt.id === 're') {
if(tgt.id === "pam-any" || tgt.id === "pam-in" || tgt.id === "pam-out"){
this.#rootdoc.querySelector("#restriction-enzyme").disabled = true;
} else if(tgt.id === "re" ) {
this.#rootdoc.querySelector("#restriction-enzyme").disabled = false;
}
this.#filter.mode = tgt.value
this.#rootdoc.querySelector("#table-container").innerHTML = this.renderTable();
}
})
this.#rootdoc.querySelector("div.filters").addEventListener("input", ev=>{
const tgt = ev.target
if(tgt.id === "distance"){
if(this.#timeout == null){
this.#timeout = window.setTimeout(()=>{
this.#filter.distance = Number(this.#rootdoc.querySelector("#distance").value)
this.#rootdoc.querySelector("#table-container").innerHTML = this.renderTable()
this.#timeout = null;
}, 1000);
}
}
})
this.#rootdoc.querySelector("button[name='tsvexport2']").addEventListener("click", ev=>{
this.exportTable2("\t")
})
this.#rootdoc.querySelector("button[name='export2ws']").addEventListener("click", ev => {
this.export2Workspace("\t");
})
this.#rootdoc.querySelector("#table-container").addEventListener("click", ev=>{
var root = ev.target.parentElement
while(root != null && !root.getAttribute("data-rank")){
root = root.parentElement
}
if(root == null) return;
const rank = root.getAttribute("data-rank")
if(rank){
Array.prototype.slice.call(this.#rootdoc.querySelectorAll(`#table-container tr[data-rel-rank='${rank}']`)).forEach(tr=>tr.classList.toggle("d-none"))
}
})
this.#rootdoc.querySelector("#toggle-zero-matches").addEventListener("change", ev=>{
const tgt = ev.target
this.#filter.zeromatches = tgt.checked
this.toggleVisibility()
})
const reSelect = this.#rootdoc.querySelector("#restriction-enzyme");
reSelect.data = iGeneIndexer.restrictionEnzymes.filter(re=>!re.sequence.includes('?') && !re.sequence.includes('('))
/*const reSelect = this.#rootdoc.querySelector("#restriction-enzyme");
const reSelectFeedback = this.#rootdoc.querySelector("#restriction-enzyme-feedback");
const reSelectDatalist = this.#rootdoc.querySelector("#restriction-enzyme-datalist");
reSelectDatalist.innerHTML = iGeneIndexer.restrictionEnzymes.reduce((acc, e)=>{
return acc + `
<option ${e.sequence.includes('?') || e.sequence.includes('(') ? 'disabled' : ''} value="${e.sequence}">${e.name} (${e.sequence})</option>
`
}, '<option vallue=""></option>')*/
/*iGeneIndexer.restrictionEnzymes.forEach(enzyme => {
const newOption = document.createElement("option");
newOption.text = enzyme.name + ' (' + enzyme.sequence + ')';
newOption.value = enzyme.sequence;
if (enzyme.sequence.includes('?') || enzyme.sequence.includes('(')) {
newOption.disabled = true;
}
reSelectDatalist.add(newOption);
});*/
// reSelect.addEventListener("change", ev => {
// this.#filter.enzyme = reSelect.value;
// const o = reSelectDatalist.querySelector("option[value='" + reSelect.value + "']")
// reSelectFeedback.textContent = o ? o.textContent : reSelect.value
// this.#rootdoc.querySelector("#table-container").innerHTML = this.renderTable();
// })
}
}
toggleVisibility(){
const trs = Array.prototype.slice.call(this.#rootdoc.querySelectorAll("tr.table-row-nomatch"))
trs.forEach(tr=>tr.classList.toggle('d-none'))
}
updateEnzyme(enzyme){
this.#filter.enzyme = enzyme
setTimeout(() => {
this.#rootdoc.querySelector("#table-container").innerHTML = this.renderTable();
}, 100);
}
renderTable() {
const renderer = this.#source.getRenderer(this.#filter, "html")
return renderer.renderTable()
}
constructFilename() {
const indexer = this.#source.getIndexer();
const filter = this.#filter;
let filename = 'export_';
filename += 'genome=' + indexer.getGenome() + '_chr=' + indexer.getChromosome() + '_pam=' + indexer.getPAM();
filename += '_dist=' + filter.distance + '_mode=pam-' + filter.mode + (filter.mode === 're' ? '_(enz=' + filter.enzyme + ')' : '');
return filename;
}
exportTable(sep){
const renderer = this.#source.getRenderer(this.#filter, "tsv")
if(renderer != null){
this.#export = renderer.renderTable()
var element = document.createElement('a');
element.setAttribute('href', 'data:text/tab-separated-values;charset=utf-8,' + encodeURIComponent(this.#export));
element.setAttribute('download', this.constructFilename() + '.tsv');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
exportTable2(sep){
const renderer = this.#source.getRenderer(this.#filter, "tsv2")
if(renderer != null){
this.#export = renderer.renderTable()
var element = document.createElement('a');
element.setAttribute('href', 'data:text/tab-separated-values;charset=utf-8,' + encodeURIComponent(this.#export));
element.setAttribute('download', this.constructFilename() + '.tsv');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
async export2Workspace(sep) {
const renderer = this.#source.getRenderer(this.#filter, "tsv2")
if(renderer != null){
const folderName = "i-Gene-Matcher";
const date = new Date();
const fileName = this.constructFilename() + '_' + date.toISOString().substring(0, 11) + date.toLocaleTimeString().replaceAll(":", "-") + ".tsv";
const file2export = renderer.renderTable();
const workspace = new D4SWorkspace("https://api.d4science.org/workspace");
const wsId = await workspace.getWorkspace(null);
const iGeneFolder = await workspace.checkOrCreateFolder(wsId, folderName, "I-Gene tool export folder", null);
if(await workspace.uploadFile(iGeneFolder, fileName, "I-Gene Matcher Export work of " + date, file2export, "text/tab-separated-values", null)) {
alert("File successfully saved on D4Science Workspace as:\n\n/" + folderName + "/" + fileName);
} else {
alert("An error occurred during the file upload to workspace, check browser console for more details");
}
}
}
}
window.customElements.define('igene-data-display', DataDisplay);

View File

@ -0,0 +1,83 @@
class SmartInput extends HTMLElement {
#data = null;
#rootdoc = null;
#delay = 300;
#value = null;
#timeoutid = null;
#input = null;
#list = null;
#label = null;
constructor(){
super()
this.#rootdoc = this.attachShadow({ "mode" : "open"})
this.render()
}
connectedCallback(){}
set data(data){ this.#data = data; this.render() }
render(){
this.#rootdoc.innerHTML = `
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<div>
<div class="d-flex align-items-center" style="gap:5px">
<input class="form-control"/>
<small id="restriction-enzyme-feedback" class="form-text text-muted"></small>
</div>
<div>
<ul class="d-none list-group" style="position:absolute;z-index:1000"></ul>
</div>
<div>
`
this.#list = this.#rootdoc.querySelector("ul")
this.#input = this.#rootdoc.querySelector("input")
this.#label = this.#rootdoc.querySelector("small#restriction-enzyme-feedback")
this.#rootdoc.addEventListener("keydown", ev=>{
if(ev.code === "Escape"){
this.#list.classList.add("d-none")
}
})
this.#input.addEventListener("input", ev=>{
this.#value = ev.target.value
this.#label.textContent = ""
if(this.#timeoutid != null) window.clearTimeout(this.#timeoutid)
this.#timeoutid = window.setTimeout(()=>{
this.renderOptions()
}, this.#delay)
})
this.#input.addEventListener("keypress", ev=>{
if(ev.code === "Enter"){
this.#list.classList.add("d-none")
document.querySelector("igene-data-display").updateEnzyme(this.#input.value)
}
})
this.#list.addEventListener("click", ev=>{
if(ev.target.classList.contains("list-group-item")){
this.#input.value = ev.target.getAttribute("data-sequence")
this.#label.textContent = ev.target.getAttribute("data-enzyme")
this.#list.classList.add("d-none")
document.querySelector("igene-data-display").updateEnzyme(this.#input.value)
}
})
}
renderOptions(){
if(this.#value == null || this.#value === ""){
this.#list.classList.add("d-none")
return
}
const options = this.#data.filter(d=>{
return d.name.startsWith(this.#value) || d.sequence.startsWith(this.#value)
}).map(o=>{
return `
<li class="list-group-item d-flex" style="gap:5px; cursor: pointer;" data-sequence="${o.sequence}" data-enzyme="${o.name}"><span style="pointer-events: none;">${o.name}</span><span style="pointer-events: none;" class="badge bg-primary">${o.sequence}</span></li>
`
}).join("")
this.#list.innerHTML = options
this.#list.classList.remove("d-none")
}
}
window.customElements.define('nw-smart-input', SmartInput);

117
i-gene/d4s-workspace.js Normal file
View File

@ -0,0 +1,117 @@
class D4SWorkspace {
#d4sboot = null;
#workspaceURL = null;
constructor(workspaceURL, d4sboot) {
this.#workspaceURL = workspaceURL;
if (d4sboot) {
this.#d4sboot = d4sboot;
} else {
this.#d4sboot = document.querySelector("d4s-boot-2");
}
}
/**
* Calls with a secure fetch the workspace with provided data.
* To upload data by using a <code>FormData</code> object you have to leave the <code>mime</code> attribute as <code>null</code>.
* @param {*} method the method to use for the call, default to <code>GET</code>
* @param {*} uri the uri to invoke, relative to <code>workspaceURL</code> 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;
});
};
}

1135
i-gene/i-gene.js Normal file

File diff suppressed because it is too large Load Diff

41
i-gene/index.html Normal file
View File

@ -0,0 +1,41 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.dev.d4science.org/common/js/keycloak.js" type="text/javascript"></script>
<script src="https://cdn.dev.d4science.org/boot/d4s-boot.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="i-gene.js"></script>
<script src="components/chopchop_importer.js"></script>
<script src="components/crisprscan_importer.js"></script>
<script src="components/datadisplay.js"></script>
<script src="components/smartinput.js"></script>
<script src="d4s-workspace.js"></script>
</head>
<body class="m-4">
<div>
<d4s-boot-2 clientid="https://i-gene.d4science.org"
context="%2Fd4science.research-infrastructures.eu%2FD4OS%2FI-GenePublic"
gateway="i-gene.d4science.org"
redirect-url="http://localhost:8080/i-gene-tool"
url="https://accounts.d4science.org/auth">
</div><!-- redirect-url="https://i-gene.d4science.org/group/i-genepublic/i-gene-tool" -->
<header class="card-header mb-2 p-2">
<h3>Welcome to the I-Gene Crispr tool</h3>
<p>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.</p>
<p>The input information comes from the web tool <a href="https://chopchop.cbu.uib.no/">CHOPCHOP</a> either as a link or as an imported TSV file and from <a href="https://www.crisprscan.org/">CrisprScan</a> as imported TSV.</p>
</header>
<section class="d-flex flex-column" style="gap:1rem">
<div class="d-flex flex-wrap" style="gap:1rem;">
<igene-chopchop-importer></igene-chopchop-importer>
<igene-crisprscan-importer></igene-crisprscan-importer>
</div>
<div class="border"></div>
<div>
<igene-data-display></igene-data-display>
</div>
</section>
</body>
</html>

324
i-gene/readme.html Normal file
View File

@ -0,0 +1,324 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<style>
table.table {
margin-bottom: 4rem;
}
video {
margin: 0 auto 4rem auto;
max-width: 800px;
}
</style>
</head>
<body>
<section class="d-flex flex-column gap-3" style="min-width:800px;max-width:60%; margin:auto">
<h1>I-GeneMatcher Readme</h1>
<p>Please follow the tutorial on how to use the I-GeneMatcher. If your video does not play you can download the video from <a href="https://data.d4science.net/vFbH">here</a>.</p>
<video controls>
<source type="video/mp4" src="https://cdn.cloud-dev.d4science.org/i-gene/resources/tutorial.mp4"/>
</video>
<div>
<h3>Table description</h3>
<p>The following tables describe the meaning of the columns in the various data tables produced as output by the I-GeneMatcher.</p>
<h5>Pair of two guides importing file from CHOPCHOP</h5>
<table class="table table-striped">
<thead>
<tr>
<td>Column</td>
<td>Description</td>
</tr>
</thead>
<tbody class="text-primary">
<tr>
<td>Rank</td>
<td>Rank of guide 1 as reported in CHOPCHOP</td>
</tr>
<tr>
<td>Match rank</td>
<td>Rank of guide 2 as reported in CHOPCHOP</td>
</tr>
<tr>
<td>Distance</td>
<td>Precise distance between the guides with the selected configuration</td>
</tr>
<tr>
<td>Target sequence</td>
<td>Sequence of guide 1</td>
</tr>
<tr>
<td>Genomic location</td>
<td>Position of guide 1</td>
</tr>
<tr>
<td>Match Genomic location</td>
<td>Position of guide 2</td>
</tr>
<tr>
<td>Strand</td>
<td>DNA strand that the guide 1 targets</td>
</tr>
<tr>
<td>Match Strand</td>
<td>DNA strand that the guide 2 targets</td>
</tr>
<tr>
<td>GC content (%)</td>
<td>GC content percentage of guide 1</td>
</tr>
<tr>
<td>Match GC content (%)</td>
<td>GC content percentage of guide 2</td>
</tr>
<tr>
<td>Self-complementarity</td>
<td>Self-complementarity value of guide 1 as reported in CHOPCHOP</td>
</tr>
<tr>
<td>Match Self-complementarity</td>
<td>Self-complementarity value of guide 2 as reported in CHOPCHOP</td>
</tr>
<tr>
<td>MM0-3</td>
<td>Targets containing 0 to 3 mismatches for guide 1</td>
</tr>
<tr>
<td>Match MM0-3</td>
<td>Targets containing 0 to 3 mismatches for guide 2</td>
</tr>
<tr>
<td>Efficiency</td>
<td>Efficiency value of guide 1 as reported in CHOPCHOP</td>
</tr>
<tr>
<td>Match Efficiency</td>
<td>Efficiency value of guide 2 as reported in CHOPCHOP</td>
</tr>
</tbody>
</table>
<h5>Pair of two guides importing file from Crisprscan</h5>
<table class="table table-striped">
<thead>
<tr>
<td>Column</td>
<td>Description</td>
</tr>
</thead>
<tbody class="text-primary">
<tr>
<td>Rank</td>
<td>Rank of guide 1 as reported in crisprscan</td>
</tr>
<tr>
<td>Match rank</td>
<td>Rank of guide 2 as reported in crisprscan</td>
</tr>
<tr>
<td>Distance</td>
<td>Precise distance between the guides with the selected configuration</td>
</tr>
<tr>
<td>Name</td>
<td>Targeted chromosome of guide 1</td>
</tr>
<tr>
<td>Match Name</td>
<td>Targeted chromosome of guide 2</td>
</tr>
<tr>
<td>Start</td>
<td>Starting genomic position of guide 1</td>
</tr>
<tr>
<td>Match Start</td>
<td>Starting genomic position of guide 2</td>
</tr>
<tr>
<td>End</td>
<td>Ending genomic position of guide 1</td>
</tr>
<tr>
<td>Match End</td>
<td>Ending genomic position of guide 2</td>
</tr>
<tr>
<td>Strand</td>
<td>DNA strand that the guide 1 targets</td>
</tr>
<tr>
<td>Match Strand</td>
<td>DNA strand that the guide 2 targets</td>
</tr>
<tr>
<td>Type</td>
<td>20 nucleotides following the type of PAM for guide 1</td>
</tr>
<tr>
<td>Match Type</td>
<td>20 nucleotides following the type of PAM for guide 2</td>
</tr>
<tr>
<td>Seq</td>
<td>Targeting sequence containing the PAM sequence of guide 1</td>
</tr>
<tr>
<td>Match Seq</td>
<td>Targeting sequence containing the PAM sequence of guide 2</td>
</tr>
<tr>
<td>Sgrna_seq</td>
<td>Sequence of guide 1</td>
</tr>
<tr>
<td>Match Sgrna_seq</td>
<td>Sequence of guide 2</td>
</tr>
<tr>
<td>Promoter</td>
<td>If selected during the crisprscan research</td>
</tr>
<tr>
<td>Match Promoter</td>
<td>If selected during the crisprscan research</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="10">Remaining values as reported by crisprscan. Prefix "Match" always indicates the corresponding values of guide 2.</td>
</tr>
</tfoot>
</table>
<h5>Restriction enzyme importing file from CHOPCHOP</h5>
<table class="table table-striped">
<thead>
<tr>
<td>Column</td>
<td>Description</td>
</tr>
</thead>
<tbody class="text-primary">
<tr>
<td>RE sequence</td>
<td>Restriction site of the selected restriction enzyme</td>
</tr>
<tr>
<td>RE index</td>
<td>Position of the restriction site</td>
</tr>
<tr>
<td>RE strand</td>
<td>5'- 3'</td>
</tr>
<tr>
<td>Distance</td>
<td>Precise distance from the guide</td>
</tr>
<tr>
<td>Rank</td>
<td>Rank of the guide as reported in CHOPCHOP</td>
</tr>
<tr>
<td>Target sequence</td>
<td>Sequence of the guide</td>
</tr>
<tr>
<td>Genomic location</td>
<td>Position of the guide</td>
</tr>
<tr>
<td>Strand</td>
<td>DNA strand that the guide targets</td>
</tr>
<tr>
<td>GC content (%)</td>
<td>GC content percentage of the guide</td>
</tr>
<tr>
<td>Self-complementarity</td>
<td>Self-complementarity value of the guide as reported in CHOPCHOP</td>
</tr>
<tr>
<td>MM0-3</td>
<td>Targets containing 0 to 3 mismatches</td>
</tr>
<tr>
<td>Efficiency</td>
<td>Efficiency value as reported in CHOPCHOP</td>
</tr>
</tbody>
</table>
<h5>Restriction enzyme importing file from Crisprscan</h5>
<table class="table table-striped">
<thead>
<tr>
<td>Column</td>
<td>Description</td>
</tr>
</thead>
<tbody class="text-primary">
<tr>
<td>RE sequence</td>
<td>Restriction site of the selected restriction enzyme</td>
</tr>
<tr>
<td>RE index</td>
<td>Position of the restriction site</td>
</tr>
<tr>
<td>RE strand</td>
<td>5' - 3'</td>
</tr>
<tr>
<td>Distance</td>
<td>Precise distance from the guide</td>
</tr>
<tr>
<td>Rank</td>
<td>Rank of the guide as reported in crisprscan</td>
</tr>
<tr>
<td>Name</td>
<td>Targeted chromosome</td>
</tr>
<tr>
<td>Start</td>
<td>Starting genomic position of the guide</td>
</tr>
<tr>
<td>End</td>
<td>Ending genomic position of the guide</td>
</tr>
<tr>
<td>Strand</td>
<td>DNA strand that the guide targets</td>
</tr>
<tr>
<td>Type</td>
<td>20 nucleotides following the type of PAM</td>
</tr>
<tr>
<td>Seq</td>
<td>Targeting sequence containing the PAM sequence</td>
</tr>
<tr>
<td>Sgrna_seq</td>
<td>Sequence of the guide</td>
</tr>
<tr>
<td>Promoter</td>
<td>If selected during the crisprscan research</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="10">Remaining values as reported by crisprscan.</td>
</tr>
</tfoot>
</table>
</div>
</section>
</body>
</html>

BIN
i-gene/resources/pamin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
i-gene/resources/pamout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
i-gene/resources/re.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB