forked from enviPath/enviPy
Compare commits
12 Commits
ce1d709715
...
develop-ba
| Author | SHA1 | Date | |
|---|---|---|---|
| 57f5ce089c | |||
| 28e5dbbcf5 | |||
| 8620f98082 | |||
| 4ee37b5c71 | |||
| c65be30b6b | |||
| b5e3c1957b | |||
| 3bb9220b22 | |||
| d06ed33196 | |||
| ea672ec91a | |||
| dfc1309fd3 | |||
| 6e2dbccd6c | |||
| b77fbc9b90 |
@ -4,7 +4,9 @@
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Link to PES</div>
|
||||
<div class="collapse-content">
|
||||
<a href="{{ compound_structure.pes_link }}" class="hover:bg-base-200">{{ compound_structure.pes_link }}</a>
|
||||
<p>
|
||||
<a href="{{ compound_structure.pes_link }}" class="hover:bg-base-200">{{ compound_structure.pes_link }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Link to PES</div>
|
||||
<div class="collapse-content">
|
||||
<a href="{{ compound.default_structure.pes_link }}" class="hover:bg-base-200">{{ compound.default_structure.pes_link }}</a>
|
||||
<p>
|
||||
<a href="{{ compound.default_structure.pes_link }}" class="hover:bg-base-200">{{ compound.default_structure.pes_link }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Link to PES</div>
|
||||
<div class="collapse-content">
|
||||
<a href="{{ node.default_node_label.pes_link }}" class="hover:bg-base-200">{{ node.default_node_label.pes_link }}</a>
|
||||
<p>
|
||||
<a href="{{ node.default_node_label.pes_link }}" class="hover:bg-base-200">{{ node.default_node_label.pes_link }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -633,9 +633,14 @@ class CompoundSchema(Schema):
|
||||
reviewStatus: str = Field(False, alias="review_status")
|
||||
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
|
||||
structures: List["CompoundStructureSchema"] = []
|
||||
pesLink: str | None = Field(None, alias="pes_link")
|
||||
|
||||
@staticmethod
|
||||
def resolve_review_status(obj: CompoundStructure):
|
||||
def resolve_pes_link(obj: Compound):
|
||||
return getattr(obj.default_structure, "pes_link", None)
|
||||
|
||||
@staticmethod
|
||||
def resolve_review_status(obj: Compound):
|
||||
return "reviewed" if obj.package.reviewed else "unreviewed"
|
||||
|
||||
@staticmethod
|
||||
@ -709,6 +714,7 @@ class CompoundStructureSchema(Schema):
|
||||
reviewStatus: str = Field(None, alias="review_status")
|
||||
scenarios: List["SimpleScenario"] = Field([], alias="scenarios")
|
||||
smiles: str = Field(None, alias="smiles")
|
||||
pesLink: str | None = Field(None, alias="pes_link")
|
||||
|
||||
@staticmethod
|
||||
def resolve_review_status(obj: CompoundStructure):
|
||||
@ -1695,6 +1701,7 @@ class PathwayNode(Schema):
|
||||
proposed: List[Dict[str, str]] = Field([], alias="proposed_intermediate")
|
||||
smiles: str = Field(None, alias="smiles")
|
||||
pseudo: bool = Field(False, alias="pseudo")
|
||||
pesLink: str | None = Field(None, alias="pes_link")
|
||||
|
||||
@staticmethod
|
||||
def resolve_atom_count(obj: Node):
|
||||
@ -1707,24 +1714,10 @@ class PathwayNode(Schema):
|
||||
# TODO
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def resolve_engineered_intermediate(obj: Node):
|
||||
# TODO
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def resolve_image(obj: Node):
|
||||
return f"{obj.default_node_label.url}?image=svg"
|
||||
|
||||
@staticmethod
|
||||
def resolve_image_size(obj: Node):
|
||||
return 400
|
||||
|
||||
@staticmethod
|
||||
def resolve_proposed_intermediate(obj: Node):
|
||||
# TODO
|
||||
return []
|
||||
|
||||
|
||||
class PathwaySchema(Schema):
|
||||
aliases: List[str] = Field([], alias="aliases")
|
||||
@ -2392,3 +2385,31 @@ def get_setting(request, setting_uuid):
|
||||
return 403, {
|
||||
"message": f"Getting Setting with id {setting_uuid} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
|
||||
########
|
||||
# Util #
|
||||
########
|
||||
class NonPersistent(Schema):
|
||||
smiles: str
|
||||
setting_url: str = Field(..., alias="settingUri")
|
||||
|
||||
|
||||
@router.post("/util", response={200: Any, 403: Error})
|
||||
def predict(request, np: Form[NonPersistent]):
|
||||
try:
|
||||
print(request.user)
|
||||
print(np.setting_url)
|
||||
setting = SettingManager.get_setting_by_url(request.user, np.setting_url)
|
||||
|
||||
from epdb.logic import SPathway
|
||||
|
||||
spw = SPathway(prediction_setting=setting, root_nodes=[np.smiles])
|
||||
spw.predict()
|
||||
|
||||
return spw.to_json()
|
||||
|
||||
except ValueError:
|
||||
return 403, {
|
||||
"message": f"Getting Setting with id {np.setting_url} failed due to insufficient rights!"
|
||||
}
|
||||
|
||||
@ -1896,6 +1896,12 @@ class SPathway(object):
|
||||
"to": to_indices,
|
||||
}
|
||||
|
||||
if edge.rule:
|
||||
e["rule"] = edge.rule.simple_json()
|
||||
|
||||
if edge.probability:
|
||||
e["probability"] = edge.probability
|
||||
|
||||
edges.append(e)
|
||||
|
||||
return {
|
||||
|
||||
@ -2379,6 +2379,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
||||
},
|
||||
"predicted_properties": predicted_properties,
|
||||
"is_engineered_intermediate": self.kv.get("is_engineered_intermediate", False),
|
||||
"proposed": self.is_proposed_intermediate(),
|
||||
"timeseries": self.get_timeseries_data(),
|
||||
**structure_data,
|
||||
}
|
||||
@ -2452,6 +2453,26 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
||||
|
||||
return data
|
||||
|
||||
def is_proposed_intermediate(self):
|
||||
collected = defaultdict(dict)
|
||||
for ai in self.additional_information.filter(
|
||||
type__in=["ProposedIntermediate", "TransformationProductImportance", "Confidence"]
|
||||
, scenario__isnull=False
|
||||
):
|
||||
collected[str(ai.scenario.uuid)]["scenarioId"] = ai.scenario.url
|
||||
collected[str(ai.scenario.uuid)]["scenarioName"] = ai.scenario.name
|
||||
|
||||
if ai.type == "ProposedIntermediate":
|
||||
collected[str(ai.scenario.uuid)]["proposed"] = True
|
||||
|
||||
if ai.type == "Confidence":
|
||||
collected[str(ai.scenario.uuid)]["Confidence"] = ai.get().level
|
||||
|
||||
if ai.type == "TransformationProductImportance":
|
||||
collected[str(ai.scenario.uuid)]["Transformation product importance"] = ai.get().importance.value
|
||||
|
||||
return list(collected.values())
|
||||
|
||||
def simple_json(self, include_description=False):
|
||||
res = super().simple_json()
|
||||
name = res.get("name", None)
|
||||
|
||||
@ -2482,7 +2482,22 @@ def package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
|
||||
|
||||
return JsonResponse({"success": current_node.url})
|
||||
|
||||
return HttpResponseBadRequest()
|
||||
new_node_name = request.POST.get("node-name")
|
||||
new_node_description = request.POST.get("node-description")
|
||||
|
||||
if any([new_node_name, new_node_description]):
|
||||
if new_node_name is not None and new_node_name.strip() != "":
|
||||
new_node_name = nh3.clean(new_node_name.strip(), tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
current_node.name = new_node_name
|
||||
|
||||
if new_node_description is not None and new_node_description.strip() != "":
|
||||
new_node_description = nh3.clean(new_node_description.strip(), tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
current_node.description = new_node_description
|
||||
|
||||
current_node.save()
|
||||
return redirect(current_node.url)
|
||||
|
||||
return error(request, "Node update failed!", "No changes were made to the node")
|
||||
else:
|
||||
return HttpResponseNotAllowed(["GET", "POST"])
|
||||
|
||||
|
||||
@ -5,6 +5,125 @@
|
||||
*/
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
|
||||
const basePagination = (
|
||||
items,
|
||||
currentPage,
|
||||
totalPages,
|
||||
totalItems,
|
||||
perPage,
|
||||
isReviewed,
|
||||
instanceId
|
||||
) => ({
|
||||
items,
|
||||
currentPage,
|
||||
totalPages,
|
||||
totalItems,
|
||||
perPage,
|
||||
isReviewed,
|
||||
instanceId,
|
||||
|
||||
get paginatedItems() {
|
||||
return this.items;
|
||||
},
|
||||
|
||||
get showingStart() {
|
||||
if (this.totalItems === 0) return 0;
|
||||
return (this.currentPage - 1) * this.perPage + 1;
|
||||
},
|
||||
|
||||
get showingEnd() {
|
||||
if (this.totalItems === 0) return 0;
|
||||
return Math.min((this.currentPage - 1) * this.perPage + this.items.length, this.totalItems);
|
||||
},
|
||||
|
||||
nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
this.fetchPage(this.currentPage + 1);
|
||||
}
|
||||
},
|
||||
|
||||
prevPage() {
|
||||
if (this.currentPage > 1) {
|
||||
this.fetchPage(this.currentPage - 1);
|
||||
}
|
||||
},
|
||||
|
||||
goToPage(page) {
|
||||
if (page >= 1 && page <= this.totalPages) {
|
||||
this.fetchPage(page);
|
||||
}
|
||||
},
|
||||
|
||||
get pageNumbers() {
|
||||
const pages = [];
|
||||
const total = this.totalPages;
|
||||
const current = this.currentPage;
|
||||
|
||||
if (total === 0) {
|
||||
return pages;
|
||||
}
|
||||
|
||||
if (total <= 7) {
|
||||
for (let i = 1; i <= total; i++) {
|
||||
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
||||
}
|
||||
} else {
|
||||
pages.push({ page: 1, isEllipsis: false, key: `${this.instanceId}-page-1` });
|
||||
|
||||
let rangeStart;
|
||||
let rangeEnd;
|
||||
|
||||
if (current <= 4) {
|
||||
rangeStart = 2;
|
||||
rangeEnd = 5;
|
||||
} else if (current >= total - 3) {
|
||||
rangeStart = total - 4;
|
||||
rangeEnd = total - 1;
|
||||
} else {
|
||||
rangeStart = current - 1;
|
||||
rangeEnd = current + 1;
|
||||
}
|
||||
|
||||
if (rangeStart > 2) {
|
||||
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-start` });
|
||||
}
|
||||
|
||||
for (let i = rangeStart; i <= rangeEnd; i++) {
|
||||
pages.push({ page: i, isEllipsis: false, key: `${this.instanceId}-page-${i}` });
|
||||
}
|
||||
|
||||
if (rangeEnd < total - 1) {
|
||||
pages.push({ page: '...', isEllipsis: true, key: `${this.instanceId}-ellipsis-end` });
|
||||
}
|
||||
|
||||
pages.push({ page: total, isEllipsis: false, key: `${this.instanceId}-page-${total}` });
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
});
|
||||
|
||||
Alpine.data('paginatedList',
|
||||
(items, options = {}) => ({
|
||||
...basePagination(items,1, 0, 0, options.perPage || 50, options.isReviewed || false, options.instanceId || Math.random().toString(36).substring(2, 9),),
|
||||
|
||||
init() {
|
||||
this.fetchPage(1);
|
||||
},
|
||||
|
||||
async fetchPage(page) {
|
||||
this.totalItems = this.items.length;
|
||||
this.totalPages = Math.ceil(this.items.length / this.perPage);
|
||||
this.currentPage = page
|
||||
|
||||
const start = page * this.perPage - this.perPage;
|
||||
const end = page * this.perPage;
|
||||
return this.items.slice(start, end);
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
Alpine.data('remotePaginatedList', (options = {}) => ({
|
||||
items: [],
|
||||
currentPage: 1,
|
||||
|
||||
@ -67,6 +67,9 @@ function draw(pathway, elem) {
|
||||
const horizontalSpacing = 75; // horizontal space between nodes
|
||||
const depthMap = new Map();
|
||||
|
||||
// Avoid leaving unconnected Nodes leaving the viewport
|
||||
nodes.forEach(node => {if (node.depth < 0) node.depth = 0;});
|
||||
|
||||
// Sort nodes by depth first to minimize crossings
|
||||
const sortedNodes = [...nodes].sort((a, b) => a.depth - b.depth);
|
||||
|
||||
@ -154,6 +157,10 @@ function draw(pathway, elem) {
|
||||
});
|
||||
|
||||
node.attr("transform", d => `translate(${d.x},${d.y})`);
|
||||
|
||||
linkText
|
||||
.attr("x", d => (d.source.x + d.target.x) / 2)
|
||||
.attr("y", d => (d.source.y + d.target.y) / 2);
|
||||
}
|
||||
|
||||
function dragstarted(event, d) {
|
||||
@ -256,7 +263,7 @@ function draw(pathway, elem) {
|
||||
}
|
||||
|
||||
// Wait before showing popup (ms)
|
||||
var popupWaitBeforeShow = 1000;
|
||||
var popupWaitBeforeShow = 500;
|
||||
|
||||
// Custom popover element
|
||||
let popoverTimeout = null;
|
||||
@ -647,21 +654,31 @@ function draw(pathway, elem) {
|
||||
.on("tick", ticked);
|
||||
|
||||
// Kanten zeichnen
|
||||
const link = zoomable.append("g")
|
||||
.selectAll("path")
|
||||
.data(links)
|
||||
.enter().append("path")
|
||||
// Check if target is pseudo and draw marker only if not pseudo
|
||||
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
||||
.attr("marker-end", d => {
|
||||
if (d.target.pseudo) return '';
|
||||
if (d.source.id === d.target.id) return 'url(#curve-arrow)'; // Use curve arrow for self-loops
|
||||
return d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)';
|
||||
})
|
||||
.attr("fill", "none")
|
||||
.on("click", function(event, d) {
|
||||
const linkGroup = zoomable.append("g")
|
||||
.selectAll("g")
|
||||
.data(links)
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("class", "link-group");
|
||||
|
||||
const link = linkGroup
|
||||
.append("path")
|
||||
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
||||
.attr("marker-end", d => d.target.pseudo ? "" : "url(#arrow)")
|
||||
.attr("fill", "none")
|
||||
|
||||
// Check if target is pseudo and draw marker only if not pseudo
|
||||
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
||||
.attr("marker-end", d => {
|
||||
if (d.target.pseudo) return '';
|
||||
if (d.source.id === d.target.id) return 'url(#curve-arrow)'; // Use curve arrow for self-loops
|
||||
return d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)';
|
||||
}
|
||||
)
|
||||
.attr("fill", "none")
|
||||
.on("click", function(event, d) {
|
||||
const wasHighlighted = d3.select(this).classed("highlighted");
|
||||
d3.selectAll("path").classed("highlighted", false);
|
||||
d3.selectAll("path").classed("highlighted", false);
|
||||
if (!wasHighlighted) {
|
||||
const toHighlight = [];
|
||||
toHighlight.push(d.el);
|
||||
@ -688,6 +705,18 @@ function draw(pathway, elem) {
|
||||
}
|
||||
});
|
||||
|
||||
const linkText = linkGroup
|
||||
.append("text")
|
||||
.attr("class", "link-label")
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("dy", -6)
|
||||
.style("font-size", "4px")
|
||||
.style("pointer-events", "none")
|
||||
.style("display", "none")
|
||||
.text(d => d.name)
|
||||
.attr("x", d => (d.source.x + d.target.x) / 2)
|
||||
.attr("y", d => (d.source.y + d.target.y) / 2);
|
||||
|
||||
// add element to links array
|
||||
link.each(function (d) {
|
||||
d.el = this; // attach the DOM element to the data object
|
||||
|
||||
@ -55,6 +55,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
{% if node.description and node.description != "no description" %}
|
||||
<div class="collapse-arrow bg-base-200 collapse">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Description</div>
|
||||
<div class="collapse-content">{{ node.description }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% epdb_slot_templates "epdb.objects.node.viz" as viz_templates %}
|
||||
|
||||
{% for tpl in viz_templates %}
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
.link {
|
||||
stroke: #999;
|
||||
stroke-opacity: 0.6;
|
||||
stroke-width: 1.5px;
|
||||
/* marker-end: url(#arrow); */
|
||||
}
|
||||
|
||||
@ -177,6 +178,52 @@
|
||||
tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-60 p-2"
|
||||
>
|
||||
<li>
|
||||
<a id="compound-names-toggle-button" class="cursor-pointer">
|
||||
<svg
|
||||
id="compound-names-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-eye"
|
||||
>
|
||||
<path
|
||||
d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"
|
||||
/>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
Compound Names
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="reaction-names-toggle-button" class="cursor-pointer">
|
||||
<svg
|
||||
id="reaction-names-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-eye"
|
||||
>
|
||||
<path
|
||||
d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"
|
||||
/>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
Reaction Names
|
||||
</a>
|
||||
</li>
|
||||
{% if pathway.setting.model.app_domain %}
|
||||
<li>
|
||||
<a id="app-domain-toggle-button" class="cursor-pointer">
|
||||
@ -497,6 +544,10 @@
|
||||
{{ pathway.d3_json|json_script:"pathway" }}
|
||||
|
||||
<script>
|
||||
// Global switch for compound names view
|
||||
var compoundNamesViewEnabled = false;
|
||||
// Gloabl switch for reaction names view
|
||||
var reactionNamesViewEnabled = false;
|
||||
// Global switch for app domain view
|
||||
var appDomainViewEnabled = false;
|
||||
// Global switch for timeseries view
|
||||
@ -532,6 +583,67 @@
|
||||
descContent.innerHTML = newDesc;
|
||||
}
|
||||
|
||||
const compoundNamesBtn = document.getElementById("compound-names-toggle-button");
|
||||
if (compoundNamesBtn) {
|
||||
compoundNamesBtn.addEventListener("click", function () {
|
||||
compoundNamesViewEnabled = !compoundNamesViewEnabled;
|
||||
const icon = document.getElementById("compound-names-icon");
|
||||
|
||||
if (compoundNamesViewEnabled) {
|
||||
// Change to eye-off icon
|
||||
icon.innerHTML =
|
||||
'<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" x2="22" y1="2" y2="22"/>';
|
||||
|
||||
nodes.forEach((x) => {
|
||||
if (x.name) {
|
||||
d3.select(x.el)
|
||||
.append("text")
|
||||
.text(d => d.name)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("y", -20)
|
||||
.style("font-size", "4px");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Change back to eye icon
|
||||
icon.innerHTML =
|
||||
'<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>';
|
||||
|
||||
nodes.forEach((x) => {
|
||||
d3.select(x.el)
|
||||
.select("text")
|
||||
.remove()
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const reactionNamesBtn = document.getElementById("reaction-names-toggle-button");
|
||||
if (reactionNamesBtn) {
|
||||
reactionNamesBtn.addEventListener("click", function () {
|
||||
reactionNamesViewEnabled = !reactionNamesViewEnabled;
|
||||
const icon = document.getElementById("reaction-names-icon");
|
||||
|
||||
if (reactionNamesViewEnabled) {
|
||||
// Change to eye-off icon
|
||||
icon.innerHTML =
|
||||
'<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" x2="22" y1="2" y2="22"/>';
|
||||
|
||||
d3.selectAll(".link-label")
|
||||
.style("display", null);
|
||||
|
||||
} else {
|
||||
// Change back to eye icon
|
||||
icon.innerHTML =
|
||||
'<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>';
|
||||
|
||||
d3.selectAll(".link-label")
|
||||
.style("display", "none");
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// App domain toggle
|
||||
const appDomainBtn = document.getElementById("app-domain-toggle-button");
|
||||
if (appDomainBtn) {
|
||||
|
||||
@ -24,6 +24,76 @@
|
||||
<td>Setting Name</td>
|
||||
<td>{{ setting_to_render.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Setting URL</td>
|
||||
<td>
|
||||
<a href="{{ setting_to_render.url }}" class="link link-primary">{{ setting_to_render.url }}</a>
|
||||
<div
|
||||
x-data="{
|
||||
value: '{{ setting_to_render.url }}',
|
||||
copied: false,
|
||||
|
||||
async copy() {
|
||||
await navigator.clipboard.writeText(this.value)
|
||||
this.copied = true
|
||||
|
||||
setTimeout(() => {
|
||||
this.copied = false
|
||||
}, 1500)
|
||||
}
|
||||
}"
|
||||
class="join"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost"
|
||||
:data-tip="copied ? 'Copied!' : 'Copy'"
|
||||
@click="copy"
|
||||
aria-label="Copy to clipboard"
|
||||
>
|
||||
<svg
|
||||
x-show="!copied"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2"
|
||||
/>
|
||||
<rect
|
||||
width="12"
|
||||
height="12"
|
||||
x="8"
|
||||
y="8"
|
||||
rx="2"
|
||||
ry="2"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
x-show="copied"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% if setting_to_render.description %}
|
||||
<tr>
|
||||
<td>Setting Description</td>
|
||||
|
||||
@ -36,7 +36,7 @@ from epdb.models import (
|
||||
Setting,
|
||||
SimpleAmbitRule,
|
||||
SimpleRDKitRule,
|
||||
SimpleRule,
|
||||
SimpleRule, Group,
|
||||
)
|
||||
from utilities.chem import FormatConverter
|
||||
|
||||
@ -44,7 +44,7 @@ logger = logging.getLogger(__name__)
|
||||
Package = s.GET_PACKAGE_MODEL()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from epdb.logic import SPathway
|
||||
from epdb.logic import SPathway, GroupManager
|
||||
|
||||
|
||||
class LicenseExportSchema(Schema):
|
||||
@ -123,8 +123,8 @@ class PESCompoundExportSchema(RefCompoundExportSchema):
|
||||
description: str
|
||||
aliases: List[str]
|
||||
default_structure: RefCompoundStructureExportSchema
|
||||
structures: List["CompoundStructureExportSchema"]
|
||||
structures: List["PESCompoundStructureExportSchema"]
|
||||
scenarios: List[RefScenarioExportSchema]
|
||||
|
||||
|
||||
class PESCompoundStructureExportSchema(RefCompoundStructureExportSchema):
|
||||
@ -277,7 +277,7 @@ class PackageExportSchema(Schema):
|
||||
|
||||
@staticmethod
|
||||
def resolve_classification_level(obj):
|
||||
if isinstance(obj, str):
|
||||
if isinstance(obj, dict):
|
||||
return obj["classification_level"]
|
||||
return obj.Classification(obj.classification_level).name
|
||||
|
||||
@ -602,15 +602,20 @@ class PackageImporter:
|
||||
package_license = License.objects.get(link=parsed.license.link) if parsed.license else None
|
||||
package_classification_level = Package.Classification[parsed.classification_level.upper()]
|
||||
|
||||
package = Package.objects.create(
|
||||
uuid=package_uuid,
|
||||
name=f"{parsed.name} - Imported at {datetime.now()}",
|
||||
description=parsed.description,
|
||||
reviewed=False, # Everything will be imported as non reviewed
|
||||
license=package_license,
|
||||
classification_level=package_classification_level,
|
||||
data_pool=parsed.data_pool,
|
||||
)
|
||||
print(parsed.classification_level, "->", package_classification_level)
|
||||
|
||||
package = Package()
|
||||
package.uuid = package_uuid
|
||||
package.name = f"{parsed.name} - Imported at {datetime.now()}"
|
||||
package.description = parsed.description
|
||||
package.reviewed = False
|
||||
package.license = package_license
|
||||
package.classification_level = package_classification_level
|
||||
|
||||
if package_classification_level == Package.Classification.SECRET:
|
||||
package.data_pool = Group.objects.get(uuid=parsed.data_pool.uuid)
|
||||
|
||||
package.save()
|
||||
|
||||
# Import Elements
|
||||
self._import_compounds(package, parsed.compounds)
|
||||
|
||||
Reference in New Issue
Block a user