Skip to content

Commit

Permalink
feat: improve editor display, now results go full width while example…
Browse files Browse the repository at this point in the history
…s goes alongside the Yasqe editor, improve examples display on small screens, generates tabs name based on the example description
  • Loading branch information
vemonet committed Oct 22, 2024
1 parent ae6b386 commit c610296
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 78 deletions.
37 changes: 0 additions & 37 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,6 @@
examples-repo-add-url="https://github.com/sib-swiss/sparql-examples/new/master/examples/UniProt"
add-limit="1000"
></sparql-editor>
<!-- MULTI with children -->
<!-- <sparql-editor
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"
examples-repo-add-url="https://github.com/sib-swiss/sparql-examples/new/master/examples/UniProt"
add-limit="1000"
>
<h4>Metadata-based SPARQL editor</h4>
<p>Metadata used by this editor are automatically retrieved by querying the endpoints:</p>
<ul>
<li>Context-aware autocomplete for classes and predicates based on the content of the endpoints</li>
<li>Example queries</li>
<li>Commonly used prefixes</li>
</ul>
<p>
See the <a href="https://github.com/sib-swiss/sparql-editor" target="_blank">repository</a> for more details,
or the <a href="check">check page</a> to check if an endpoint contains the necessary metadata.
</p>
<h4>About</h4>
<p>
This SPARQL endpoint contains all UniProt data. It is free to access and supports the
<a href="http://www.w3.org/TR/sparql11-query/">SPARQL 1.1 Standard</a>.
</p>
<p>
There are 186,631,773,819 triples in this release (2024_04). The query timeout is 45 minutes. All triples are
available in the default graph. There are 22 named graphs.
</p>
<h4>Documentation</h4>
<ol>
<li><a href="http://purl.uniprot.org/core/">Classes and predicates defined by the UniProt consortium</a></li>
<li>
<a href="https://github.com/sib-swiss/sparql-training/tree/master/uniprot"
>Tutorial on using SPARQL with UniProt</a
>
</li>
<li><a href="/.well-known/void">Statistics and diagrams</a></li>
</ol>
</sparql-editor> -->

<!-- BGEE -->
<!-- <sparql-editor endpoint="https://www.bgee.org/sparql/">
Expand Down
91 changes: 64 additions & 27 deletions src/sparql-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getServiceUriForCursorPosition,
compressUri,
defaultPrefixes,
generateTabLabel,
getClassesFallback,
getPredicatesFallback,
} from "./utils";
Expand Down Expand Up @@ -69,7 +70,7 @@ export class SparqlEditor extends HTMLElement {
throw new Error("No endpoint provided. Please use the 'endpoint' attribute to specify the SPARQL endpoint URL.");

this.addLimit = Number(this.getAttribute("add-limit")) || null;
this.examplesOnMainPage = Number(this.getAttribute("examples-on-main-page")) || 10;
this.examplesOnMainPage = Number(this.getAttribute("examples-on-main-page")) || 8;
this.examplesRepoAddUrl = this.getAttribute("examples-repo-add-url");
this.examplesRepo = this.getAttribute("examples-repository");
if (this.examplesRepoAddUrl && !this.examplesRepo) this.examplesRepo = this.examplesRepoAddUrl.split("/new/")[0];
Expand All @@ -88,23 +89,20 @@ export class SparqlEditor extends HTMLElement {
}
this.className = "sparql-editor-container";
this.innerHTML = `
<div id="sparql-text-editor">
<div style="width: 100%;">
<a id="status-link" href="" target="_blank" title="Loading..." style="display: inline-flex; width: 16px; height: 16px;">
<div id="status-light" style="width: 10px; height: 10px; background-color: purple; border-radius: 50%; margin: 0 auto;"></div>
</a>
<button id="sparql-add-prefixes-btn" class="btn" style="margin-bottom: 0.3em;">Add common prefixes</button>
<button id="sparql-save-example-btn" class="btn" style="margin-bottom: 0.3em;">Save query as example</button>
<button id="sparql-examples-top-btn" class="btn" style="margin-bottom: 0.3em;">Browse examples</button>
<button id="sparql-clear-cache-btn" class="btn" style="margin-bottom: 0.3em;">Clear cache</button>
<div id="yasgui"></div>
<div id="loading-spinner" style="display: flex; justify-content: center; align-items: center; height: 100px; flex-direction: column;">
<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>
<p style="margin-top: 10px; text-align: center;">Loading editor...</p>
</div>
</div>
<div>
<div id="sparql-examples"></div>
<slot></slot>
</div>
`;
this.appendChild(style);

Expand Down Expand Up @@ -181,13 +179,14 @@ export class SparqlEditor extends HTMLElement {
}

// Load current endpoint in the YASGUI input box
async loadCurrentEndpoint(endpoint: string = this.endpointUrl()) {
async loadCurrentEndpoint(endpoint: string = this.endpointUrl(), forceExamplesReload: boolean = false) {
// console.log("Switching endpoint", endpoint);
await this.getMetadata(endpoint);
await this.showExamples();
await this.showExamples(forceExamplesReload);
// @ts-ignore set default query when new tab
this.yasgui.config.yasqe.value =
this.addPrefixesToQuery(this.currentEndpoint().examples[0]?.query) || Yasgui.Yasqe.defaults.value;

Yasgui.Yasr.defaults.prefixes = this.meta[endpoint].prefixes;
// Update the statusLight
const statusLight = this.querySelector("#status-light") as HTMLElement;
Expand Down Expand Up @@ -245,7 +244,10 @@ export class SparqlEditor extends HTMLElement {
setTimeout(() => this.loadCurrentEndpoint());
});
this.yasgui?.on("endpointHistoryChange", () => {
setTimeout(() => this.loadCurrentEndpoint());
setTimeout(() => this.loadCurrentEndpoint(this.endpointUrl(), true));
});
this.yasgui?.on("tabAdd", () => {
setTimeout(() => this.showExamples());
});

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

async showExamples() {
async showExamples(forceReload: boolean = false) {
// Display examples on the main page and in a dialog for the currently selected endpoint
const exampleQueriesEl = this.querySelector("#sparql-examples") as HTMLElement;
const existingExampleQueriesEl = this.querySelector(".active .sparql-examples") as HTMLButtonElement;
const examplesTopBtnEl = this.querySelector("#sparql-examples-top-btn") as HTMLButtonElement;
const btnTextContent = `Browse ${this.currentEndpoint().examples.length} examples`;
if (this.currentEndpoint().examples.length === 0) {
existingExampleQueriesEl?.remove();
examplesTopBtnEl.style.display = "none";
return;
} else {
examplesTopBtnEl.textContent = btnTextContent;
examplesTopBtnEl.style.display = "inline-block";
}
if (existingExampleQueriesEl && !forceReload) {
return;
}
if (existingExampleQueriesEl) existingExampleQueriesEl?.remove();
const yasqeEl = this.querySelector(".active .yasqe") as HTMLElement;
const yasqeElParent = yasqeEl.parentElement as HTMLElement;
const exampleQueriesEl = document.createElement("div");
exampleQueriesEl.className = "sparql-examples";
exampleQueriesEl.innerHTML = "";
if (this.currentEndpoint().examples.length === 0) return;
// Add title for examples
const exQueryTitleDiv = document.createElement("div");
exQueryTitleDiv.style.textAlign = "center";
const exQueryTitle = document.createElement("h3");
exQueryTitle.style.margin = "0.1em";
exQueryTitle.style.fontWeight = "200";
exQueryTitle.textContent = "Examples";
exQueryTitleDiv.appendChild(exQueryTitle);
exampleQueriesEl.appendChild(exQueryTitleDiv);
// TODO: remove title for examples?
// const exQueryTitleDiv = document.createElement("div");
// exQueryTitleDiv.style.textAlign = "center";
// const exQueryTitle = document.createElement("h3");
// exQueryTitle.style.margin = "0.1em";
// exQueryTitle.style.fontWeight = "200";
// exQueryTitle.textContent = "Examples";
// exQueryTitleDiv.appendChild(exQueryTitle);
// exampleQueriesEl.appendChild(exQueryTitleDiv);

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

// Add examples to the main page and dialog
this.currentEndpoint().examples.forEach(async (example, index) => {
Expand All @@ -601,7 +620,7 @@ ex:${exampleUri} a sh:SPARQLExecutable${
useBtn.style.marginLeft = "0.5em";
useBtn.className = "btn sparqlExampleButton";
useBtn.addEventListener("click", () => {
this.addTab(example.query, index);
this.addTab(example.query, example.comment);
exQueryDialog.close();
});
exQueryP.appendChild(useBtn);
Expand All @@ -614,7 +633,7 @@ ex:${exampleUri} a sh:SPARQLExecutable${
cloneExQueryDiv.className = "main-query-example";
// Cloning does not include click event so we need to redo it :(
cloneExQueryDiv.lastChild?.lastChild?.addEventListener("click", () => {
this.addTab(example.query, index);
this.addTab(example.query, example.comment);
});
exampleQueriesEl.appendChild(cloneExQueryDiv);
}
Expand Down Expand Up @@ -650,10 +669,14 @@ ex:${exampleUri} a sh:SPARQLExecutable${

// Add button to open dialog
const openExDialogBtn = document.createElement("button");
openExDialogBtn.textContent = `Browse ${this.currentEndpoint().examples.length} examples`;
openExDialogBtn.textContent = btnTextContent;
openExDialogBtn.className = "btn";
exampleQueriesEl.appendChild(openExDialogBtn);

examplesTopBtnEl.addEventListener("click", () => {
exQueryDialog.showModal();
document.body.style.overflow = "hidden";
});
openExDialogBtn.addEventListener("click", () => {
exQueryDialog.showModal();
document.body.style.overflow = "hidden";
Expand All @@ -665,6 +688,20 @@ ex:${exampleUri} a sh:SPARQLExecutable${
exQueryDialog.addEventListener("close", () => {
document.body.style.overflow = "";
});

// Add the examples next to the YASQE editor
// yasqeEl.style.height = "100%";
yasqeEl.style.width = "100%";
exampleQueriesEl.style.width = "50%";
yasqeElParent.style.display = "flex";
yasqeElParent.appendChild(exampleQueriesEl);
const yasqe = this.yasgui?.getTab()?.getYasqe();
yasqe?.expandEditor();
// if (exampleQueriesEl.offsetHeight > 0) {
// Yasgui.Yasqe.defaults.editorHeight = `${exampleQueriesEl.offsetHeight}px`;
// const yasqe = this.yasgui?.getTab()?.getYasqe();
// yasqe?.expandEditor();
// }
}

addPrefixesToQuery(query: string) {
Expand Down Expand Up @@ -698,10 +735,10 @@ ex:${exampleUri} a sh:SPARQLExecutable${
}
}

addTab(query: string, index: number) {
addTab(query: string, label: string) {
this.yasgui?.addTab(true, {
...Yasgui.Tab.getDefaults(),
name: `Query ${index + 1}`,
name: generateTabLabel(label),
requestConfig: {
...Yasgui.defaults.requestConfig,
endpoint: this.endpointUrl(),
Expand Down
37 changes: 23 additions & 14 deletions src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,39 @@ export const editorCss = `.sparql-editor-container a {
display: flex;
flex-direction: row;
}
.sparql-editor-container #sparql-text-editor {
flex: 0 0 60%;
margin-right: 1em;
}
.sparql-editor-container #sparql-examples {
flex-grow: 1;
border-left: 1px solid #ccc;
.sparql-editor-container .sparql-examples {
padding-left: 1em;
}
@media (max-width: 600px) {
.sparql-editor-container {
flex-direction: column;
}
.sparql-editor-container #sparql-text-editor {
margin-right: 0;
.sparql-editor-container .sparql-examples {
display: none;
}
.sparql-editor-container #sparql-examples {
border-left: none;
padding-left: 0;
border-top: 1px solid #ccc;
padding-top: 1em;
.sparql-editor-container #sparql-examples-top-btn {
display: inline-block;
}
// .sparql-editor-container .sparql-examples {
// border-left: none;
// padding-left: 0;
// border-top: 1px solid #ccc;
// padding-top: 1em;
// }
}
@media (min-width: 600px) {
// .sparql-editor-container {
// flex-direction: column;
// }
// .sparql-editor-container .sparql-examples {
// display: none;
// }
.sparql-editor-container #sparql-examples-top-btn {
display: none !important;
}
}
.sparql-editor-container {
--btn-color: #e30613;
--btn-bg-color: #f8bca5;
Expand Down
39 changes: 39 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,45 @@ export function getServiceUriForCursorPosition(query: string, lineNumber: number
return null;
}

// Automatically generates a tab label from a query description by removing small words and stopwords
export function generateTabLabel(description: string): string {
// const stopwords = ['all', 'with', 'and', 'the', 'on', 'of', 'in', 'for', 'a', 'an', 'entries', 'annotated'];
const ignoreStopwords = [
"select",
"that",
"with",
"entries",
"annotated",
"were",
"triples",
"relate",
"entry",
"each",
"using",
"where",
"find",
"list",
"sometimes",
"known",
"their",
"them",
"from",
"these",
];
// Remove HTML tags and parenthesis
const words = description
.replace(/<\/?[^>]+(>|$)/g, "")
.replace(/[(),]/gm, "")
.split(" ");
const filteredWords = words.filter(word => !ignoreStopwords.includes(word.toLowerCase()) && word.length > 3);
const label = filteredWords.slice(0, 3).join(" ");
const capitalizedLabel = label
.split(" ")
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
return capitalizedLabel;
}

// NOTE: In case we need to store the counts
// type VoidDict2 = {
// // Subject class
Expand Down

0 comments on commit c610296

Please sign in to comment.