@matdata/yasr 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +150 -0
- package/build/ts/src/bindingsToCsv.d.ts +2 -0
- package/build/ts/src/defaults.d.ts +2 -0
- package/build/ts/src/helpers/addCSS.d.ts +1 -0
- package/build/ts/src/helpers/addScript.d.ts +1 -0
- package/build/ts/src/helpers/index.d.ts +3 -0
- package/build/ts/src/helpers/sanitize.d.ts +2 -0
- package/build/ts/src/imgs.d.ts +4 -0
- package/build/ts/src/index.d.ts +130 -0
- package/build/ts/src/parsers/csv.d.ts +2 -0
- package/build/ts/src/parsers/index.d.ts +68 -0
- package/build/ts/src/parsers/json.d.ts +2 -0
- package/build/ts/src/parsers/tsv.d.ts +2 -0
- package/build/ts/src/parsers/turtleFamily.d.ts +4 -0
- package/build/ts/src/parsers/xml.d.ts +2 -0
- package/build/ts/src/plugins/boolean/index.d.ts +13 -0
- package/build/ts/src/plugins/error/index.d.ts +13 -0
- package/build/ts/src/plugins/index.d.ts +19 -0
- package/build/ts/src/plugins/response/index.d.ts +28 -0
- package/build/ts/src/plugins/table/index.d.ts +49 -0
- package/build/yasr.html +32 -0
- package/build/yasr.min.css +2 -0
- package/build/yasr.min.css.map +1 -0
- package/build/yasr.min.js +3 -0
- package/build/yasr.min.js.LICENSE.txt +34 -0
- package/build/yasr.min.js.map +1 -0
- package/package.json +56 -0
- package/src/bin/takeScreenshot.js +373 -0
- package/src/bindingsToCsv.ts +18 -0
- package/src/defaults.ts +28 -0
- package/src/helpers/addCSS.ts +7 -0
- package/src/helpers/addScript.ts +12 -0
- package/src/helpers/index.ts +3 -0
- package/src/helpers/sanitize.ts +11 -0
- package/src/imgs.ts +7 -0
- package/src/index.ts +688 -0
- package/src/jquery/extendJquery.js +1 -0
- package/src/jquery/tableToCsv.js +88 -0
- package/src/main.scss +229 -0
- package/src/parsers/csv.ts +30 -0
- package/src/parsers/index.ts +311 -0
- package/src/parsers/json.ts +22 -0
- package/src/parsers/tsv.ts +43 -0
- package/src/parsers/turtleFamily.ts +60 -0
- package/src/parsers/xml.ts +79 -0
- package/src/plugins/boolean/index.scss +11 -0
- package/src/plugins/boolean/index.ts +42 -0
- package/src/plugins/error/index.scss +57 -0
- package/src/plugins/error/index.ts +124 -0
- package/src/plugins/index.ts +24 -0
- package/src/plugins/response/index.scss +63 -0
- package/src/plugins/response/index.ts +170 -0
- package/src/plugins/table/index.scss +154 -0
- package/src/plugins/table/index.ts +437 -0
- package/src/scss/global.scss +10 -0
- package/src/scss/variables.scss +2 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import Parser from "./";
|
|
2
|
+
|
|
3
|
+
export default function (tsvString: string) {
|
|
4
|
+
const lines = tsvString.split("\n");
|
|
5
|
+
|
|
6
|
+
lines.pop(); //remove the last empty line
|
|
7
|
+
|
|
8
|
+
const [headersString, ...sparqlDataStringArr] = lines;
|
|
9
|
+
const headers = headersString.split("\t").map((header) => header.substring(1));
|
|
10
|
+
|
|
11
|
+
const sparqlData = sparqlDataStringArr.map((row) => {
|
|
12
|
+
const binding: Parser.Binding = {};
|
|
13
|
+
for (const [index, value] of row.split("\t").entries()) {
|
|
14
|
+
const bindingName = headers[index];
|
|
15
|
+
if (value[0] === "<") {
|
|
16
|
+
binding[bindingName] = { value: value.substring(1, value.length - 1), type: "uri" };
|
|
17
|
+
} else if (value[0] === '"') {
|
|
18
|
+
const lastDoubleQuote = value.lastIndexOf('"');
|
|
19
|
+
const literalValue = value.substring(1, lastDoubleQuote);
|
|
20
|
+
if (lastDoubleQuote === value.length - 1) binding[bindingName] = { value: literalValue, type: "literal" };
|
|
21
|
+
else if (lastDoubleQuote < value.lastIndexOf("@")) {
|
|
22
|
+
const langTag = value.substring(value.lastIndexOf("@") + 1);
|
|
23
|
+
binding[bindingName] = { value: literalValue, type: "literal", "xml:lang": langTag };
|
|
24
|
+
} else if (lastDoubleQuote < value.lastIndexOf("^^")) {
|
|
25
|
+
const dataTag = value.substring(value.lastIndexOf("^^") + 2);
|
|
26
|
+
binding[bindingName] = { value: literalValue, type: "typed-literal", datatype: dataTag };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return binding;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const sparqlResults: Parser.SparqlResults = {
|
|
34
|
+
head: {
|
|
35
|
+
vars: headers,
|
|
36
|
+
},
|
|
37
|
+
results: {
|
|
38
|
+
bindings: sparqlData,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return sparqlResults;
|
|
43
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Parser from "./";
|
|
2
|
+
import * as N3 from "n3";
|
|
3
|
+
|
|
4
|
+
function n3TermToSparqlBinding(term: N3.Term): Parser.BindingValue {
|
|
5
|
+
if (term.termType === "NamedNode") {
|
|
6
|
+
return {
|
|
7
|
+
value: term.value,
|
|
8
|
+
type: "uri",
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
if (term.termType === "Literal") {
|
|
12
|
+
const bindingVal: Parser.BindingValue = {
|
|
13
|
+
value: term.value,
|
|
14
|
+
type: "literal",
|
|
15
|
+
};
|
|
16
|
+
const lang = term.language;
|
|
17
|
+
if (lang) bindingVal["xml:lang"] = lang;
|
|
18
|
+
const type = term.datatypeString;
|
|
19
|
+
if (type) bindingVal.datatype = type;
|
|
20
|
+
|
|
21
|
+
return bindingVal;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (term.termType === "BlankNode") {
|
|
25
|
+
return {
|
|
26
|
+
value: term.value,
|
|
27
|
+
type: "bnode",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
value: term.value,
|
|
32
|
+
type: "uri",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export default function (queryResponse: any): Parser.SparqlResults {
|
|
36
|
+
const statements = getTurtleAsStatements(queryResponse);
|
|
37
|
+
const vars = ["subject", "predicate", "object"];
|
|
38
|
+
const bindings = statements.map((statement) => {
|
|
39
|
+
const binding: Parser.Binding = {
|
|
40
|
+
subject: n3TermToSparqlBinding(statement.subject),
|
|
41
|
+
predicate: n3TermToSparqlBinding(statement.predicate),
|
|
42
|
+
object: n3TermToSparqlBinding(statement.object),
|
|
43
|
+
};
|
|
44
|
+
return binding;
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
head: {
|
|
48
|
+
vars: vars,
|
|
49
|
+
},
|
|
50
|
+
results: {
|
|
51
|
+
bindings: bindings,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function getTurtleAsStatements(queryResponse: any): N3.Quad[] {
|
|
56
|
+
const parser = new N3.Parser();
|
|
57
|
+
// When no the response has no body use an empty string
|
|
58
|
+
const parsed = parser.parse(queryResponse || "");
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import Parser from "./";
|
|
2
|
+
import { unescape, isEmpty } from "lodash-es";
|
|
3
|
+
function parseHead(node: ChildNode): Parser.SparqlResults["head"] {
|
|
4
|
+
const head: Parser.SparqlResults["head"] = {
|
|
5
|
+
vars: [],
|
|
6
|
+
};
|
|
7
|
+
for (let headNodeIt = 0; headNodeIt < node.childNodes.length; headNodeIt++) {
|
|
8
|
+
const headNode = node.childNodes[headNodeIt] as Element;
|
|
9
|
+
if (headNode.nodeName == "variable") {
|
|
10
|
+
const name = headNode.getAttribute("name");
|
|
11
|
+
if (name) head.vars.push(name);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return head;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const allowedBindingValueTypes: Array<Parser.BindingValue["type"]> = ["uri", "literal", "bnode", "typed-literal"];
|
|
18
|
+
function parseResults(node: ChildNode, postProcessBinding: Parser.PostProcessBinding): Parser.SparqlResults["results"] {
|
|
19
|
+
const results: Parser.SparqlResults["results"] = {
|
|
20
|
+
bindings: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
for (let resultIt = 0; resultIt < node.childNodes.length; resultIt++) {
|
|
24
|
+
const resultNode = node.childNodes[resultIt];
|
|
25
|
+
const binding: Parser.Binding = {};
|
|
26
|
+
|
|
27
|
+
for (let bindingIt = 0; bindingIt < resultNode.childNodes.length; bindingIt++) {
|
|
28
|
+
const bindingNode = resultNode.childNodes[bindingIt] as Element;
|
|
29
|
+
if (bindingNode.nodeName == "binding") {
|
|
30
|
+
const varName = bindingNode.getAttribute("name");
|
|
31
|
+
if (varName) {
|
|
32
|
+
for (let bindingInfIt = 0; bindingInfIt < bindingNode.childNodes.length; bindingInfIt++) {
|
|
33
|
+
const bindingInf = bindingNode.childNodes[bindingInfIt] as Element;
|
|
34
|
+
if (bindingInf.nodeName === "#text") continue;
|
|
35
|
+
if (allowedBindingValueTypes.indexOf(bindingInf.nodeName as any) < 0) continue;
|
|
36
|
+
const bindingValue: Parser.BindingValue = {
|
|
37
|
+
type: bindingInf.nodeName as Parser.BindingValue["type"],
|
|
38
|
+
value: unescape(bindingInf.innerHTML),
|
|
39
|
+
};
|
|
40
|
+
const dataType = bindingInf.getAttribute("datatype");
|
|
41
|
+
if (dataType) bindingValue.datatype = dataType;
|
|
42
|
+
binding[varName] = bindingValue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!isEmpty) {
|
|
49
|
+
}
|
|
50
|
+
if (!isEmpty(binding)) results.bindings.push(postProcessBinding(binding));
|
|
51
|
+
}
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseBoolean(node: Element) {
|
|
56
|
+
return node.innerHTML === "true";
|
|
57
|
+
}
|
|
58
|
+
export default function (
|
|
59
|
+
xmlString: string,
|
|
60
|
+
postProcessBinding: Parser.PostProcessBinding
|
|
61
|
+
): Parser.SparqlResults | undefined {
|
|
62
|
+
if (typeof xmlString !== "string") return;
|
|
63
|
+
const domParser = new DOMParser();
|
|
64
|
+
let mainXml = domParser.parseFromString(xmlString, "text/xml");
|
|
65
|
+
if (!mainXml.childNodes.length) return;
|
|
66
|
+
|
|
67
|
+
const xml = mainXml.childNodes[0];
|
|
68
|
+
|
|
69
|
+
const json: Partial<Parser.SparqlResults> = {};
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < xml.childNodes.length; i++) {
|
|
72
|
+
const node = xml.childNodes[i];
|
|
73
|
+
if (node.nodeName == "head") json.head = parseHead(node as Element);
|
|
74
|
+
if (node.nodeName == "results") json.results = parseResults(node as Element, postProcessBinding);
|
|
75
|
+
if (node.nodeName == "boolean") json.boolean = parseBoolean(node as Element);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return json as Parser.SparqlResults;
|
|
79
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make sure not to include any deps from our main index file. That way, we can easily publish the publin as standalone build
|
|
3
|
+
*/
|
|
4
|
+
import Yasr from "../../";
|
|
5
|
+
import { Plugin } from "../";
|
|
6
|
+
require("./index.scss");
|
|
7
|
+
export interface PluginConfig {}
|
|
8
|
+
import { drawSvgStringAsElement } from "@matdata/yasgui-utils";
|
|
9
|
+
const cross =
|
|
10
|
+
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" width="30" height="30" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><g><path d="M83.288,88.13c-2.114,2.112-5.575,2.112-7.689,0L53.659,66.188c-2.114-2.112-5.573-2.112-7.687,0L24.251,87.907 c-2.113,2.114-5.571,2.114-7.686,0l-4.693-4.691c-2.114-2.114-2.114-5.573,0-7.688l21.719-21.721c2.113-2.114,2.113-5.573,0-7.686 L11.872,24.4c-2.114-2.113-2.114-5.571,0-7.686l4.842-4.842c2.113-2.114,5.571-2.114,7.686,0L46.12,33.591 c2.114,2.114,5.572,2.114,7.688,0l21.721-21.719c2.114-2.114,5.573-2.114,7.687,0l4.695,4.695c2.111,2.113,2.111,5.571-0.003,7.686 L66.188,45.973c-2.112,2.114-2.112,5.573,0,7.686L88.13,75.602c2.112,2.111,2.112,5.572,0,7.687L83.288,88.13z"/></g></svg>';
|
|
11
|
+
const check =
|
|
12
|
+
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" width="30" height="30" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path d="M14.301,49.982l22.606,17.047L84.361,4.903c2.614-3.733,7.76-4.64,11.493-2.026l0.627,0.462 c3.732,2.614,4.64,7.758,2.025,11.492l-51.783,79.77c-1.955,2.791-3.896,3.762-7.301,3.988c-3.405,0.225-5.464-1.039-7.508-3.084 L2.447,61.814c-3.263-3.262-3.263-8.553,0-11.814l0.041-0.019C5.75,46.718,11.039,46.718,14.301,49.982z"/></svg>';
|
|
13
|
+
|
|
14
|
+
export default class Boolean implements Plugin<PluginConfig> {
|
|
15
|
+
private yasr: Yasr;
|
|
16
|
+
public priority = 10;
|
|
17
|
+
hideFromSelection = true;
|
|
18
|
+
constructor(yasr: Yasr) {
|
|
19
|
+
this.yasr = yasr;
|
|
20
|
+
}
|
|
21
|
+
draw() {
|
|
22
|
+
const el = document.createElement("div");
|
|
23
|
+
el.className = "booleanResult";
|
|
24
|
+
|
|
25
|
+
const boolVal = this.yasr.results?.getBoolean();
|
|
26
|
+
el.appendChild(drawSvgStringAsElement(boolVal ? check : cross));
|
|
27
|
+
const textEl = document.createElement("span");
|
|
28
|
+
textEl.textContent = boolVal ? "True" : "False";
|
|
29
|
+
el.appendChild(textEl);
|
|
30
|
+
|
|
31
|
+
this.yasr.resultsEl.appendChild(el);
|
|
32
|
+
}
|
|
33
|
+
canHandleResults() {
|
|
34
|
+
return (
|
|
35
|
+
!!this.yasr.results?.getBoolean &&
|
|
36
|
+
(this.yasr.results.getBoolean() === true || this.yasr.results.getBoolean() == false)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
getIcon() {
|
|
40
|
+
return document.createElement("");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.yasr {
|
|
2
|
+
.errorResult {
|
|
3
|
+
padding: 10px;
|
|
4
|
+
.errorHeader {
|
|
5
|
+
// display:flex;
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
.yasr_tryQuery {
|
|
8
|
+
float: right;
|
|
9
|
+
// color: #428bca;
|
|
10
|
+
text-decoration: none;
|
|
11
|
+
padding-top: 3px;
|
|
12
|
+
padding-bottom: 3px;
|
|
13
|
+
}
|
|
14
|
+
span.status {
|
|
15
|
+
display: inline-block;
|
|
16
|
+
padding: 0.35em 0.5rem;
|
|
17
|
+
font-size: 75%;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
line-height: 1.35;
|
|
20
|
+
text-align: center;
|
|
21
|
+
white-space: nowrap;
|
|
22
|
+
vertical-align: baseline;
|
|
23
|
+
border-radius: 0.25em;
|
|
24
|
+
color: #fff;
|
|
25
|
+
background-color: #dc3545;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
.errorMessageContainer {
|
|
29
|
+
display: flex;
|
|
30
|
+
.errorMessage {
|
|
31
|
+
flex-grow: 1;
|
|
32
|
+
width: 0;
|
|
33
|
+
min-width: 100px;
|
|
34
|
+
overflow: auto;
|
|
35
|
+
display: block;
|
|
36
|
+
padding: 10px;
|
|
37
|
+
margin: 10px 0 10px;
|
|
38
|
+
font-size: 13px;
|
|
39
|
+
line-height: 1.42857;
|
|
40
|
+
word-break: break-all;
|
|
41
|
+
word-wrap: break-word;
|
|
42
|
+
color: #333;
|
|
43
|
+
background-color: #f5f5f5;
|
|
44
|
+
border: 1px solid #ccc;
|
|
45
|
+
border-radius: 4px;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
.redOutline {
|
|
49
|
+
color: #a94442;
|
|
50
|
+
background-color: #f2dede;
|
|
51
|
+
margin-top: 10px;
|
|
52
|
+
padding: 5px 1em;
|
|
53
|
+
border: 1px solid #ebccd1;
|
|
54
|
+
border-radius: 4px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make sure not to include any deps from our main index file. That way, we can easily publish the publin as standalone build
|
|
3
|
+
*/
|
|
4
|
+
import { Plugin } from "../";
|
|
5
|
+
import Yasr from "../../";
|
|
6
|
+
import { addClass } from "@matdata/yasgui-utils";
|
|
7
|
+
require("./index.scss");
|
|
8
|
+
|
|
9
|
+
export default class Error implements Plugin<never> {
|
|
10
|
+
private yasr: Yasr;
|
|
11
|
+
constructor(yasr: Yasr) {
|
|
12
|
+
this.yasr = yasr;
|
|
13
|
+
}
|
|
14
|
+
canHandleResults() {
|
|
15
|
+
return !!this.yasr.results && !!this.yasr.results.getError();
|
|
16
|
+
}
|
|
17
|
+
private getTryBtn(link: string) {
|
|
18
|
+
const tryBtn = document.createElement("a");
|
|
19
|
+
tryBtn.href = link;
|
|
20
|
+
tryBtn.rel = "noopener noreferrer";
|
|
21
|
+
tryBtn.target = "_blank";
|
|
22
|
+
tryBtn.className = "yasr_tryQuery";
|
|
23
|
+
tryBtn.textContent = "Try query in a new browser window";
|
|
24
|
+
return tryBtn;
|
|
25
|
+
}
|
|
26
|
+
private getCorsMessage() {
|
|
27
|
+
const corsEl = document.createElement("div");
|
|
28
|
+
corsEl.className = "redOutline";
|
|
29
|
+
const mainMsg = document.createElement("p");
|
|
30
|
+
mainMsg.textContent = "Unable to get response from endpoint. Possible reasons:";
|
|
31
|
+
corsEl.appendChild(mainMsg);
|
|
32
|
+
|
|
33
|
+
const list = document.createElement("ul");
|
|
34
|
+
const incorrectEndpoint = document.createElement("li");
|
|
35
|
+
incorrectEndpoint.textContent = "Incorrect endpoint URL";
|
|
36
|
+
list.appendChild(incorrectEndpoint);
|
|
37
|
+
|
|
38
|
+
const endpointDown = document.createElement("li");
|
|
39
|
+
endpointDown.textContent = "Endpoint is down";
|
|
40
|
+
list.appendChild(endpointDown);
|
|
41
|
+
|
|
42
|
+
const cors = document.createElement("li");
|
|
43
|
+
const firstPart = document.createElement("span");
|
|
44
|
+
firstPart.textContent = "Endpoint is not accessible from the YASGUI server and website, and the endpoint is not ";
|
|
45
|
+
cors.appendChild(firstPart);
|
|
46
|
+
const secondPart = document.createElement("a");
|
|
47
|
+
secondPart.textContent = "CORS-enabled";
|
|
48
|
+
secondPart.href = "http://enable-cors.org/";
|
|
49
|
+
secondPart.target = "_blank";
|
|
50
|
+
secondPart.rel = "noopener noreferrer";
|
|
51
|
+
cors.appendChild(secondPart);
|
|
52
|
+
list.appendChild(cors);
|
|
53
|
+
|
|
54
|
+
corsEl.appendChild(list);
|
|
55
|
+
return corsEl;
|
|
56
|
+
}
|
|
57
|
+
async draw() {
|
|
58
|
+
const el = document.createElement("div");
|
|
59
|
+
el.className = "errorResult";
|
|
60
|
+
this.yasr.resultsEl.appendChild(el);
|
|
61
|
+
|
|
62
|
+
const error = this.yasr.results?.getError();
|
|
63
|
+
if (!error) return;
|
|
64
|
+
const header = document.createElement("div");
|
|
65
|
+
header.className = "errorHeader";
|
|
66
|
+
el.appendChild(header);
|
|
67
|
+
|
|
68
|
+
// Try whether a custom rendering of the error exists
|
|
69
|
+
const newMessage = await this.yasr.renderError(error);
|
|
70
|
+
if (newMessage) {
|
|
71
|
+
const customMessage = document.createElement("div");
|
|
72
|
+
customMessage.className = "redOutline";
|
|
73
|
+
customMessage.appendChild(newMessage);
|
|
74
|
+
el.appendChild(customMessage);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// No custom rendering? Let's render it ourselves!
|
|
79
|
+
if (error.status) {
|
|
80
|
+
var statusText = "Error";
|
|
81
|
+
if (error.statusText && error.statusText.length < 100) {
|
|
82
|
+
//use a max: otherwise the alert span will look ugly
|
|
83
|
+
statusText = error.statusText;
|
|
84
|
+
}
|
|
85
|
+
statusText += ` (#${error.status})`;
|
|
86
|
+
const statusTextEl = document.createElement("span");
|
|
87
|
+
statusTextEl.className = "status";
|
|
88
|
+
statusTextEl.textContent = statusText;
|
|
89
|
+
|
|
90
|
+
header.appendChild(statusTextEl);
|
|
91
|
+
if (this.yasr.config.getPlainQueryLinkToEndpoint) {
|
|
92
|
+
const link = this.yasr.config.getPlainQueryLinkToEndpoint();
|
|
93
|
+
if (link) header.appendChild(this.getTryBtn(link));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (error.text) {
|
|
97
|
+
const textContainer = document.createElement("div");
|
|
98
|
+
addClass(textContainer, "errorMessageContainer");
|
|
99
|
+
el.appendChild(textContainer);
|
|
100
|
+
const errTextEl = document.createElement("pre");
|
|
101
|
+
addClass(errTextEl, "errorMessage");
|
|
102
|
+
errTextEl.textContent = error.text;
|
|
103
|
+
textContainer.appendChild(errTextEl);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
if (this.yasr.config.getPlainQueryLinkToEndpoint) {
|
|
107
|
+
const link = this.yasr.config.getPlainQueryLinkToEndpoint();
|
|
108
|
+
if (link) header.appendChild(this.getTryBtn(link));
|
|
109
|
+
}
|
|
110
|
+
if (!error.text || error.text.indexOf("Request has been terminated") >= 0) {
|
|
111
|
+
el.appendChild(this.getCorsMessage());
|
|
112
|
+
} else {
|
|
113
|
+
const errTextEl = document.createElement("pre");
|
|
114
|
+
errTextEl.textContent = error.text;
|
|
115
|
+
el.appendChild(errTextEl);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
getIcon() {
|
|
120
|
+
return document.createElement("");
|
|
121
|
+
}
|
|
122
|
+
priority = 20;
|
|
123
|
+
hideFromSelection = true;
|
|
124
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface Plugin<Opts extends any> {
|
|
2
|
+
priority: number;
|
|
3
|
+
canHandleResults(): boolean;
|
|
4
|
+
hideFromSelection?: boolean;
|
|
5
|
+
// getPersistentSettings?: () => any;
|
|
6
|
+
label?: string;
|
|
7
|
+
options?: Opts;
|
|
8
|
+
|
|
9
|
+
initialize?(): Promise<void>;
|
|
10
|
+
destroy?(): void;
|
|
11
|
+
draw(persistentConfig: any, runtimeConfig?: any): Promise<void> | void;
|
|
12
|
+
getIcon(): Element | undefined;
|
|
13
|
+
download?(filename?: string): DownloadInfo | undefined;
|
|
14
|
+
helpReference?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface DownloadInfo {
|
|
17
|
+
contentType: string;
|
|
18
|
+
/**
|
|
19
|
+
* File contents as a string or a data url
|
|
20
|
+
*/
|
|
21
|
+
getData: () => string;
|
|
22
|
+
filename: string;
|
|
23
|
+
title: string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
.yasr {
|
|
2
|
+
.yasr_results {
|
|
3
|
+
position: relative;
|
|
4
|
+
.CodeMirror {
|
|
5
|
+
border: 1px solid #d1d1d1;
|
|
6
|
+
margin-top: 5px;
|
|
7
|
+
height: 100%;
|
|
8
|
+
&.overflow::before {
|
|
9
|
+
background: linear-gradient(transparent 0%, #ffffffaa 75%, white 100%);
|
|
10
|
+
content: "";
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
z-index: 1200;
|
|
14
|
+
position: absolute;
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
.overlay {
|
|
19
|
+
width: 100%;
|
|
20
|
+
position: absolute;
|
|
21
|
+
bottom: 0;
|
|
22
|
+
z-index: 1201;
|
|
23
|
+
display: flex;
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
&::before,
|
|
26
|
+
&::after {
|
|
27
|
+
content: "";
|
|
28
|
+
flex-grow: 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.overlay_content {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-content: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
align-items: center;
|
|
37
|
+
background: white;
|
|
38
|
+
pointer-events: all;
|
|
39
|
+
background: linear-gradient(to right, transparent, white 5%, white 95%, transparent 100%);
|
|
40
|
+
}
|
|
41
|
+
.yasr_btn.overlay_btn {
|
|
42
|
+
border: 1px solid #337ab7;
|
|
43
|
+
background: white;
|
|
44
|
+
color: #337ab7;
|
|
45
|
+
padding: 10px;
|
|
46
|
+
margin: 10px;
|
|
47
|
+
svg {
|
|
48
|
+
margin-left: 0.5rem;
|
|
49
|
+
fill: #337ab7;
|
|
50
|
+
color: #337ab7;
|
|
51
|
+
}
|
|
52
|
+
&:hover {
|
|
53
|
+
color: #255681;
|
|
54
|
+
border-color: #337ab7;
|
|
55
|
+
fill: #255681;
|
|
56
|
+
svg {
|
|
57
|
+
color: #255681;
|
|
58
|
+
fill: #255681;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make sure not to include any deps from our main index file. That way, we can easily publish the publin as standalone build
|
|
3
|
+
*/
|
|
4
|
+
import { Plugin } from "../";
|
|
5
|
+
import Yasr from "../../";
|
|
6
|
+
require("./index.scss");
|
|
7
|
+
const CodeMirror = require("codemirror");
|
|
8
|
+
require("codemirror/addon/fold/foldcode.js");
|
|
9
|
+
require("codemirror/addon/fold/foldgutter.js");
|
|
10
|
+
require("codemirror/addon/fold/xml-fold.js");
|
|
11
|
+
require("codemirror/addon/fold/brace-fold.js");
|
|
12
|
+
|
|
13
|
+
require("codemirror/addon/edit/matchbrackets.js");
|
|
14
|
+
require("codemirror/mode/xml/xml.js");
|
|
15
|
+
|
|
16
|
+
require("codemirror/mode/javascript/javascript.js");
|
|
17
|
+
require("codemirror/lib/codemirror.css");
|
|
18
|
+
import { drawSvgStringAsElement, addClass, removeClass, drawFontAwesomeIconAsSvg } from "@matdata/yasgui-utils";
|
|
19
|
+
import * as faAlignIcon from "@fortawesome/free-solid-svg-icons/faAlignLeft";
|
|
20
|
+
import { DeepReadonly } from "ts-essentials";
|
|
21
|
+
import * as imgs from "../../imgs";
|
|
22
|
+
|
|
23
|
+
export interface PluginConfig {
|
|
24
|
+
maxLines: number;
|
|
25
|
+
}
|
|
26
|
+
export default class Response implements Plugin<PluginConfig> {
|
|
27
|
+
private yasr: Yasr;
|
|
28
|
+
label = "Response";
|
|
29
|
+
priority = 2;
|
|
30
|
+
helpReference = "https://docs.triply.cc/yasgui/#response";
|
|
31
|
+
private config: DeepReadonly<PluginConfig>;
|
|
32
|
+
private overLay: HTMLDivElement | undefined;
|
|
33
|
+
private cm: CodeMirror.Editor | undefined;
|
|
34
|
+
constructor(yasr: Yasr) {
|
|
35
|
+
this.yasr = yasr;
|
|
36
|
+
this.config = Response.defaults;
|
|
37
|
+
if (yasr.config.plugins["response"] && yasr.config.plugins["response"].dynamicConfig) {
|
|
38
|
+
this.config = {
|
|
39
|
+
...this.config,
|
|
40
|
+
...yasr.config.plugins["response"].dynamicConfig,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// getDownloadInfo: getDownloadInfo
|
|
45
|
+
canHandleResults() {
|
|
46
|
+
if (!this.yasr.results) return false;
|
|
47
|
+
if (!this.yasr.results.getOriginalResponseAsString) return false;
|
|
48
|
+
var response = this.yasr.results.getOriginalResponseAsString();
|
|
49
|
+
if ((!response || response.length == 0) && this.yasr.results.getError()) return false; //in this case, show exception instead, as we have nothing to show anyway
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
public getIcon() {
|
|
53
|
+
return drawSvgStringAsElement(drawFontAwesomeIconAsSvg(faAlignIcon));
|
|
54
|
+
}
|
|
55
|
+
download(filename?: string) {
|
|
56
|
+
if (!this.yasr.results) return;
|
|
57
|
+
const contentType = this.yasr.results.getContentType();
|
|
58
|
+
const type = this.yasr.results.getType();
|
|
59
|
+
const extension = type === "xml" ? "rdf" : type;
|
|
60
|
+
return {
|
|
61
|
+
getData: () => {
|
|
62
|
+
return this.yasr.results?.getOriginalResponseAsString() || "";
|
|
63
|
+
},
|
|
64
|
+
filename: `${filename || "queryResults"}${extension ? "." + extension : ""}`,
|
|
65
|
+
contentType: contentType ? contentType : "text/plain",
|
|
66
|
+
title: "Download result",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
draw(persistentConfig: PluginConfig) {
|
|
70
|
+
const config: DeepReadonly<PluginConfig> = {
|
|
71
|
+
...this.config,
|
|
72
|
+
...persistentConfig,
|
|
73
|
+
};
|
|
74
|
+
// When the original response is empty, use an empty string
|
|
75
|
+
let value = this.yasr.results?.getOriginalResponseAsString() || "";
|
|
76
|
+
const lines = value.split("\n");
|
|
77
|
+
if (lines.length > config.maxLines) {
|
|
78
|
+
value = lines.slice(0, config.maxLines).join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const codemirrorOpts: Partial<CodeMirror.EditorConfiguration> = {
|
|
82
|
+
readOnly: true,
|
|
83
|
+
lineNumbers: true,
|
|
84
|
+
lineWrapping: true,
|
|
85
|
+
foldGutter: true,
|
|
86
|
+
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
|
87
|
+
value: value,
|
|
88
|
+
extraKeys: { Tab: false },
|
|
89
|
+
};
|
|
90
|
+
const mode = this.yasr.results?.getType();
|
|
91
|
+
if (mode === "json") {
|
|
92
|
+
codemirrorOpts.mode = { name: "javascript", json: true };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.cm = CodeMirror(this.yasr.resultsEl, codemirrorOpts);
|
|
96
|
+
// Don't show less originally we've already set the value in the codemirrorOpts
|
|
97
|
+
if (lines.length > config.maxLines) this.showLess(false);
|
|
98
|
+
}
|
|
99
|
+
private limitData(value: string) {
|
|
100
|
+
const lines = value.split("\n");
|
|
101
|
+
if (lines.length > this.config.maxLines) {
|
|
102
|
+
value = lines.slice(0, this.config.maxLines).join("\n");
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
*
|
|
108
|
+
* @param setValue Optional, if set to false the string will not update
|
|
109
|
+
*/
|
|
110
|
+
showLess(setValue = true) {
|
|
111
|
+
if (!this.cm) return;
|
|
112
|
+
// Add overflow
|
|
113
|
+
addClass(this.cm.getWrapperElement(), "overflow");
|
|
114
|
+
|
|
115
|
+
// Remove old instance
|
|
116
|
+
if (this.overLay) {
|
|
117
|
+
this.overLay.remove();
|
|
118
|
+
this.overLay = undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Wrapper
|
|
122
|
+
this.overLay = document.createElement("div");
|
|
123
|
+
addClass(this.overLay, "overlay");
|
|
124
|
+
|
|
125
|
+
// overlay content
|
|
126
|
+
const overlayContent = document.createElement("div");
|
|
127
|
+
addClass(overlayContent, "overlay_content");
|
|
128
|
+
|
|
129
|
+
const showMoreButton = document.createElement("button");
|
|
130
|
+
showMoreButton.title = "Show all";
|
|
131
|
+
addClass(showMoreButton, "yasr_btn", "overlay_btn");
|
|
132
|
+
showMoreButton.textContent = "Show all";
|
|
133
|
+
showMoreButton.addEventListener("click", () => this.showMore());
|
|
134
|
+
overlayContent.append(showMoreButton);
|
|
135
|
+
|
|
136
|
+
const downloadButton = document.createElement("button");
|
|
137
|
+
downloadButton.title = "Download result";
|
|
138
|
+
addClass(downloadButton, "yasr_btn", "overlay_btn");
|
|
139
|
+
|
|
140
|
+
const text = document.createElement("span");
|
|
141
|
+
text.innerText = "Download result";
|
|
142
|
+
downloadButton.appendChild(text);
|
|
143
|
+
downloadButton.appendChild(drawSvgStringAsElement(imgs.download));
|
|
144
|
+
downloadButton.addEventListener("click", () => this.yasr.download());
|
|
145
|
+
downloadButton.addEventListener("keydown", (event) => {
|
|
146
|
+
if (event.code === "Space" || event.code === "Enter") this.yasr.download();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
overlayContent.appendChild(downloadButton);
|
|
150
|
+
this.overLay.appendChild(overlayContent);
|
|
151
|
+
this.cm.getWrapperElement().appendChild(this.overLay);
|
|
152
|
+
if (setValue) {
|
|
153
|
+
this.cm.setValue(this.limitData(this.yasr.results?.getOriginalResponseAsString() || ""));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Render the raw response full length
|
|
158
|
+
*/
|
|
159
|
+
showMore() {
|
|
160
|
+
if (!this.cm) return;
|
|
161
|
+
removeClass(this.cm.getWrapperElement(), "overflow");
|
|
162
|
+
this.overLay?.remove();
|
|
163
|
+
this.overLay = undefined;
|
|
164
|
+
this.cm.setValue(this.yasr.results?.getOriginalResponseAsString() || "");
|
|
165
|
+
this.cm.refresh();
|
|
166
|
+
}
|
|
167
|
+
public static defaults: PluginConfig = {
|
|
168
|
+
maxLines: 30,
|
|
169
|
+
};
|
|
170
|
+
}
|