Skip to content

Commit c610296

Browse files
committed
feat: improve editor display, now results go full width while examples goes alongside the Yasqe editor, improve examples display on small screens, generates tabs name based on the example description
1 parent ae6b386 commit c610296

File tree

4 files changed

+126
-78
lines changed

4 files changed

+126
-78
lines changed

index.html

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,43 +21,6 @@
2121
examples-repo-add-url="https://github.com/sib-swiss/sparql-examples/new/master/examples/UniProt"
2222
add-limit="1000"
2323
></sparql-editor>
24-
<!-- MULTI with children -->
25-
<!-- <sparql-editor
26-
endpoint="https://sparql.uniprot.org/sparql/,https://www.bgee.org/sparql/,https://sparql.omabrowser.org/sparql/,https://beta.sparql.swisslipids.org/,https://sparql.rhea-db.org/sparql/,https://biosoda.unil.ch/graphdb/repositories/emi-dbgi,https://hamap.expasy.org/sparql/,https://rdf.metanetx.org/sparql/,https://idsm.elixir-czech.cz/sparql/endpoint/idsm"
27-
examples-repo-add-url="https://github.com/sib-swiss/sparql-examples/new/master/examples/UniProt"
28-
add-limit="1000"
29-
>
30-
<h4>Metadata-based SPARQL editor</h4>
31-
<p>Metadata used by this editor are automatically retrieved by querying the endpoints:</p>
32-
<ul>
33-
<li>Context-aware autocomplete for classes and predicates based on the content of the endpoints</li>
34-
<li>Example queries</li>
35-
<li>Commonly used prefixes</li>
36-
</ul>
37-
<p>
38-
See the <a href="https://github.com/sib-swiss/sparql-editor" target="_blank">repository</a> for more details,
39-
or the <a href="check">check page</a> to check if an endpoint contains the necessary metadata.
40-
</p>
41-
<h4>About</h4>
42-
<p>
43-
This SPARQL endpoint contains all UniProt data. It is free to access and supports the
44-
<a href="http://www.w3.org/TR/sparql11-query/">SPARQL 1.1 Standard</a>.
45-
</p>
46-
<p>
47-
There are 186,631,773,819 triples in this release (2024_04). The query timeout is 45 minutes. All triples are
48-
available in the default graph. There are 22 named graphs.
49-
</p>
50-
<h4>Documentation</h4>
51-
<ol>
52-
<li><a href="http://purl.uniprot.org/core/">Classes and predicates defined by the UniProt consortium</a></li>
53-
<li>
54-
<a href="https://github.com/sib-swiss/sparql-training/tree/master/uniprot"
55-
>Tutorial on using SPARQL with UniProt</a
56-
>
57-
</li>
58-
<li><a href="/.well-known/void">Statistics and diagrams</a></li>
59-
</ol>
60-
</sparql-editor> -->
6124

6225
<!-- BGEE -->
6326
<!-- <sparql-editor endpoint="https://www.bgee.org/sparql/">

src/sparql-editor.ts

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
getServiceUriForCursorPosition,
2020
compressUri,
2121
defaultPrefixes,
22+
generateTabLabel,
2223
getClassesFallback,
2324
getPredicatesFallback,
2425
} from "./utils";
@@ -69,7 +70,7 @@ export class SparqlEditor extends HTMLElement {
6970
throw new Error("No endpoint provided. Please use the 'endpoint' attribute to specify the SPARQL endpoint URL.");
7071

7172
this.addLimit = Number(this.getAttribute("add-limit")) || null;
72-
this.examplesOnMainPage = Number(this.getAttribute("examples-on-main-page")) || 10;
73+
this.examplesOnMainPage = Number(this.getAttribute("examples-on-main-page")) || 8;
7374
this.examplesRepoAddUrl = this.getAttribute("examples-repo-add-url");
7475
this.examplesRepo = this.getAttribute("examples-repository");
7576
if (this.examplesRepoAddUrl && !this.examplesRepo) this.examplesRepo = this.examplesRepoAddUrl.split("/new/")[0];
@@ -88,23 +89,20 @@ export class SparqlEditor extends HTMLElement {
8889
}
8990
this.className = "sparql-editor-container";
9091
this.innerHTML = `
91-
<div id="sparql-text-editor">
92+
<div style="width: 100%;">
9293
<a id="status-link" href="" target="_blank" title="Loading..." style="display: inline-flex; width: 16px; height: 16px;">
9394
<div id="status-light" style="width: 10px; height: 10px; background-color: purple; border-radius: 50%; margin: 0 auto;"></div>
9495
</a>
9596
<button id="sparql-add-prefixes-btn" class="btn" style="margin-bottom: 0.3em;">Add common prefixes</button>
9697
<button id="sparql-save-example-btn" class="btn" style="margin-bottom: 0.3em;">Save query as example</button>
98+
<button id="sparql-examples-top-btn" class="btn" style="margin-bottom: 0.3em;">Browse examples</button>
9799
<button id="sparql-clear-cache-btn" class="btn" style="margin-bottom: 0.3em;">Clear cache</button>
98100
<div id="yasgui"></div>
99101
<div id="loading-spinner" style="display: flex; justify-content: center; align-items: center; height: 100px; flex-direction: column;">
100102
<div class="spinner" style="border: 4px solid rgba(0,0,0,0.1); border-left-color: #000; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite;"></div>
101103
<p style="margin-top: 10px; text-align: center;">Loading editor...</p>
102104
</div>
103105
</div>
104-
<div>
105-
<div id="sparql-examples"></div>
106-
<slot></slot>
107-
</div>
108106
`;
109107
this.appendChild(style);
110108

@@ -181,13 +179,14 @@ export class SparqlEditor extends HTMLElement {
181179
}
182180

183181
// Load current endpoint in the YASGUI input box
184-
async loadCurrentEndpoint(endpoint: string = this.endpointUrl()) {
182+
async loadCurrentEndpoint(endpoint: string = this.endpointUrl(), forceExamplesReload: boolean = false) {
185183
// console.log("Switching endpoint", endpoint);
186184
await this.getMetadata(endpoint);
187-
await this.showExamples();
185+
await this.showExamples(forceExamplesReload);
188186
// @ts-ignore set default query when new tab
189187
this.yasgui.config.yasqe.value =
190188
this.addPrefixesToQuery(this.currentEndpoint().examples[0]?.query) || Yasgui.Yasqe.defaults.value;
189+
191190
Yasgui.Yasr.defaults.prefixes = this.meta[endpoint].prefixes;
192191
// Update the statusLight
193192
const statusLight = this.querySelector("#status-light") as HTMLElement;
@@ -245,7 +244,10 @@ export class SparqlEditor extends HTMLElement {
245244
setTimeout(() => this.loadCurrentEndpoint());
246245
});
247246
this.yasgui?.on("endpointHistoryChange", () => {
248-
setTimeout(() => this.loadCurrentEndpoint());
247+
setTimeout(() => this.loadCurrentEndpoint(this.endpointUrl(), true));
248+
});
249+
this.yasgui?.on("tabAdd", () => {
250+
setTimeout(() => this.showExamples());
249251
});
250252

251253
// Button to clear and update cache of SPARQL endpoints metadata
@@ -554,20 +556,37 @@ ex:${exampleUri} a sh:SPARQLExecutable${
554556
}
555557
}
556558

557-
async showExamples() {
559+
async showExamples(forceReload: boolean = false) {
558560
// Display examples on the main page and in a dialog for the currently selected endpoint
559-
const exampleQueriesEl = this.querySelector("#sparql-examples") as HTMLElement;
561+
const existingExampleQueriesEl = this.querySelector(".active .sparql-examples") as HTMLButtonElement;
562+
const examplesTopBtnEl = this.querySelector("#sparql-examples-top-btn") as HTMLButtonElement;
563+
const btnTextContent = `Browse ${this.currentEndpoint().examples.length} examples`;
564+
if (this.currentEndpoint().examples.length === 0) {
565+
existingExampleQueriesEl?.remove();
566+
examplesTopBtnEl.style.display = "none";
567+
return;
568+
} else {
569+
examplesTopBtnEl.textContent = btnTextContent;
570+
examplesTopBtnEl.style.display = "inline-block";
571+
}
572+
if (existingExampleQueriesEl && !forceReload) {
573+
return;
574+
}
575+
if (existingExampleQueriesEl) existingExampleQueriesEl?.remove();
576+
const yasqeEl = this.querySelector(".active .yasqe") as HTMLElement;
577+
const yasqeElParent = yasqeEl.parentElement as HTMLElement;
578+
const exampleQueriesEl = document.createElement("div");
579+
exampleQueriesEl.className = "sparql-examples";
560580
exampleQueriesEl.innerHTML = "";
561-
if (this.currentEndpoint().examples.length === 0) return;
562-
// Add title for examples
563-
const exQueryTitleDiv = document.createElement("div");
564-
exQueryTitleDiv.style.textAlign = "center";
565-
const exQueryTitle = document.createElement("h3");
566-
exQueryTitle.style.margin = "0.1em";
567-
exQueryTitle.style.fontWeight = "200";
568-
exQueryTitle.textContent = "Examples";
569-
exQueryTitleDiv.appendChild(exQueryTitle);
570-
exampleQueriesEl.appendChild(exQueryTitleDiv);
581+
// TODO: remove title for examples?
582+
// const exQueryTitleDiv = document.createElement("div");
583+
// exQueryTitleDiv.style.textAlign = "center";
584+
// const exQueryTitle = document.createElement("h3");
585+
// exQueryTitle.style.margin = "0.1em";
586+
// exQueryTitle.style.fontWeight = "200";
587+
// exQueryTitle.textContent = "Examples";
588+
// exQueryTitleDiv.appendChild(exQueryTitle);
589+
// exampleQueriesEl.appendChild(exQueryTitleDiv);
571590

572591
// Create dialog for examples
573592
const exQueryDialog = document.createElement("dialog");
@@ -586,7 +605,7 @@ ex:${exampleUri} a sh:SPARQLExecutable${
586605
exDialogCloseBtn.style.top = "1.5em";
587606
exDialogCloseBtn.style.right = "2em";
588607
exQueryDialog.appendChild(exDialogCloseBtn);
589-
exampleQueriesEl.appendChild(exQueryDialog);
608+
yasqeElParent.appendChild(exQueryDialog);
590609

591610
// Add examples to the main page and dialog
592611
this.currentEndpoint().examples.forEach(async (example, index) => {
@@ -601,7 +620,7 @@ ex:${exampleUri} a sh:SPARQLExecutable${
601620
useBtn.style.marginLeft = "0.5em";
602621
useBtn.className = "btn sparqlExampleButton";
603622
useBtn.addEventListener("click", () => {
604-
this.addTab(example.query, index);
623+
this.addTab(example.query, example.comment);
605624
exQueryDialog.close();
606625
});
607626
exQueryP.appendChild(useBtn);
@@ -614,7 +633,7 @@ ex:${exampleUri} a sh:SPARQLExecutable${
614633
cloneExQueryDiv.className = "main-query-example";
615634
// Cloning does not include click event so we need to redo it :(
616635
cloneExQueryDiv.lastChild?.lastChild?.addEventListener("click", () => {
617-
this.addTab(example.query, index);
636+
this.addTab(example.query, example.comment);
618637
});
619638
exampleQueriesEl.appendChild(cloneExQueryDiv);
620639
}
@@ -650,10 +669,14 @@ ex:${exampleUri} a sh:SPARQLExecutable${
650669

651670
// Add button to open dialog
652671
const openExDialogBtn = document.createElement("button");
653-
openExDialogBtn.textContent = `Browse ${this.currentEndpoint().examples.length} examples`;
672+
openExDialogBtn.textContent = btnTextContent;
654673
openExDialogBtn.className = "btn";
655674
exampleQueriesEl.appendChild(openExDialogBtn);
656675

676+
examplesTopBtnEl.addEventListener("click", () => {
677+
exQueryDialog.showModal();
678+
document.body.style.overflow = "hidden";
679+
});
657680
openExDialogBtn.addEventListener("click", () => {
658681
exQueryDialog.showModal();
659682
document.body.style.overflow = "hidden";
@@ -665,6 +688,20 @@ ex:${exampleUri} a sh:SPARQLExecutable${
665688
exQueryDialog.addEventListener("close", () => {
666689
document.body.style.overflow = "";
667690
});
691+
692+
// Add the examples next to the YASQE editor
693+
// yasqeEl.style.height = "100%";
694+
yasqeEl.style.width = "100%";
695+
exampleQueriesEl.style.width = "50%";
696+
yasqeElParent.style.display = "flex";
697+
yasqeElParent.appendChild(exampleQueriesEl);
698+
const yasqe = this.yasgui?.getTab()?.getYasqe();
699+
yasqe?.expandEditor();
700+
// if (exampleQueriesEl.offsetHeight > 0) {
701+
// Yasgui.Yasqe.defaults.editorHeight = `${exampleQueriesEl.offsetHeight}px`;
702+
// const yasqe = this.yasgui?.getTab()?.getYasqe();
703+
// yasqe?.expandEditor();
704+
// }
668705
}
669706

670707
addPrefixesToQuery(query: string) {
@@ -698,10 +735,10 @@ ex:${exampleUri} a sh:SPARQLExecutable${
698735
}
699736
}
700737

701-
addTab(query: string, index: number) {
738+
addTab(query: string, label: string) {
702739
this.yasgui?.addTab(true, {
703740
...Yasgui.Tab.getDefaults(),
704-
name: `Query ${index + 1}`,
741+
name: generateTabLabel(label),
705742
requestConfig: {
706743
...Yasgui.defaults.requestConfig,
707744
endpoint: this.endpointUrl(),

src/styles.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,39 @@ export const editorCss = `.sparql-editor-container a {
2121
display: flex;
2222
flex-direction: row;
2323
}
24-
.sparql-editor-container #sparql-text-editor {
25-
flex: 0 0 60%;
26-
margin-right: 1em;
27-
}
28-
.sparql-editor-container #sparql-examples {
29-
flex-grow: 1;
30-
border-left: 1px solid #ccc;
24+
.sparql-editor-container .sparql-examples {
3125
padding-left: 1em;
3226
}
3327
@media (max-width: 600px) {
3428
.sparql-editor-container {
3529
flex-direction: column;
3630
}
37-
.sparql-editor-container #sparql-text-editor {
38-
margin-right: 0;
31+
.sparql-editor-container .sparql-examples {
32+
display: none;
3933
}
40-
.sparql-editor-container #sparql-examples {
41-
border-left: none;
42-
padding-left: 0;
43-
border-top: 1px solid #ccc;
44-
padding-top: 1em;
34+
.sparql-editor-container #sparql-examples-top-btn {
35+
display: inline-block;
36+
}
37+
// .sparql-editor-container .sparql-examples {
38+
// border-left: none;
39+
// padding-left: 0;
40+
// border-top: 1px solid #ccc;
41+
// padding-top: 1em;
42+
// }
43+
}
44+
@media (min-width: 600px) {
45+
// .sparql-editor-container {
46+
// flex-direction: column;
47+
// }
48+
// .sparql-editor-container .sparql-examples {
49+
// display: none;
50+
// }
51+
.sparql-editor-container #sparql-examples-top-btn {
52+
display: none !important;
4553
}
4654
}
4755
56+
4857
.sparql-editor-container {
4958
--btn-color: #e30613;
5059
--btn-bg-color: #f8bca5;

src/utils.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,45 @@ export function getServiceUriForCursorPosition(query: string, lineNumber: number
256256
return null;
257257
}
258258

259+
// Automatically generates a tab label from a query description by removing small words and stopwords
260+
export function generateTabLabel(description: string): string {
261+
// const stopwords = ['all', 'with', 'and', 'the', 'on', 'of', 'in', 'for', 'a', 'an', 'entries', 'annotated'];
262+
const ignoreStopwords = [
263+
"select",
264+
"that",
265+
"with",
266+
"entries",
267+
"annotated",
268+
"were",
269+
"triples",
270+
"relate",
271+
"entry",
272+
"each",
273+
"using",
274+
"where",
275+
"find",
276+
"list",
277+
"sometimes",
278+
"known",
279+
"their",
280+
"them",
281+
"from",
282+
"these",
283+
];
284+
// Remove HTML tags and parenthesis
285+
const words = description
286+
.replace(/<\/?[^>]+(>|$)/g, "")
287+
.replace(/[(),]/gm, "")
288+
.split(" ");
289+
const filteredWords = words.filter(word => !ignoreStopwords.includes(word.toLowerCase()) && word.length > 3);
290+
const label = filteredWords.slice(0, 3).join(" ");
291+
const capitalizedLabel = label
292+
.split(" ")
293+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
294+
.join(" ");
295+
return capitalizedLabel;
296+
}
297+
259298
// NOTE: In case we need to store the counts
260299
// type VoidDict2 = {
261300
// // Subject class

0 commit comments

Comments
 (0)