Merge pull request 'master' (#25) from master into prod
Reviewed-on: #25
This commit is contained in:
commit
1a65fc022e
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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,38 +701,42 @@ 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()
|
||||||
const req = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/request.json").async("string"))))
|
try{
|
||||||
const status = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/jobStatus.json").async("string"))))
|
const req = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/request.json").async("string"))))
|
||||||
const meth = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/method.json").async("string"))))
|
const status = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/jobStatus.json").async("string"))))
|
||||||
const infra = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/infrastructure.json").async("string"))))
|
const meth = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/method.json").async("string"))))
|
||||||
const parser = new DOMParser()
|
const infra = JSON.parse(await (zip.loadAsync(metadata).then(z=>z.file("metadata/infrastructure.json").async("string"))))
|
||||||
// Fasted way to get context is from prov-o. If it doesn't match the context in boot skip this execution
|
const parser = new DOMParser()
|
||||||
const provo = parser.parseFromString(await zip.loadAsync(metadata).then(z=>z.file("metadata/prov-o.xml").async("string")), "text/xml")
|
// Fasted way to get context is from prov-o. If it doesn't match the context in boot skip this execution
|
||||||
const context = provo.querySelector("entity[*|id='d4s:VRE']>value").textContent
|
const provo = parser.parseFromString(await zip.loadAsync(metadata).then(z=>z.file("metadata/prov-o.xml").async("string")), "text/xml")
|
||||||
if(context !== this.#boot.context && context.replaceAll("/", "%2F") !== this.#boot.context) continue;
|
const context = provo.querySelector("entity[*|id='d4s:VRE']>value").textContent
|
||||||
//build entry from downloaded data
|
if(context !== this.#boot.context && context.replaceAll("/", "%2F") !== this.#boot.context) continue;
|
||||||
const entry = {
|
//build entry from downloaded data
|
||||||
id : req.id,
|
const entry = {
|
||||||
status : status.status,
|
id : req.id,
|
||||||
created : status.created,
|
status : status.status,
|
||||||
started : status.started,
|
created : status.created,
|
||||||
updated : status.updated,
|
started : status.started,
|
||||||
message : status.message,
|
updated : status.updated,
|
||||||
method : meth.title,
|
message : status.message,
|
||||||
methodversion : meth.version,
|
method : meth.title,
|
||||||
infrastructure: infra.name,
|
methodversion : meth.version,
|
||||||
infrastructuretype : infra.type,
|
infrastructure: infra.name,
|
||||||
ccpnote : req.inputs.ccpnote ? req.inputs.ccpnote : "",
|
infrastructuretype : infra.type,
|
||||||
runtime : req.inputs.ccpimage,
|
ccpnote : req.inputs.ccpnote ? req.inputs.ccpnote : "",
|
||||||
replicas : req.inputs.ccpreplicas ? req.inputs.ccpreplicas : 1,
|
runtime : req.inputs.ccpimage,
|
||||||
href : `items/${executionfolders[j].id}/download`,
|
replicas : req.inputs.ccpreplicas ? req.inputs.ccpreplicas : 1,
|
||||||
wsid : executionfolders[j].id,
|
href : `items/${executionfolders[j].id}/download`,
|
||||||
fullrequest : req,
|
wsid : executionfolders[j].id,
|
||||||
fullmethod : meth,
|
fullrequest : req,
|
||||||
fullinfrastructure : infra
|
fullmethod : meth,
|
||||||
|
fullinfrastructure : infra
|
||||||
|
}
|
||||||
|
if(this.#archived[entry.method]) this.#archived[entry.method].push(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])
|
||||||
}
|
}
|
||||||
if(this.#archived[entry.method]) this.#archived[entry.method].push(entry);
|
|
||||||
else this.#archived[entry.method] = [entry]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -765,9 +775,15 @@ class CCPExecutionHistory extends HTMLElement {
|
||||||
{
|
{
|
||||||
target : "details[name=level1]",
|
target : "details[name=level1]",
|
||||||
apply : (e,d)=>{
|
apply : (e,d)=>{
|
||||||
e.alt = e.title = d
|
if(!!d && d !== "undefined"){
|
||||||
if(sessionStorage.getItem(d) === "open") e.open = "open";
|
e.alt = e.title = d
|
||||||
else e.removeAttribute("open");
|
if(sessionStorage.getItem(d) === "open") e.open = "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")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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(){
|
||||||
|
|
|
@ -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,16 +442,14 @@
|
||||||
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;
|
scope = "openid";
|
||||||
} else {
|
} else if (scope.indexOf("openid") === -1) {
|
||||||
scope = "openid " + options.scope;
|
// if openid scope is missing, prefix the given scopes with it
|
||||||
}
|
scope = "openid " + scope;
|
||||||
} else {
|
}
|
||||||
scope = "openid";
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = baseUrl
|
var url = baseUrl
|
||||||
+ '?client_id=' + encodeURIComponent(kc.clientId)
|
+ '?client_id=' + encodeURIComponent(kc.clientId)
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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>
|
|
@ -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>
|
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
Loading…
Reference in New Issue